Skip to content

Commit

Permalink
feat: endorsement chain provider handling (#807)
Browse files Browse the repository at this point in the history
* feat: handling for slow/errored rpc
* fix: error handling, added infura polygon mainnet
  • Loading branch information
osslgtm authored Aug 24, 2023
1 parent 9ba5bbc commit dcba964
Show file tree
Hide file tree
Showing 10 changed files with 899 additions and 329 deletions.
1,095 changes: 780 additions & 315 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@govtechsg/open-attestation-cli": "^2.5.0",
"@govtechsg/open-attestation-utils": "^1.0.9",
"@govtechsg/token-registry": "^4.1.7",
"@govtechsg/tradetrust-ui-components": "^2.19.2",
"@govtechsg/tradetrust-ui-components": "^2.21.0",
"@govtechsg/tradetrust-utils": "^1.6.0",
"@reduxjs/toolkit": "^1.6.1",
"@synthetixio/synpress": "^3.5.1",
Expand All @@ -80,6 +80,7 @@
"react-markdown": "^6.0.2",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-timer-hook": "^3.0.7",
"react-tooltip": "^4.2.21",
"redux": "^4.1.0",
"redux-saga": "^1.1.3",
Expand Down
8 changes: 3 additions & 5 deletions src/common/hooks/useEndorsementChain/useEndorsementChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { mergeTransfers } from "./helpers";
import { fetchTokenTransfers } from "./fetchTokenTransfer";
import { getEndorsementChain } from "./retrieveEndorsementChain";
import { retrieveTitleEscrowAddressOnFactory } from "../useTitleEscrowContract";
import { getErrorMessage } from "../../utils/errorHandling";

export const useEndorsementChain = (
tokenRegistryAddress: string,
Expand Down Expand Up @@ -36,11 +37,8 @@ export const useEndorsementChain = (
const transferEvents = mergeTransfers([...titleEscrowLogs, ...tokenLogs]);
const retrievedEndorsementChain = await getEndorsementChain(provider, transferEvents);
setEndorsementChain(retrievedEndorsementChain);
} catch (e) {
if (e instanceof Error) {
console.error(e);
setError(e.message);
}
} catch (e: unknown) {
setError(getErrorMessage(e));
}
setPending(false);
}, [provider, providerOrSigner, tokenId, tokenRegistry]);
Expand Down
26 changes: 26 additions & 0 deletions src/common/utils/errorHandling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type ErrorWithMessage = {
message: string;
};

export const isErrorWithMessage = (error: unknown): error is ErrorWithMessage => {
return (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof (error as Record<string, unknown>).message === "string"
);
};

export const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => {
if (isErrorWithMessage(maybeError)) return maybeError;

try {
return new Error(JSON.stringify(maybeError));
} catch {
return new Error(String(maybeError));
}
};

export const getErrorMessage = (error: unknown): string => {
return toErrorWithMessage(error).message;
};
45 changes: 43 additions & 2 deletions src/components/EndorsementChain/EndorsementChainContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { FunctionComponent } from "react";
import React, { FunctionComponent, useContext, useEffect } from "react";
import { useEndorsementChain } from "../../common/hooks/useEndorsementChain";
import { EndorsementChainLayout } from "./EndorsementChainLayout";
import { OverlayContext, OverlayContextProvider, ProviderTimeoutMessage } from "@govtechsg/tradetrust-ui-components";
import { useTimer } from "react-timer-hook";

const ProviderDocumentationURL = "https://docs.tradetrust.io/docs/advanced/add-polygon-networks-to-metamask-wallet/";
const timeout = 60;

interface EndorsementChainContainer {
tokenRegistry: string;
Expand All @@ -14,5 +19,41 @@ export const EndorsementChainContainer: FunctionComponent<EndorsementChainContai
setShowEndorsementChain,
}) => {
const endorsementChainProps = useEndorsementChain(tokenRegistry, tokenId);
return <EndorsementChainLayout {...endorsementChainProps} setShowEndorsementChain={setShowEndorsementChain} />;
const expiryTimestamp = new Date();
const { showOverlay } = useContext(OverlayContext);
expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + timeout);

const handleProviderTimeout = () => {
showOverlay(<ProviderTimeoutMessage address={ProviderDocumentationURL} />);
};

const { start, pause } = useTimer({
expiryTimestamp,
onExpire: handleProviderTimeout,
});

const { error, pending } = endorsementChainProps;
useEffect(() => {
if (!!error || !pending) {
pause();
} else if (pending) {
start();
}
}, [start, pause, error, pending]);

useEffect(() => {
if (error) {
pause();
}
}, [error, pause]);

return (
<OverlayContextProvider>
<EndorsementChainLayout
{...endorsementChainProps}
setShowEndorsementChain={setShowEndorsementChain}
providerDocumentationURL={ProviderDocumentationURL}
/>
</OverlayContextProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const EndorsementChainError: FunctionComponent<EndorsementChainErrorProps
return (
<div className="text-center">
<AlertTriangle className="text-scarlet-500" />
<h4>{error} has occurred, please try again later.</h4>
<h4 className="break-words">{error} has occurred, please try again later.</h4>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,24 @@ const sampleSuccessEndorsementChain: EndorsementChain = [
];

export const Loading = () => {
return <EndorsementChainLayout pending={true} setShowEndorsementChain={() => {}} />;
return <EndorsementChainLayout providerDocumentationURL={""} pending={true} setShowEndorsementChain={() => {}} />;
};

export const Error = () => {
return <EndorsementChainLayout error="UNKNOWN" pending={false} setShowEndorsementChain={() => {}} />;
return (
<EndorsementChainLayout
providerDocumentationURL={""}
error="UNKNOWN"
pending={false}
setShowEndorsementChain={() => {}}
/>
);
};

export const TransferHolder = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={transferHolderEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand All @@ -265,6 +273,7 @@ export const TransferHolder = () => {
export const EndorseBeneficiary = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={endorseBeneficiaryEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand All @@ -275,6 +284,7 @@ export const EndorseBeneficiary = () => {
export const ChangeOwners = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={changeOwnersEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand All @@ -285,6 +295,7 @@ export const ChangeOwners = () => {
export const Surrender = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={surrenderEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand All @@ -295,6 +306,7 @@ export const Surrender = () => {
export const RejectSurrendered = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={rejectSurrenderedEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand All @@ -305,6 +317,7 @@ export const RejectSurrendered = () => {
export const AcceptSurrendered = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={acceptSurrenderedEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand All @@ -315,6 +328,7 @@ export const AcceptSurrendered = () => {
export const SampleSuccessFlow = () => {
return (
<EndorsementChainLayout
providerDocumentationURL={""}
endorsementChain={sampleSuccessEndorsementChain}
pending={false}
setShowEndorsementChain={() => {}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,22 @@ const acceptSurrenderedEndorsementChain: EndorsementChain = [
describe("EndorsementChainLayout", () => {
it("should render the loading component when pending is true", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(<EndorsementChainLayout error={""} pending={true} setShowEndorsementChain={() => {}} />);
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={true}
setShowEndorsementChain={() => {}}
/>
);
expect(screen.getAllByTestId("loader-skeleton")).toHaveLength(9);
});

it("should render 'Document has been issued'", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={initialEndorsementChain}
Expand All @@ -147,6 +155,7 @@ describe("EndorsementChainLayout", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={transferHolderEndorsementChain}
Expand All @@ -163,6 +172,7 @@ describe("EndorsementChainLayout", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={endorseBeneficiaryEndorsementChain}
Expand All @@ -179,6 +189,7 @@ describe("EndorsementChainLayout", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={changeOwnersEndorsementChain}
Expand All @@ -198,6 +209,7 @@ describe("EndorsementChainLayout", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={surrenderEndorsementChain}
Expand All @@ -211,6 +223,7 @@ describe("EndorsementChainLayout", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={rejectSurrenderedEndorsementChain}
Expand All @@ -231,6 +244,7 @@ describe("EndorsementChainLayout", () => {
mockUseIdentifierResolver.mockReturnValue({ resolvedIdentifier: "FooBar" });
render(
<EndorsementChainLayout
providerDocumentationURL={""}
error={""}
pending={false}
endorsementChain={acceptSurrenderedEndorsementChain}
Expand All @@ -248,6 +262,7 @@ describe("EndorsementChainLayout", () => {
<EndorsementChainLayout
endorsementChain={transferHolderEndorsementChain}
setShowEndorsementChain={mockSetShowEndorsementChain}
providerDocumentationURL={""}
error={""}
pending={false}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface EndorsementChainLayout {
error?: string;
pending: boolean;
setShowEndorsementChain: (payload: boolean) => void;
providerDocumentationURL: string;
}

enum ActionType {
Expand Down Expand Up @@ -192,6 +193,7 @@ export const EndorsementChainLayout: FunctionComponent<EndorsementChainLayout> =
setShowEndorsementChain,
error,
pending,
providerDocumentationURL,
}) => {
const historyChain = getHistoryChain(endorsementChain);

Expand All @@ -203,6 +205,14 @@ export const EndorsementChainLayout: FunctionComponent<EndorsementChainLayout> =
<div className="my-4" data-testid="endorsement-chain-title">
<h3>Endorsement Chain</h3>
</div>
{!!error && (
<div className="py-3 bg-red-100" data-testid="endorsement-chain-error">
<p className="text-cloud-800 text-center">
There might be some issue with your Remote Procedure Call (RPC). Click{" "}
<a href={providerDocumentationURL}>here</a> to learn how to change your RPC Provider.
</p>
</div>
)}
<div className="bg-white rounded-xl shadow-xl px-3 py-8 lg:px-8">
<div className="hidden lg:block mb-8">
<div className="flex text-cloud-800">
Expand Down
4 changes: 2 additions & 2 deletions src/constants/chain-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const ChainInfo: ChainInfo = {
networkName: "matic",
networkLabel: "Polygon",
explorerUrl: "https://polygonscan.com",
rpcUrl: "https://polygon-rpc.com",
rpcUrl: `https://polygon-mainnet.infura.io/v3/${INFURA_API_KEY}`,
nativeCurrency: {
name: "MATIC",
symbol: "MATIC",
Expand All @@ -103,7 +103,7 @@ export const ChainInfo: ChainInfo = {
networkName: "maticmum",
networkLabel: "Polygon Mumbai",
explorerUrl: "https://mumbai.polygonscan.com",
rpcUrl: "https://rpc-mumbai.maticvigil.com",
rpcUrl: `https://polygon-mumbai.infura.io/v3/${INFURA_API_KEY}`,
nativeCurrency: {
name: "MATIC",
symbol: "mMATIC",
Expand Down

0 comments on commit dcba964

Please sign in to comment.