Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contracts/src/CurateV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down Expand Up @@ -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.");
Expand Down
70 changes: 30 additions & 40 deletions web/src/components/ActionButton/Modal/ChallengeItemModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,7 +47,6 @@ const ChallengeItemModal: React.FC<IChallengeItemModal> = ({
}) => {
const { address } = useAccount();
const publicClient = usePublicClient();
const { data: walletClient } = useWalletClient();

const [isChallengingItem, setIsChallengingItem] = useState(false);
const [isEvidenceUploading, setIsEvidenceUploading] = useState(false);
Expand Down Expand Up @@ -81,6 +81,22 @@ const ChallengeItemModal: React.FC<IChallengeItemModal> = ({

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 ||
Expand All @@ -101,11 +117,6 @@ const ChallengeItemModal: React.FC<IChallengeItemModal> = ({
]
);

const isDisabled = useMemo(() => {
if (!userBalance || !depositRequired || isEvidenceUploading || !isEvidenceValid) return true;
return userBalance?.value < depositRequired;
}, [depositRequired, userBalance, isEvidenceUploading, isEvidenceValid]);

return (
<ReStyledModal {...{ toggleModal }}>
<Header text={`Challenge ${isItem ? "Item" : "List"}`} />
Expand All @@ -117,37 +128,16 @@ const ChallengeItemModal: React.FC<IChallengeItemModal> = ({
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));
}
}}
/>
</ReStyledModal>
Expand Down
68 changes: 29 additions & 39 deletions web/src/components/ActionButton/Modal/RemoveModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,11 +34,10 @@ const alertMessage = (isItem: boolean) =>
const RemoveModal: React.FC<IRemoveModal> = ({ 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<Evidence>();
const [isRemovingItem, setIsRemovingItem] = useState(false);

const { data: userBalance, isLoading: isBalanceLoading } = useBalance({ address });

Expand All @@ -59,6 +60,21 @@ const RemoveModal: React.FC<IRemoveModal> = ({ 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 ||
Expand All @@ -77,11 +93,6 @@ const RemoveModal: React.FC<IRemoveModal> = ({ toggleModal, isItem, registryAddr
]
);

const isDisabled = useMemo(() => {
if (!userBalance || !depositRequired || isEvidenceUploading || !isEvidenceValid) return true;
return userBalance?.value < depositRequired;
}, [depositRequired, userBalance, isEvidenceUploading, isEvidenceValid]);

return (
<ReStyledModal {...{ toggleModal }}>
<Header text={`Remove ${isItem ? "Item" : "List"}`} />
Expand All @@ -94,36 +105,15 @@ const RemoveModal: React.FC<IRemoveModal> = ({ 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));
}
}}
/>
</ReStyledModal>
Expand Down
11 changes: 4 additions & 7 deletions web/src/components/HistoryDisplay/Party/JustificationDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
Expand Down Expand Up @@ -32,18 +33,14 @@ const StyledA = styled.a`
}
`;

export type Justification = {
name: string;
description: string;
fileURI?: string;
};
export type Justification = Pick<Evidence, "name" | "description" | "evidence" | "fileURI">;

const JustificationDetails: React.FC<{ justification: Justification }> = ({ justification }) => {
return (
<Container>
<JustificationTitle>{justification.name}</JustificationTitle>
<JustificationTitle>{justification.name ?? "Unable to determine title"}</JustificationTitle>
<DescriptionContainer>
<ReactMarkdown>{justification.description}</ReactMarkdown>
<ReactMarkdown>{justification.description ?? "Unable to determine description"}</ReactMarkdown>
</DescriptionContainer>
{justification?.fileURI && (
<StyledA href={getIpfsUrl(justification.fileURI)}>
Expand Down
29 changes: 9 additions & 20 deletions web/src/components/HistoryDisplay/Party/JustificationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -49,31 +47,22 @@ interface IJustificationModal {
const JustificationModal: React.FC<IJustificationModal> = ({ request, toggleModal, isRemoval }) => {
const { data: evidenceData, isLoading: isLoadingEvidences } = useEvidences(request.externalDisputeID);
const [justification, setJustification] = useState<Justification>();
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 (
<StyledModal {...{ toggleModal }}>
<Header text={isRemoval ? "Removal Requested" : "Request Challenged"} />
<JustificationText>Justification</JustificationText>
{isLoadingEvidences || isLoadingJustification ? (
{isLoadingEvidences ? (
<SkeletonJustificationCard />
) : justification ? (
<JustificationDetails {...{ justification }} />
Expand All @@ -100,16 +89,16 @@ const JustificationModal: React.FC<IJustificationModal> = ({ 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;
}
Expand All @@ -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;
};
Expand Down
3 changes: 3 additions & 0 deletions web/src/hooks/queries/useEvidences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const evidencesQuery = graphql(`
evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: asc, first: 2) {
evidence
timestamp
name
description
fileURI
}
}
`);
Expand Down