Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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