diff --git a/package-lock.json b/package-lock.json index 87c0aaf95..03b55f0b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@reduxjs/toolkit": "^1.6.1", "@tradetrust-tt/decentralized-renderer-react-components": "^3.14.1", "@tradetrust-tt/document-store": "^3.2.0", - "@tradetrust-tt/token-registry": "^4.12.1", + "@tradetrust-tt/token-registry": "^4.14.0", "@tradetrust-tt/tradetrust": "^6.9.4", "@tradetrust-tt/tradetrust-ui-components": "^2.22.2", "@tradetrust-tt/tradetrust-utils": "^1.14.1", @@ -9903,9 +9903,9 @@ } }, "node_modules/@tradetrust-tt/token-registry": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@tradetrust-tt/token-registry/-/token-registry-4.12.1.tgz", - "integrity": "sha512-GM/q+pyRSzoRAaHuIxt1W/tr45JXv1CPMwF4nYIZhofDtVJTbLIeQsEnBpzJSyCp4hSQUe1LDAI3ma6SZRMq8A==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@tradetrust-tt/token-registry/-/token-registry-4.15.0.tgz", + "integrity": "sha512-hwi/a8O1ng1Zj+gleg/9HaaggDGe8xxeVB0ghfVctrPFW93YXgPBlrGNPPox/02DmiMOSe5QDxYpRq7ZWgtwfA==", "dependencies": { "@typechain/ethers-v5": "10.2.1" }, diff --git a/package.json b/package.json index b7f476eae..e86f11ceb 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@reduxjs/toolkit": "^1.6.1", "@tradetrust-tt/decentralized-renderer-react-components": "^3.14.1", "@tradetrust-tt/document-store": "^3.2.0", - "@tradetrust-tt/token-registry": "^4.12.1", + "@tradetrust-tt/token-registry": "^4.14.0", "@tradetrust-tt/tradetrust": "^6.9.4", "@tradetrust-tt/tradetrust-ui-components": "^2.22.2", "@tradetrust-tt/tradetrust-utils": "^1.14.1", diff --git a/src/AppContainer.tsx b/src/AppContainer.tsx index fc6fa8156..d772eed43 100644 --- a/src/AppContainer.tsx +++ b/src/AppContainer.tsx @@ -1,9 +1,11 @@ -import { Overlay } from "@tradetrust-tt/tradetrust-ui-components"; +import { Button, ButtonSize, Overlay } from "@tradetrust-tt/tradetrust-ui-components"; import React, { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; import { Footer } from "./components/Layout/Footer"; import { NavigationBar, rightNavItems } from "./components/Layout/NavigationBar"; import { Routes, routes } from "./routes"; +import PopupMessage from "./components/PopupMessage"; +import { URLS } from "./constants"; const AppContainer = (): React.ReactElement => { const location = useLocation(); @@ -22,6 +24,31 @@ const AppContainer = (): React.ReactElement => { leftItems={[]} rightItems={rightNavItems} /> + +
+
+
+
+

+ To serve our community better, we are upgrading to a newer version that has new capabilities to your + transferable documents. +

+ +
+
+
+
= ({ children, }) => { diff --git a/src/common/utils/getDropzoneBoxUi.ts b/src/common/utils/getDropzoneBoxUi.ts index d6acfcd22..1184dcf0a 100644 --- a/src/common/utils/getDropzoneBoxUi.ts +++ b/src/common/utils/getDropzoneBoxUi.ts @@ -15,6 +15,7 @@ interface GetDropzoneBoxUi { isVerificationPending: boolean; isVerificationError: boolean | null; isActionError?: boolean | null; + isTokenRegistryV5?: boolean; } export const getDropzoneBoxUi = ({ @@ -24,6 +25,7 @@ export const getDropzoneBoxUi = ({ isVerificationPending, isVerificationError, isActionError, + isTokenRegistryV5, }: GetDropzoneBoxUi): string => { switch (true) { case isDragReject: @@ -35,6 +37,7 @@ export const getDropzoneBoxUi = ({ case isVerificationPending: return DropzoneBoxUiState.VERIFICATION_PENDING; case isVerificationError: + case isTokenRegistryV5: return DropzoneBoxUiState.VERIFICATION_ERROR; case isActionError: return DropzoneBoxUiState.ACTION_ERROR; diff --git a/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx b/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx index e675edcd1..258dd3a55 100644 --- a/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx +++ b/src/components/AssetManagementPanel/AssetManagementApplication/index.tsx @@ -8,6 +8,7 @@ import { AssetManagementTags } from "../AssetManagementTags"; import { DocumentStatus } from "../../DocumentStatus"; import { constants } from "@tradetrust-tt/token-registry"; import { useTokenRegistryRole } from "../../../common/hooks/useTokenRegistryRole"; +import { AssetInformationPanel } from "../AssetInformationPanel"; interface AssetManagementApplicationProps { isMagicDemo?: boolean; @@ -79,7 +80,17 @@ export const AssetManagementApplication: FunctionComponent - +
+
+ +
+
+ +
+
)} diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/AcceptSurrenderedForm/AcceptSurrenderedForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/AcceptSurrenderedForm/AcceptSurrenderedForm.tsx index 59750d509..e29c977a5 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/AcceptSurrenderedForm/AcceptSurrenderedForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/AcceptSurrenderedForm/AcceptSurrenderedForm.tsx @@ -7,7 +7,7 @@ import { } from "@tradetrust-tt/tradetrust-ui-components"; import React, { FunctionComponent, useContext, useEffect } from "react"; import { FormState } from "../../../../../constants/FormState"; -import { TagBorderedLg } from "../../../../UI/Tag"; +import { TagBorderedSm } from "../../../../UI/Tag"; import { AssetInformationPanel } from "../../../AssetInformationPanel"; import { AssetManagementActions } from "../../../AssetManagementActions"; import { AssetManagementTitle } from "../../AssetManagementTitle"; @@ -58,9 +58,9 @@ export const AcceptSurrenderedForm: FunctionComponent
- -

Surrendered To Issuer

-
+ +
Surrendered To Issuer
+
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx index aa6cf0e88..4c0b9a08c 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/ActionSelectionForm/ActionSelectionForm.tsx @@ -5,8 +5,7 @@ import { showDocumentTransferMessage, } from "@tradetrust-tt/tradetrust-ui-components"; import React, { FunctionComponent, useContext, useRef, useState } from "react"; -import { TagBorderedLg } from "../../../../UI/Tag"; -import { AssetInformationPanel } from "../../../AssetInformationPanel"; +import { TagBorderedSm } from "../../../../UI/Tag"; import { AssetManagementActions } from "../../../AssetManagementActions"; import { AssetManagementDropdown } from "../../AssetManagementDropdown"; import { EditableAssetTitle } from "./../EditableAssetTitle"; @@ -34,7 +33,6 @@ interface ActionSelectionFormProps { export const ActionSelectionForm: FunctionComponent = ({ onSetFormAction, - tokenRegistryAddress, beneficiary, holder, account, @@ -48,7 +46,6 @@ export const ActionSelectionForm: FunctionComponent = isTokenBurnt, canNominateBeneficiary, canEndorseTransfer, - setShowEndorsementChain, isTitleEscrow, }) => { const [tooltipMessage, setTooltipMessage] = useState("Copy"); @@ -105,29 +102,28 @@ export const ActionSelectionForm: FunctionComponent = return ( <>
-
- -
+ {(isSurrendered || isTokenBurnt) && ( +
+
+
+ )} {isSurrendered && (
- -

+ +
Surrendered To Issuer -
- +

+
)} {isTokenBurnt && (
- -

Surrendered

-
+ +
Surrendered
+
)} @@ -139,6 +135,7 @@ export const ActionSelectionForm: FunctionComponent =
+
)}
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EditableAssetTitle/EditableAssetTitle.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EditableAssetTitle/EditableAssetTitle.tsx index 865ba2b1c..a621842bb 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EditableAssetTitle/EditableAssetTitle.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EditableAssetTitle/EditableAssetTitle.tsx @@ -43,7 +43,7 @@ export const EditableAssetTitle: FunctionComponent = ({ return (
-
+
tokenRegistryAddress={tokenRegistryAddress} />
+
+
+
{beneficiaryEndorseState === FormState.ERROR && (
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EndorseTransferForm/EndorseTransferForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EndorseTransferForm/EndorseTransferForm.tsx index f76f1139e..5337fa2ae 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EndorseTransferForm/EndorseTransferForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/EndorseTransferForm/EndorseTransferForm.tsx @@ -68,6 +68,8 @@ export const EndorseTransferForm: FunctionComponent = tokenRegistryAddress={tokenRegistryAddress} />
+
+
= isError={transferOwnersState === FormState.ERROR} />
+
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/NominateBeneficiary/NominateBeneficiary.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/NominateBeneficiary/NominateBeneficiary.tsx index 3a4d1d7dc..1bdfb87e9 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/NominateBeneficiary/NominateBeneficiary.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/NominateBeneficiary/NominateBeneficiary.tsx @@ -68,6 +68,8 @@ export const NominateBeneficiaryForm: FunctionComponent
+
+
+
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/RejectSurrenderedForm/RejectSurrenderedForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/RejectSurrenderedForm/RejectSurrenderedForm.tsx index 004aa2ca6..1728d4ccf 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/RejectSurrenderedForm/RejectSurrenderedForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/RejectSurrenderedForm/RejectSurrenderedForm.tsx @@ -7,7 +7,7 @@ import { } from "@tradetrust-tt/tradetrust-ui-components"; import React, { FunctionComponent, useContext, useEffect } from "react"; import { FormState } from "../../../../../constants/FormState"; -import { TagBorderedLg } from "../../../../UI/Tag"; +import { TagBorderedSm } from "../../../../UI/Tag"; import { AssetInformationPanel } from "../../../AssetInformationPanel"; import { AssetManagementActions } from "../../../AssetManagementActions"; import { AssetManagementTitle } from "../../AssetManagementTitle"; @@ -73,9 +73,9 @@ export const RejectSurrenderedForm: FunctionComponent
- -

Surrendered To Issuer

-
+ +
Surrendered To Issuer
+
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/SurrenderForm/SurrenderForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/SurrenderForm/SurrenderForm.tsx index 90b4d0e5e..5ea99ad3b 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/SurrenderForm/SurrenderForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/SurrenderForm/SurrenderForm.tsx @@ -59,12 +59,15 @@ export const SurrenderForm: FunctionComponent = ({ tokenRegistryAddress={tokenRegistryAddress} />
+
+
+
diff --git a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/TransferHolderForm/TransferHolderForm.tsx b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/TransferHolderForm/TransferHolderForm.tsx index f7932085d..e4cae939a 100644 --- a/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/TransferHolderForm/TransferHolderForm.tsx +++ b/src/components/AssetManagementPanel/AssetManagementForm/FormVariants/TransferHolderForm/TransferHolderForm.tsx @@ -72,6 +72,8 @@ export const TransferHolderForm: FunctionComponent = ({ tokenRegistryAddress={tokenRegistryAddress} />
+
+
@@ -85,6 +87,7 @@ export const TransferHolderForm: FunctionComponent = ({ isError={holderTransferringState === FormState.ERROR} />
+
diff --git a/src/components/CertificateDropZone/CertificateDropZone.tsx b/src/components/CertificateDropZone/CertificateDropZone.tsx index dd3c8313a..6823dcb00 100644 --- a/src/components/CertificateDropZone/CertificateDropZone.tsx +++ b/src/components/CertificateDropZone/CertificateDropZone.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, useCallback, useMemo } from "react"; +import React, { FunctionComponent, useCallback, useEffect, useMemo, useState } from "react"; import { useDropzone } from "react-dropzone"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../../reducers"; @@ -16,6 +16,7 @@ import { useProviderContext } from "../../common/contexts/provider"; import { getChainId } from "../../utils/shared"; import { CONSTANTS } from "@tradetrust-tt/tradetrust-utils"; import { useNetworkSelect } from "./../../common/hooks/useNetworkSelect"; +import { ViewTokenRegistryMismatch } from "../DocumentDropzone/Views/ViewTokenRegistryMismatch"; const { TYPES } = CONSTANTS; @@ -26,11 +27,16 @@ interface CertificateDropzoneProps { export const CertificateDropZone: FunctionComponent = (props) => { const { toggleQrReaderVisible } = props; const dispatch = useDispatch(); - const { verificationPending, retrieveCertificateByActionState, verificationStatus, verificationError } = useSelector( - (state: RootState) => state.certificate - ); + const { + verificationPending, + retrieveCertificateByActionState, + verificationStatus, + verificationError, + tokenRegistryV5, + } = useSelector((state: RootState) => state.certificate); const isVerificationPending = verificationPending; + const isTokenRegistryV5 = tokenRegistryV5; const isVerificationError = useMemo(() => { if (verificationError) return true; if (verificationStatus && !isValid(verificationStatus)) return true; @@ -56,10 +62,17 @@ export const CertificateDropZone: FunctionComponent = try { const json = JSON.parse(reader.result as string); const chainId = getChainId(json); - if (chainId && currentChainId !== chainId) { + if (!chainId) { + dispatch(updateCertificate(json)); + return; + } + if (currentChainId === chainId) { + dispatch(updateCertificate(json)); + } else { await switchNetwork(chainId); + setTargetChainId(chainId); + setPendingCertificateData(json); } - dispatch(updateCertificate(json)); } catch (e) { if (e instanceof Error) { dispatch(verifyingCertificateCompleted([e.message])); @@ -74,6 +87,17 @@ export const CertificateDropZone: FunctionComponent = [currentChainId, dispatch, switchNetwork] ); + const [targetChainId, setTargetChainId] = useState(null); + const [pendingCertificateData, setPendingCertificateData] = useState(null); + // Effect to dispatch once currentChainId matches targetChainId + useEffect(() => { + if (targetChainId && currentChainId === targetChainId && pendingCertificateData) { + dispatch(updateCertificate(pendingCertificateData)); + setTargetChainId(null); + setPendingCertificateData(null); + } + }, [currentChainId, targetChainId, pendingCertificateData, dispatch]); + const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({ onDrop, multiple: false, @@ -88,8 +112,17 @@ export const CertificateDropZone: FunctionComponent = isVerificationPending, isVerificationError, isActionError, + isTokenRegistryV5, }); - }, [isDragReject, isDragActive, isDragAccept, isVerificationPending, isVerificationError, isActionError]); + }, [ + isDragReject, + isDragActive, + isDragAccept, + isVerificationPending, + isVerificationError, + isActionError, + isTokenRegistryV5, + ]); return (
@@ -101,6 +134,8 @@ export const CertificateDropZone: FunctionComponent = switch (true) { case isVerificationPending: return ; + case isTokenRegistryV5: + return ; case isVerificationError: return ; case isActionError: diff --git a/src/components/CertificateViewer.tsx b/src/components/CertificateViewer.tsx index eb57a22dd..52d445cb6 100644 --- a/src/components/CertificateViewer.tsx +++ b/src/components/CertificateViewer.tsx @@ -150,7 +150,13 @@ export const CertificateViewer: FunctionComponent = ({ i const renderedCertificateViewer = ( <>
- {!isTransferableDocument && } + {!isTransferableDocument && ( +
+
+ +
+
+ )} {renderBanner(isSampleDocument, isMagicDemo)} {isTransferableDocument && ( diff --git a/src/components/DocumentDropzone/Views/ViewTokenRegistryMismatch.tsx b/src/components/DocumentDropzone/Views/ViewTokenRegistryMismatch.tsx new file mode 100644 index 000000000..0868f4520 --- /dev/null +++ b/src/components/DocumentDropzone/Views/ViewTokenRegistryMismatch.tsx @@ -0,0 +1,34 @@ +import React, { FunctionComponent } from "react"; +import { Button, ButtonSize } from "@tradetrust-tt/tradetrust-ui-components"; +import { URLS } from "../../../constants"; +import { TooltipIcon } from "../../UI/SvgIcon"; +import { Info } from "react-feather"; +export const ViewTokenRegistryMismatch: FunctionComponent = () => { + return ( +
+ Document Dropzone TradeTrust +

Document cannot be read.

+

Please check that you have a valid .tt or .json file

+

Drop your TradeTrust Document to view its contents

+

Or

+ +
+
+ + + + + {" "} + Please visit {URLS.REF_V5TR} to verify new version of transferable + documents. + +
+
+ ); +}; diff --git a/src/components/DocumentStatus/DocumentStatus.tsx b/src/components/DocumentStatus/DocumentStatus.tsx index c15b68f8e..b72a70957 100644 --- a/src/components/DocumentStatus/DocumentStatus.tsx +++ b/src/components/DocumentStatus/DocumentStatus.tsx @@ -33,7 +33,7 @@ const getV2FormattedDomainNames = (verificationStatus: VerificationFragment[]) = }; const identityProofFragment = utils .getIssuerIdentityFragments(verificationStatus) - .find((fragment) => utils.isValidFragment(fragment)) as VerificationFragmentWithData; + .find((fragment) => utils.isValidFragment(fragment)) as unknown as VerificationFragmentWithData; const dataFragment = identityProofFragment?.data; const fragmentValidity = @@ -72,10 +72,10 @@ export const IssuedBy: FunctionComponent = ({ title = "Issued by" // formattedDomainNames = getV4IdentityVerificationText(document); // } return ( -

+

{title} {formattedDomainNames} -

+ ); }; @@ -93,18 +93,16 @@ export const DocumentStatus: FunctionComponent = ({ isMagic if (!document || !verificationStatus) return null; return ( -
-
-
-
- -
- +
+
+
+
+
); diff --git a/src/components/PopupMessage.tsx b/src/components/PopupMessage.tsx new file mode 100644 index 000000000..65eb1fc53 --- /dev/null +++ b/src/components/PopupMessage.tsx @@ -0,0 +1,73 @@ +import { Button, ButtonSize, IconSuccess } from "@tradetrust-tt/tradetrust-ui-components"; +import React, { useState, useEffect } from "react"; +import { Checkbox } from "./UI/Checkbox"; +import { URLS } from "../constants"; +const PopupMessage = () => { + const [showPopup, setShowPopup] = useState(false); + const [doNotShowAgain, setDoNotShowAgain] = useState(false); + // Check if the user has already seen the popup or opted to not show it again + useEffect(() => { + const hasSeenPopup = localStorage.getItem("hasSeenPopup"); + if (!hasSeenPopup) { + setShowPopup(true); + } + }, []); + const handleClosePopup = () => { + if (doNotShowAgain) { + localStorage.setItem("hasSeenPopup", "true"); + } + setShowPopup(false); + }; + const handleChangeCheckbox = (event: React.ChangeEvent) => { + setDoNotShowAgain(event.target.checked); + }; + if (!showPopup) return null; + return ( +
+
+ {/* Text content */} +
+

+ + Latest Document Updates +

+

Here's why you should update and issue your files in the latest iteration:

+
    +
  • Latest iteration enabled new functions that can help you better align with the IG P&I requirement:
  • +
      +
    • + Reject function - Received a wrongfully transferred document, you may now reject it to where it came + from! +
    • +
    • Remark column - Any actions you take for your transferable document can now include a remark!
    • +
    +
+
+ {/* Do not show again checkbox */} +
+ + Do not show this again + +
+ {/* Buttons - Dismiss and Learn More */} +
+ {/* Dismiss Button */} + + {/* Learn More Button */} + + + +
+
+
+ ); +}; +export default PopupMessage; diff --git a/src/constants/index.ts b/src/constants/index.ts index a3df2e91e..99e3ad65b 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,6 +1,7 @@ export enum URLS { INFO = "https://tradetrust.io", REF = "https://ref.tradetrust.io", + REF_V5TR = "https://v5-token-registry.tradetrust.io", CREATOR = "https://creator.tradetrust.io/", GITHUB = "https://github.com/TradeTrust/tradetrust-website", DOCS = "https://docs.tradetrust.io/", diff --git a/src/reducers/certificate.js b/src/reducers/certificate.js index 4bc9bbe8a..ed381cc5b 100644 --- a/src/reducers/certificate.js +++ b/src/reducers/certificate.js @@ -11,6 +11,9 @@ export const initialState = { raw: null, rawModified: null, + providerOrSigner: null, + tokenRegistryV5: false, + verificationPending: false, verificationStatus: null, verificationError: null, @@ -25,6 +28,8 @@ export const types = { UPDATE_CERTIFICATE: "UPDATE_CERTIFICATE", + DETECTING_TR_V5_CERTIFICATE: "DETECTING_TR_V5_CERTIFICATE", + VERIFYING_CERTIFICATE: "VERIFYING_CERTIFICATE", VERIFYING_CERTIFICATE_COMPLETED: "VERIFYING_CERTIFICATE_COMPLETED", @@ -54,6 +59,12 @@ export default function reducer(state = initialState, action) { raw: action.payload, rawModified: action.payload, }; + case types.DETECTING_TR_V5_CERTIFICATE: + return { + ...state, + verificationPending: false, + tokenRegistryV5: true, + }; case types.VERIFYING_CERTIFICATE: return { ...state, @@ -117,6 +128,11 @@ export function updateCertificate(payload) { }; } +export const detectingTRV5Certificate = (payload) => ({ + type: types.DETECTING_TR_V5_CERTIFICATE, + payload, +}); + export function applyPrivacyFilter(payload) { return { type: types.CERTIFICATE_OBFUSCATE_UPDATE, diff --git a/src/sagas/certificate.ts b/src/sagas/certificate.ts index 79ff4b408..a5556fb16 100644 --- a/src/sagas/certificate.ts +++ b/src/sagas/certificate.ts @@ -5,6 +5,7 @@ import { verifyingCertificateCompleted, verifyingCertificateFailure, getCertificate, + detectingTRV5Certificate, } from "../reducers/certificate"; import { processQrCode } from "../services/qrProcessor"; import { verifyDocument } from "../services/verify"; @@ -13,6 +14,8 @@ import { decryptString } from "@govtechsg/oa-encryption"; import { history } from "../history"; import { CONSTANTS } from "@tradetrust-tt/tradetrust-utils"; import { ActionPayload } from "./../types"; +import { utils } from "@tradetrust-tt/tradetrust"; +import { getTokenRegistryAddress, isTokenRegistryV5 } from "../utils/shared"; const { trace } = getLogger("saga:certificate"); @@ -25,6 +28,19 @@ export function* verifyCertificate(): any { }); const certificate = yield select(getCertificate); + const isTransferableAsset = utils.isTransferableAsset(certificate); + if (isTransferableAsset) { + const registryAddress = getTokenRegistryAddress(certificate); + const tokenId = `0x${utils.getAssetId(certificate)}`; + if (registryAddress && tokenId) { + const tokenRegistryV5 = yield isTokenRegistryV5(registryAddress, tokenId); + if (tokenRegistryV5) { + yield put(detectingTRV5Certificate(TYPES.INVALID)); + return; + } + } + } + const verificationStatus = yield verifyDocument(certificate); trace(`Verification Status: ${JSON.stringify(verificationStatus)}`); diff --git a/src/test/helper.js b/src/test/helper.js index cad5e3840..caa812a92 100644 --- a/src/test/helper.js +++ b/src/test/helper.js @@ -14,6 +14,12 @@ export const validateTextContent = async (testcafe, component, texts) => ); export const navigateToVerify = async () => { + const button = Selector("button").withText("Dismiss"); + if (await button.exists) { + await t.click(button); + } else { + console.log("Button does not exist"); + } await t.click(VerifyPage); }; diff --git a/src/utils/shared.ts b/src/utils/shared.ts index 2ff8d456d..a7dd67a3a 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.ts @@ -1,6 +1,10 @@ import { getData, utils, v2, v3, OpenAttestationDocument, WrappedDocument } from "@tradetrust-tt/tradetrust"; import { getSupportedChainIds } from "../common/utils/chain-utils"; -import { AvailableBlockChains, ChainId } from "../constants/chain-info"; +import { AvailableBlockChains, BurnAddress, ChainId } from "../constants/chain-info"; +import { TitleEscrow__factory, TradeTrustToken__factory } from "@tradetrust-tt/token-registry/contracts"; +import { TitleEscrowInterface } from "../common/contexts/TokenInformationContext"; +import { getCurrentProvider } from "../common/contexts/provider"; +import { ethers } from "ethers"; export type WrappedOrSignedOpenAttestationDocument = WrappedDocument; // note that the return type for getting attachments will normalise the structure into v2.Attachment @@ -85,3 +89,34 @@ export const getChainId = (rawDocument: WrappedOrSignedOpenAttestationDocument): return undefined; } }; + +export async function isTokenRegistryV5(registryAddress: string, tokenId: string): Promise { + try { + const provider = getCurrentProvider(); + if (!provider) { + return false; + } + const tokenRegistry = TradeTrustToken__factory.connect(registryAddress, provider); + const titleEscrowOwner = await tokenRegistry.ownerOf(tokenId); + const inactiveEscrow = [BurnAddress, registryAddress] + .map((s) => s.toLowerCase()) + .includes(titleEscrowOwner.toLowerCase()); + let titleEscrowAddress = titleEscrowOwner; + if (inactiveEscrow) { + const titleEscrowFactoryAddress = await tokenRegistry.titleEscrowFactory(); + const tokenRegistryAddress = await tokenRegistry.address; + // Resolve V5 TitleEscrowFactory__factory contract function rename from getAddress to getEscrowAddress + const titleEscrowFactory = new ethers.Contract( + titleEscrowFactoryAddress, + `[{"inputs":[{"internalType":"address","name":"tokenRegistry","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getEscrowAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`, + provider + ); + titleEscrowAddress = await titleEscrowFactory.getEscrowAddress(tokenRegistryAddress, tokenId); + } + const titleEscrow = TitleEscrow__factory.connect(titleEscrowAddress, provider); + const isTitleEscrowV5 = await titleEscrow.supportsInterface(TitleEscrowInterface.V5); + return isTitleEscrowV5; + } catch (error) { + return false; + } +} diff --git a/tests/e2e/specs/1a-transfer-holder.spec.js b/tests/e2e/specs/1a-transfer-holder.spec.js index 609c7c6fa..5d57307f9 100644 --- a/tests/e2e/specs/1a-transfer-holder.spec.js +++ b/tests/e2e/specs/1a-transfer-holder.spec.js @@ -1,6 +1,10 @@ import { ACCOUNT_2 } from "../utils"; before(() => { + cy.window().then((window) => { + window.localStorage.setItem('hasSeenPopup', 'true'); + }); + cy.wait(1000); cy.importMetamaskAccount("0xc58c1ff75001afdca8cecb61b47f36964febe4188b8f7b26252286ecae5a8879"); cy.switchMetamaskAccount(1); // ensure switch to account 1 (owner) }); diff --git a/tests/e2e/specs/2a-surrender-reject.spec.js b/tests/e2e/specs/2a-surrender-reject.spec.js index 9e1ceaff8..9ea84f959 100644 --- a/tests/e2e/specs/2a-surrender-reject.spec.js +++ b/tests/e2e/specs/2a-surrender-reject.spec.js @@ -1,4 +1,7 @@ before(() => { + cy.window().then((window) => { + window.localStorage.setItem('hasSeenPopup', 'true'); + }); cy.switchMetamaskAccount(1); // ensure switch to account 1 (owner) }); diff --git a/tests/e2e/specs/2b-surrender-accept.spec.js b/tests/e2e/specs/2b-surrender-accept.spec.js index acc88a0d6..99e2a2302 100644 --- a/tests/e2e/specs/2b-surrender-accept.spec.js +++ b/tests/e2e/specs/2b-surrender-accept.spec.js @@ -1,4 +1,7 @@ before(() => { + cy.window().then((window) => { + window.localStorage.setItem('hasSeenPopup', 'true'); + }); cy.switchMetamaskAccount(1); // ensure switch to account 1 (owner) }); diff --git a/tests/e2e/specs/3a-endorse-transfer-owner-holder.spec.js b/tests/e2e/specs/3a-endorse-transfer-owner-holder.spec.js index a2826ce6e..536a29f71 100644 --- a/tests/e2e/specs/3a-endorse-transfer-owner-holder.spec.js +++ b/tests/e2e/specs/3a-endorse-transfer-owner-holder.spec.js @@ -1,6 +1,9 @@ import { ACCOUNT_3 } from "../utils"; before(() => { + cy.window().then((window) => { + window.localStorage.setItem('hasSeenPopup', 'true'); + }); cy.switchMetamaskAccount(1); // ensure switch to account 1 (owner) }); diff --git a/tests/e2e/specs/3b-endorse-nominate-owner.spec.js b/tests/e2e/specs/3b-endorse-nominate-owner.spec.js index 53f356ccd..88bdc1c02 100644 --- a/tests/e2e/specs/3b-endorse-nominate-owner.spec.js +++ b/tests/e2e/specs/3b-endorse-nominate-owner.spec.js @@ -1,6 +1,9 @@ import { ACCOUNT_1, ACCOUNT_3 } from "../utils"; before(() => { + cy.window().then((window) => { + window.localStorage.setItem('hasSeenPopup', 'true'); + }); cy.switchMetamaskAccount(1); // ensure switch to account 1 (owner) }); diff --git a/tests/e2e/specs/3c-endorse-nominate-owner-accept.spec.js b/tests/e2e/specs/3c-endorse-nominate-owner-accept.spec.js index 758f64d56..93450e816 100644 --- a/tests/e2e/specs/3c-endorse-nominate-owner-accept.spec.js +++ b/tests/e2e/specs/3c-endorse-nominate-owner-accept.spec.js @@ -1,6 +1,9 @@ import { ACCOUNT_2, ACCOUNT_3 } from "../utils"; before(() => { + cy.window().then((window) => { + window.localStorage.setItem('hasSeenPopup', 'true'); + }); cy.switchMetamaskAccount(2); // switch to account 2 (holder) });