Skip to content

feat: shutter support in dispute commiting & appeal #1994

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5ac64f5
feat: shutter support in dispute commiting
kemuru May 15, 2025
724a949
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 15, 2025
8a5b2f4
feat: shutter appeal support
kemuru May 15, 2025
6b240a6
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 15, 2025
05711fe
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 20, 2025
729777d
feat: postinstall script, use correct commit function
kemuru May 20, 2025
8bf3772
fix: vote hashing during commitment must follow DisputeKitShutter.has…
jaybuidl May 20, 2025
8c74d4a
feat: decryption delay is the remaining of the commit period
kemuru May 22, 2025
47049eb
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
jaybuidl May 22, 2025
bae8db4
chore: remove postinstall script, tell vite where to find it
kemuru May 23, 2025
e983fdd
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 26, 2025
2e5d2a7
feat: support for revealing shutter commit from the frontend
kemuru May 26, 2025
bf7a3c0
chore: remove console logs, few code smells
kemuru May 26, 2025
c8bf188
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 28, 2025
04605db
chore: extra 5 min decryptiondelay
kemuru May 29, 2025
b3817bd
fix: trycatch in case voting fails
kemuru May 29, 2025
f816ca3
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 29, 2025
4e2dea9
Merge branch 'feat/shutter-dispute-kit' into feat(web)/shutter-fronte…
kemuru May 29, 2025
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
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"@reown/appkit-adapter-wagmi": "^1.7.1",
"@sentry/react": "^7.120.0",
"@sentry/tracing": "^7.120.0",
"@shutter-network/shutter-sdk": "^0.0.1",
"@solana/wallet-adapter-react": "^0.15.36",
"@solana/web3.js": "^1.98.0",
"@tanstack/react-query": "^5.69.0",
Expand Down
3 changes: 3 additions & 0 deletions web/src/hooks/queries/useDisputeDetailsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const disputeDetailsQuery = graphql(`
currentRound {
id
nbVotes
disputeKit {
id
}
}
currentRoundIndex
isCrossChain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { wrapWithToast } from "utils/wrapWithToast";

import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";

import OptionsContainer from "./OptionsContainer";
import OptionsContainer from "../OptionsContainer";

const Container = styled.div`
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";

import InfoCard from "components/InfoCard";

import JustificationArea from "./JustificationArea";
import JustificationArea from "../JustificationArea";
import { Answer } from "@kleros/kleros-sdk";
import { EnsureChain } from "components/EnsureChain";

Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/Cases/CaseDetails/Voting/Classic/Vote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { wrapWithToast } from "utils/wrapWithToast";

import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";

import OptionsContainer from "./OptionsContainer";
import OptionsContainer from "../OptionsContainer";

const Container = styled.div`
width: 100%;
Expand Down
102 changes: 102 additions & 0 deletions web/src/pages/Cases/CaseDetails/Voting/Shutter/Commit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
import { useParams } from "react-router-dom";
import { useLocalStorage } from "react-use";
import { keccak256, encodePacked } from "viem";
import { useWalletClient, usePublicClient, useConfig } from "wagmi";

import { simulateDisputeKitShutterCastCommit } from "hooks/contracts/generated";
import useSigningAccount from "hooks/useSigningAccount";
import { isUndefined } from "utils/index";
import { wrapWithToast } from "utils/wrapWithToast";
import { encrypt } from "utils/shutter";
import OptionsContainer from "../OptionsContainer";

const Container = styled.div`
width: 100%;
height: auto;
`;

interface ICommit {
arbitrable: `0x${string}`;
voteIDs: string[];
setIsOpen: (val: boolean) => void;
refetch: () => void;
}

const SEPARATOR = "␟";

const Commit: React.FC<ICommit> = ({ arbitrable, voteIDs, setIsOpen, refetch }) => {
const { id } = useParams();
const parsedDisputeID = useMemo(() => BigInt(id ?? 0), [id]);
const parsedVoteIDs = useMemo(() => voteIDs.map((voteID) => BigInt(voteID)), [voteIDs]);
const { data: walletClient } = useWalletClient();
const publicClient = usePublicClient();
const wagmiConfig = useConfig();
const { signingAccount, generateSigningAccount } = useSigningAccount();
const [justification, setJustification] = useState("");
const saltKey = useMemo(() => `shutter-dispute-${id}-voteids-${voteIDs}`, [id, voteIDs]);
const [_, setSalt] = useLocalStorage(saltKey);

const handleCommit = useCallback(
async (choice: bigint) => {
const message = { message: saltKey };
const rawSalt = !isUndefined(signingAccount)
? await signingAccount.signMessage(message)
: await (async () => {
const account = await generateSigningAccount();
return await account?.signMessage(message);
})();
if (isUndefined(rawSalt)) return;

const salt = keccak256(rawSalt);
setSalt(JSON.stringify({ salt, choice: choice.toString(), justification }));

const encodedMessage = `${choice.toString()}${SEPARATOR}${salt}${SEPARATOR}${justification}`;
const { encryptedCommitment, identity } = await encrypt(encodedMessage);

const commitHash = keccak256(
encodePacked(["uint256", "uint256", "string"], [choice, BigInt(salt), justification])
);

const { request } = await simulateDisputeKitShutterCastCommit(wagmiConfig, {
args: [parsedDisputeID, parsedVoteIDs, commitHash, identity as `0x${string}`, encryptedCommitment],
});
if (walletClient && publicClient) {
await wrapWithToast(async () => await walletClient.writeContract(request), publicClient).then(({ status }) => {
setIsOpen(status);
});
}
refetch();
},
[
wagmiConfig,
justification,
saltKey,
setSalt,
parsedVoteIDs,
parsedDisputeID,
publicClient,
setIsOpen,
walletClient,
generateSigningAccount,
signingAccount,
refetch,
]
);

return id ? (
<Container>
<OptionsContainer
{...{
arbitrable,
justification,
setJustification,
handleSelection: handleCommit,
}}
/>
</Container>
) : null;
};

export default Commit;
5 changes: 5 additions & 0 deletions web/src/pages/Cases/CaseDetails/Voting/Shutter/Vote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from "react";

const Vote: React.FC = () => null;

export default Vote;
27 changes: 27 additions & 0 deletions web/src/pages/Cases/CaseDetails/Voting/Shutter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useMemo } from "react";
import { useParams } from "react-router-dom";
import { useAccount } from "wagmi";

import { useDrawQuery } from "hooks/queries/useDrawQuery";
import { useVotingContext } from "hooks/useVotingContext";
import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";

import ShutterCommit from "./Commit";

interface IShutter {
arbitrable: `0x${string}`;
setIsOpen: (val: boolean) => void;
}

const Shutter: React.FC<IShutter> = ({ arbitrable, setIsOpen }) => {
const { id } = useParams();
const { address } = useAccount();
const { data: disputeData } = useDisputeDetailsQuery(id);
const { data: drawData, refetch } = useDrawQuery(address?.toLowerCase(), id, disputeData?.dispute?.currentRound.id);
const { isCommitPeriod, commited } = useVotingContext();
const voteIDs = useMemo(() => drawData?.draws?.map((draw) => draw.voteIDNum) as string[], [drawData]);

return id && isCommitPeriod && !commited ? <ShutterCommit {...{ arbitrable, setIsOpen, voteIDs, refetch }} /> : null;
};

export default Shutter;
11 changes: 10 additions & 1 deletion web/src/pages/Cases/CaseDetails/Voting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import InfoCard from "components/InfoCard";
import Popup, { PopupType } from "components/Popup";

import Classic from "./Classic";
import Shutter from "./Shutter";
import VotingHistory from "./VotingHistory";

import { getDisputeKitName } from "consts/index";

const Container = styled.div`
padding: 20px 16px 16px;

Expand Down Expand Up @@ -66,6 +69,11 @@ const Voting: React.FC<IVoting> = ({ arbitrable, currentPeriodIndex }) => {
const timesPerPeriod = disputeData?.dispute?.court?.timesPerPeriod;
const finalDate = useFinalDate(lastPeriodChange, currentPeriodIndex, timesPerPeriod);

const disputeKitId = disputeData?.dispute?.currentRound?.disputeKit?.id;
const disputeKitName = disputeKitId ? getDisputeKitName(Number(disputeKitId)) : undefined;
const isClassicDisputeKit = disputeKitName?.toLowerCase().includes("classic") ?? false;
const isShutterDisputeKit = disputeKitName?.toLowerCase().includes("shutter") ?? false;

const isCommitOrVotePeriod = useMemo(
() => [Periods.vote, Periods.commit].includes(currentPeriodIndex),
[currentPeriodIndex]
Expand Down Expand Up @@ -107,7 +115,8 @@ const Voting: React.FC<IVoting> = ({ arbitrable, currentPeriodIndex }) => {
{userWasDrawn && isCommitOrVotePeriod && !voted ? (
<>
<VotingHistory {...{ arbitrable }} isQuestion={false} />
<Classic arbitrable={arbitrable ?? "0x0"} setIsOpen={setIsPopupOpen} />
{isClassicDisputeKit ? <Classic arbitrable={arbitrable ?? "0x0"} setIsOpen={setIsPopupOpen} /> : null}
{isShutterDisputeKit ? <Shutter arbitrable={arbitrable ?? "0x0"} setIsOpen={setIsPopupOpen} /> : null}
</>
) : (
<VotingHistory {...{ arbitrable }} isQuestion={true} />
Expand Down
Loading