From 9b329d7e7cc9ec7330536ee661cb0115f074cd64 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 1 Jul 2024 16:11:28 +0530 Subject: [PATCH 1/2] refactor(web): update-evidence-uploads-and-fetch-flow --- .../ActionButton/Modal/ChallengeItemModal.tsx | 70 ++++++++----------- .../ActionButton/Modal/RemoveModal.tsx | 68 ++++++++---------- .../Party/JustificationDetails.tsx | 11 ++- .../Party/JustificationModal.tsx | 29 +++----- web/src/hooks/queries/useEvidences.ts | 3 + 5 files changed, 75 insertions(+), 106 deletions(-) diff --git a/web/src/components/ActionButton/Modal/ChallengeItemModal.tsx b/web/src/components/ActionButton/Modal/ChallengeItemModal.tsx index 9a9b794..8caef8e 100644 --- a/web/src/components/ActionButton/Modal/ChallengeItemModal.tsx +++ b/web/src/components/ActionButton/Modal/ChallengeItemModal.tsx @@ -5,18 +5,19 @@ import Buttons from "./Buttons"; import DepositRequired from "./DepositRequired"; import Info from "./Info"; import { - prepareWriteCurateV2, + useCurateV2ChallengeRequest, useCurateV2GetArbitratorExtraData, useCurateV2RemovalChallengeBaseDeposit, useCurateV2SubmissionChallengeBaseDeposit, + usePrepareCurateV2ChallengeRequest, } from "hooks/contracts/generated"; import { useArbitrationCost } from "hooks/useArbitrationCostFromKlerosCore"; -import { useAccount, useBalance, usePublicClient, useWalletClient } from "wagmi"; +import { useAccount, useBalance, usePublicClient } from "wagmi"; import { wrapWithToast } from "utils/wrapWithToast"; import { IBaseModal } from "."; import EvidenceUpload, { Evidence } from "./EvidenceUpload"; -import { uploadFileToIPFS } from "utils/uploadFileToIPFS"; import Modal from "components/Modal"; +import { isUndefined } from "src/utils"; const ReStyledModal = styled(Modal)` gap: 32px; @@ -46,7 +47,6 @@ const ChallengeItemModal: React.FC = ({ }) => { const { address } = useAccount(); const publicClient = usePublicClient(); - const { data: walletClient } = useWalletClient(); const [isChallengingItem, setIsChallengingItem] = useState(false); const [isEvidenceUploading, setIsEvidenceUploading] = useState(false); @@ -81,6 +81,22 @@ const ChallengeItemModal: React.FC = ({ const isEvidenceValid = useMemo(() => evidence?.name !== "" && evidence?.description !== "", [evidence]); + const isDisabled = useMemo(() => { + if (!userBalance || !depositRequired || isEvidenceUploading || !isEvidenceValid) return true; + return userBalance?.value < depositRequired; + }, [depositRequired, userBalance, isEvidenceUploading, isEvidenceValid]); + + const { config } = usePrepareCurateV2ChallengeRequest({ + enabled: !isUndefined(itemId) && !isUndefined(evidence) && !isDisabled, + //@ts-ignore + address: registryAddress, + functionName: "challengeRequest", + args: [itemId as `0x${string}`, JSON.stringify(evidence)], + value: depositRequired, + }); + + const { writeAsync: challengeRequest } = useCurateV2ChallengeRequest(config); + const isLoading = useMemo( () => isBalanceLoading || @@ -101,11 +117,6 @@ const ChallengeItemModal: React.FC = ({ ] ); - const isDisabled = useMemo(() => { - if (!userBalance || !depositRequired || isEvidenceUploading || !isEvidenceValid) return true; - return userBalance?.value < depositRequired; - }, [depositRequired, userBalance, isEvidenceUploading, isEvidenceValid]); - return (
@@ -117,37 +128,16 @@ const ChallengeItemModal: React.FC = ({ toggleModal={toggleModal} isDisabled={isDisabled || isChallengingItem} isLoading={isLoading} - callback={async () => { - setIsChallengingItem(true); - - const evidenceFile = new File([JSON.stringify(evidence)], "evidence.json", { - type: "application/json", - }); - - uploadFileToIPFS(evidenceFile) - .then(async (res) => { - if (res.status === 200 && walletClient) { - const response = await res.json(); - const fileURI = response["cids"][0]; - - const { request } = await prepareWriteCurateV2({ - //@ts-ignore - address: registryAddress, - functionName: "challengeRequest", - args: [itemId as `0x${string}`, fileURI], - value: depositRequired, - }); - - wrapWithToast(async () => await walletClient.writeContract(request), publicClient) - .then((res) => { - console.log({ res }); - refetch(); - toggleModal(); - }) - .finally(() => setIsChallengingItem(false)); - } - }) - .catch((err) => console.log(err)); + callback={() => { + if (challengeRequest) { + setIsChallengingItem(true); + wrapWithToast(async () => await challengeRequest().then((response) => response.hash), publicClient) + .then(() => { + refetch(); + toggleModal(); + }) + .finally(() => setIsChallengingItem(false)); + } }} /> diff --git a/web/src/components/ActionButton/Modal/RemoveModal.tsx b/web/src/components/ActionButton/Modal/RemoveModal.tsx index 42a1407..c06cffd 100644 --- a/web/src/components/ActionButton/Modal/RemoveModal.tsx +++ b/web/src/components/ActionButton/Modal/RemoveModal.tsx @@ -5,17 +5,19 @@ import Buttons from "./Buttons"; import DepositRequired from "./DepositRequired"; import Info from "./Info"; import { IBaseModal } from "."; -import { useAccount, useBalance, usePublicClient, useWalletClient } from "wagmi"; +import { useAccount, useBalance, usePublicClient } from "wagmi"; import { - prepareWriteCurateV2, useCurateV2GetArbitratorExtraData, useCurateV2RemovalBaseDeposit, + useCurateV2RemoveItem, + usePrepareCurateV2RemoveItem, } from "hooks/contracts/generated"; import { useArbitrationCost } from "hooks/useArbitrationCostFromKlerosCore"; import { wrapWithToast } from "utils/wrapWithToast"; import EvidenceUpload, { Evidence } from "./EvidenceUpload"; import { uploadFileToIPFS } from "utils/uploadFileToIPFS"; import Modal from "components/Modal"; +import { isUndefined } from "src/utils"; const ReStyledModal = styled(Modal)` gap: 32px; @@ -32,11 +34,10 @@ const alertMessage = (isItem: boolean) => const RemoveModal: React.FC = ({ toggleModal, isItem, registryAddress, itemId, refetch }) => { const { address } = useAccount(); const publicClient = usePublicClient(); - const { data: walletClient } = useWalletClient(); - const [isRemovingItem, setIsRemovingItem] = useState(false); const [isEvidenceUploading, setIsEvidenceUploading] = useState(false); const [evidence, setEvidence] = useState(); + const [isRemovingItem, setIsRemovingItem] = useState(false); const { data: userBalance, isLoading: isBalanceLoading } = useBalance({ address }); @@ -59,6 +60,21 @@ const RemoveModal: React.FC = ({ toggleModal, isItem, registryAddr const isEvidenceValid = useMemo(() => evidence?.name !== "" && evidence?.description !== "", [evidence]); + const isDisabled = useMemo(() => { + if (!userBalance || !depositRequired || isEvidenceUploading || !isEvidenceValid) return true; + return userBalance?.value < depositRequired; + }, [depositRequired, userBalance, isEvidenceUploading, isEvidenceValid]); + + const { config } = usePrepareCurateV2RemoveItem({ + enabled: !isDisabled || !isUndefined(evidence), + //@ts-ignore + address: registryAddress, + functionName: "removeItem", + args: [itemId as `0x${string}`, JSON.stringify(evidence)], + value: depositRequired, + }); + + const { writeAsync: removeItem } = useCurateV2RemoveItem(config); const isLoading = useMemo( () => isBalanceLoading || @@ -77,11 +93,6 @@ const RemoveModal: React.FC = ({ toggleModal, isItem, registryAddr ] ); - const isDisabled = useMemo(() => { - if (!userBalance || !depositRequired || isEvidenceUploading || !isEvidenceValid) return true; - return userBalance?.value < depositRequired; - }, [depositRequired, userBalance, isEvidenceUploading, isEvidenceValid]); - return (
@@ -94,36 +105,15 @@ const RemoveModal: React.FC = ({ toggleModal, isItem, registryAddr isDisabled={isDisabled || isRemovingItem} isLoading={isLoading} callback={() => { - setIsRemovingItem(true); - - const evidenceFile = new File([JSON.stringify(evidence)], "evidence.json", { - type: "application/json", - }); - - uploadFileToIPFS(evidenceFile) - .then(async (res) => { - if (res.status === 200 && walletClient) { - const response = await res.json(); - const fileURI = response["cids"][0]; - - const { request } = await prepareWriteCurateV2({ - //@ts-ignore - address: registryAddress, - functionName: "removeItem", - args: [itemId as `0x${string}`, fileURI], - value: depositRequired, - }); - - wrapWithToast(async () => await walletClient.writeContract(request), publicClient) - .then((res) => { - console.log({ res }); - refetch(); - toggleModal(); - }) - .finally(() => setIsRemovingItem(false)); - } - }) - .catch((err) => console.log(err)); + if (removeItem) { + setIsRemovingItem(true); + wrapWithToast(async () => await removeItem().then((response) => response.hash), publicClient) + .then((res) => { + refetch(); + toggleModal(); + }) + .finally(() => setIsRemovingItem(false)); + } }} /> diff --git a/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx b/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx index 17012be..f4e56fa 100644 --- a/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx +++ b/web/src/components/HistoryDisplay/Party/JustificationDetails.tsx @@ -4,6 +4,7 @@ import styled from "styled-components"; import { getIpfsUrl } from "utils/getIpfsUrl"; import AttachmentIcon from "svgs/icons/attachment.svg"; import { customScrollbar } from "styles/customScrollbar"; +import { Evidence } from "src/graphql/graphql"; const Container = styled.div` width: 100%; @@ -32,18 +33,14 @@ const StyledA = styled.a` } `; -export type Justification = { - name: string; - description: string; - fileURI?: string; -}; +export type Justification = Pick; const JustificationDetails: React.FC<{ justification: Justification }> = ({ justification }) => { return ( - {justification.name} + {justification.name ?? "Unable to determine title"} - {justification.description} + {justification.description ?? "Unable to determine description"} {justification?.fileURI && ( diff --git a/web/src/components/HistoryDisplay/Party/JustificationModal.tsx b/web/src/components/HistoryDisplay/Party/JustificationModal.tsx index 0cddcd6..d8f1de6 100644 --- a/web/src/components/HistoryDisplay/Party/JustificationModal.tsx +++ b/web/src/components/HistoryDisplay/Party/JustificationModal.tsx @@ -9,8 +9,6 @@ import { mapFromSubgraphStatus } from "components/RegistryCard/StatusBanner"; import { EvidencesQuery, RequestDetailsFragment } from "src/graphql/graphql"; import { isUndefined } from "src/utils"; -import { getIpfsUrl } from "utils/getIpfsUrl"; -import fetchJsonIpfs from "utils/fetchJsonIpfs"; import { useEvidences } from "queries/useEvidences"; import Header from "./Header"; @@ -49,31 +47,22 @@ interface IJustificationModal { const JustificationModal: React.FC = ({ request, toggleModal, isRemoval }) => { const { data: evidenceData, isLoading: isLoadingEvidences } = useEvidences(request.externalDisputeID); const [justification, setJustification] = useState(); - const [isLoadingJustification, setIsLoadingJustification] = useState(false); useEffect(() => { if (isUndefined(evidenceData)) return; - setIsLoadingJustification(true); - const uri = getEvidenceUriForRequest(request, evidenceData.evidences, isRemoval); + const evidence = getEvidenceForRequest(request, evidenceData.evidences, isRemoval); - if (!uri) { - setIsLoadingJustification(false); - return; + if (evidence) { + setJustification(evidence); } - - fetchJsonIpfs(getIpfsUrl(uri)) - .then((res) => { - setJustification(res as Justification); - }) - .finally(() => setIsLoadingJustification(false)); }, [evidenceData, isRemoval, request]); return (
Justification - {isLoadingEvidences || isLoadingJustification ? ( + {isLoadingEvidences ? ( ) : justification ? ( @@ -100,16 +89,16 @@ const JustificationModal: React.FC = ({ request, toggleModa * @need this is needed since the removal request might not have the evidence, same for challenge request. * to get the correct evidence for the request, we match the timestamp of the request and evidence, * if both are same , it means the evidence was created in the same block as that request and belongs to the request - * @returns the evidence uri for the request if it exists, otherwise null + * @returns the evidence for the request if it exists, otherwise null */ -const getEvidenceUriForRequest = ( +const getEvidenceForRequest = ( request: RequestDetailsFragment, evidences: EvidencesQuery["evidences"], isRemoval: boolean ) => { if (isRemoval) { if (evidences.length > 0 && evidences[0].timestamp === request.submissionTime) { - return evidences[0].evidence; + return evidences[0]; } else { return null; } @@ -118,8 +107,8 @@ const getEvidenceUriForRequest = ( // in case of challenge either the first or the second one can be the challenge evidence, // in case of registration challenge, the 1st one is the challenge evidence, // or if the removal request did not have any justification, the 1st one could be the challenge justification - if (evidences.length > 0 && evidences[0].timestamp === request.challengeTime) return evidences[0].evidence; - if (evidences.length > 1 && evidences[1].timestamp === request.challengeTime) return evidences[1].evidence; + if (evidences.length > 0 && evidences[0].timestamp === request.challengeTime) return evidences[0]; + if (evidences.length > 1 && evidences[1].timestamp === request.challengeTime) return evidences[1]; return null; }; diff --git a/web/src/hooks/queries/useEvidences.ts b/web/src/hooks/queries/useEvidences.ts index 89e01ba..2c959b4 100644 --- a/web/src/hooks/queries/useEvidences.ts +++ b/web/src/hooks/queries/useEvidences.ts @@ -10,6 +10,9 @@ const evidencesQuery = graphql(` evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: asc, first: 2) { evidence timestamp + name + description + fileURI } } `); From 66eb1a54ffeae0b72ab3582e8911aa053dc6bf2f Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 1 Jul 2024 16:17:20 +0530 Subject: [PATCH 2/2] chore(contracts): update-natspec --- contracts/src/CurateV2.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/CurateV2.sol b/contracts/src/CurateV2.sol index 8b93efa..705ae9a 100644 --- a/contracts/src/CurateV2.sol +++ b/contracts/src/CurateV2.sol @@ -392,7 +392,7 @@ contract CurateV2 is IArbitrableV2 { /// @dev Submit a request to remove an item from the list. Accepts enough ETH to cover the deposit, reimburses the rest. /// @param _itemID The ID of the item to remove. - /// @param _evidence A link to evidence using its URI. + /// @param _evidence Stringified evidence object, example: '{"name" : "Justification", "description" : "Description", "fileURI" : "/ipfs/QmWQV5ZFFhEJiW8Lm7ay2zLxC2XS4wx1b2W7FfdrLMyQQc"}'. function removeItem(bytes32 _itemID, string calldata _evidence) external payable { Item storage item = items[_itemID]; @@ -430,7 +430,7 @@ contract CurateV2 is IArbitrableV2 { /// @dev Challenges the request of the item. Accepts enough ETH to cover the deposit, reimburses the rest. /// @param _itemID The ID of the item which request to challenge. - /// @param _evidence A link to evidence using its URI. + /// @param _evidence Stringified evidence object, example: '{"name" : "Justification", "description" : "Description", "fileURI" : "/ipfs/QmWQV5ZFFhEJiW8Lm7ay2zLxC2XS4wx1b2W7FfdrLMyQQc"}'. function challengeRequest(bytes32 _itemID, string calldata _evidence) external payable { Item storage item = items[_itemID]; require(item.status > Status.Registered, "The item must have a pending request.");