Skip to content

Add commit reveal support #1312

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 29 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
66521f2
feat(wip/web): add commit reveal support
alcercu Aug 18, 2023
bd8b1fd
Merge branch 'dev' into feat(web)/commit-reveal
alcercu Oct 12, 2023
f350a41
refactor(web): abstract isDesktop
alcercu Oct 12, 2023
5f34eb2
Merge branch 'dev' into feat(web)/commit-reveal
alcercu Oct 30, 2023
59d73de
Merge branch 'dev' into feat(web)/commit-reveal
alcercu Oct 30, 2023
8d4931a
feat(web): add commit reveal
alcercu Oct 30, 2023
3832028
fix: privateKeyToAccount correct args
alcercu Dec 5, 2023
6e36ecb
refactor(web): improve refuseToArbitrateContainer style so it can be …
alcercu Dec 5, 2023
7728c88
Merge branch 'dev' into feat(web)/commit-reveal
alcercu Dec 5, 2023
84e9f0e
refactor(web): check if appealCost is undefined at isLastRound level
alcercu Dec 29, 2023
983690f
fix(web): hash signature to ensure private key is 256 bits long
alcercu Dec 29, 2023
efd5875
fix(web): hash signature output to make sure salt is 256 bits long
alcercu Dec 29, 2023
49b9763
feat(web): brute force vote choice from salt
alcercu Dec 29, 2023
b986270
fix(web): pass new arguments to Reveal component
alcercu Dec 29, 2023
e421d82
fix(web): logic for showing the commit and voting screen
alcercu Dec 29, 2023
2c9fca6
Merge branch 'dev' into feat(web)/commit-reveal
alcercu Dec 29, 2023
c9bcbaa
fix(web): make sure that the user didn't cancel salt generation
alcercu Dec 29, 2023
9f8043f
Merge branch 'dev' into feat(web)/commit-reveal
alcercu Jan 8, 2024
2b9960a
fix(web): correct salt typing for commit
alcercu Jan 8, 2024
a58d60e
fix(web): show nothing under Execution period
alcercu Jan 9, 2024
a530205
refactor(web): abstract InfoCard
alcercu Jan 9, 2024
d1fcd9a
fix(web): show correct popup when revealign
alcercu Jan 9, 2024
5d2d039
fix(web): don't show popup if the user cancels the tx
alcercu Jan 9, 2024
88aa8cf
feat(web): show info messages when not in reveal period or when faile…
alcercu Jan 9, 2024
251cf94
Merge branch 'dev' into feat(web)/commit-reveal
kemuru Jan 9, 2024
5d111f4
fix(web): update chainId on eip712 messages
alcercu Jan 10, 2024
663f14f
fix(web): refetch draw query after commiting a vote
alcercu Jan 10, 2024
e64ea3d
fix(web): times up indicator on execution phase
alcercu Jan 10, 2024
0165b1d
Merge branch 'dev' into feat(web)/commit-reveal
jaybuidl Jan 10, 2024
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
10 changes: 4 additions & 6 deletions web/src/components/CasesDisplay/CasesGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React, { useMemo } from "react";
import React from "react";
import styled from "styled-components";
import { useWindowSize } from "react-use";
import { useParams } from "react-router-dom";
import { SkeletonDisputeCard, SkeletonDisputeListItem } from "../StyledSkeleton";
import { StandardPagination } from "@kleros/ui-components-library";
import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle";
import { useIsList } from "context/IsListProvider";
import { isUndefined } from "utils/index";
import { decodeURIFilter } from "utils/uri";
import { DisputeDetailsFragment } from "queries/useCasesQuery";
import useIsDesktop from "hooks/useIsDesktop";
import DisputeCard from "components/DisputeCard";
import CasesListHeader from "./CasesListHeader";

Expand Down Expand Up @@ -46,12 +45,11 @@ const CasesGrid: React.FC<ICasesGrid> = ({ disputes, casesPerPage, totalPages, c
const decodedFilter = decodeURIFilter(filter ?? "all");
const { id: searchValue } = decodedFilter;
const { isList } = useIsList();
const { width } = useWindowSize();
const screenIsBig = useMemo(() => width > BREAKPOINT_LANDSCAPE, [width]);
const isDesktop = useIsDesktop();

return (
<>
{isList && screenIsBig ? (
{isList && isDesktop ? (
<ListContainer>
<CasesListHeader />
{isUndefined(disputes)
Expand Down
10 changes: 4 additions & 6 deletions web/src/components/CasesDisplay/Filters.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import React from "react";
import styled, { useTheme } from "styled-components";
import { useNavigate, useParams } from "react-router-dom";
import { useWindowSize } from "react-use";
import { DropdownSelect } from "@kleros/ui-components-library";
import { useIsList } from "context/IsListProvider";
import useIsDesktop from "hooks/useIsDesktop";
import ListIcon from "svgs/icons/list.svg";
import GridIcon from "svgs/icons/grid.svg";
import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle";
import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri";

const Container = styled.div`
Expand Down Expand Up @@ -59,9 +58,8 @@ const Filters: React.FC = () => {
navigate(`${location}/1/${value}/${encodedFilter}`);
};

const { width } = useWindowSize();
const { isList, setIsList } = useIsList();
const screenIsBig = width > BREAKPOINT_LANDSCAPE;
const isDesktop = useIsDesktop();

return (
<Container>
Expand All @@ -87,14 +85,14 @@ const Filters: React.FC = () => {
defaultValue={order}
callback={handleOrderChange}
/>
{screenIsBig ? (
{isDesktop ? (
<IconsContainer>
{isList ? (
<StyledGridIcon onClick={() => setIsList(false)} />
) : (
<StyledListIcon
onClick={() => {
if (screenIsBig) {
if (isDesktop) {
setIsList(true);
}
}}
Expand Down
8 changes: 3 additions & 5 deletions web/src/components/HeroImage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import React from "react";
import { useTheme } from "styled-components";
import { useWindowSize } from "react-use";
import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle";
import HeroLightMobile from "tsx:svgs/hero/hero-lightmode-mobile.svg";
import HeroDarkMobile from "tsx:svgs/hero/hero-darkmode-mobile.svg";
import HeroLightDesktop from "tsx:svgs/hero/hero-lightmode-desktop.svg";
import HeroDarkDesktop from "tsx:svgs/hero/hero-darkmode-desktop.svg";
import useIsDesktop from "hooks/useIsDesktop";

const HeroImage = () => {
const { width } = useWindowSize();
const theme = useTheme();
const themeIsLight = theme.name === "light";
const screenIsBig = width > BREAKPOINT_LANDSCAPE;
return <div>{screenIsBig ? <HeroDesktop {...{ themeIsLight }} /> : <HeroMobile {...{ themeIsLight }} />}</div>;
const isDesktop = useIsDesktop();
return <div>{isDesktop ? <HeroDesktop {...{ themeIsLight }} /> : <HeroMobile {...{ themeIsLight }} />}</div>;
};

const HeroDesktop: React.FC<{ themeIsLight: boolean }> = ({ themeIsLight }) => {
Expand Down
30 changes: 30 additions & 0 deletions web/src/components/InfoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import styled from "styled-components";
import { responsiveSize } from "styles/responsiveSize";
import InfoCircle from "tsx:svgs/icons/info-circle.svg";

const InfoContainer = styled.div`
display: grid;
grid-template-columns: 16px max-content;
gap: ${responsiveSize(4, 8, 300)};
align-items: center;
justify-items: center;
text-align: center;
color: ${({ theme }) => theme.secondaryText};
`;

interface IInfoCard {
msg: string;
className?: string;
}

const InfoCard: React.FC<IInfoCard> = ({ msg, className }) => {
return (
<InfoContainer {...{ className }}>
<InfoCircle />
{msg}
</InfoContainer>
);
};

export default InfoCard;
27 changes: 3 additions & 24 deletions web/src/components/Popup/ExtraInfo/VoteWithCommitExtraInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,16 @@
import React from "react";
import styled from "styled-components";
import InfoCircle from "tsx:svgs/icons/info-circle.svg";
import InfoCard from "components/InfoCard";
import { responsiveSize } from "styles/responsiveSize";

const Container = styled.div`
display: flex;
gap: 4px;
text-align: center;
const StyledInfoCard = styled(InfoCard)`
margin: ${responsiveSize(8, 24, 300)} ${responsiveSize(8, 32, 300)} 0;
font-size: 14px;
font-weight: 400;
line-height: 19px;
color: ${({ theme }) => theme.secondaryText};
`;

const InfoCircleContainer = styled.div`
display: flex;
margin-top: 2px;

svg {
min-width: 16px;
min-height: 16px;
}
`;

const VoteWithCommitExtraInfo: React.FC = () => {
return (
<Container>
<InfoCircleContainer>
<InfoCircle />
</InfoCircleContainer>
Subscribe to receive notifications to be reminded when the reveal time comes.
</Container>
);
return <StyledInfoCard msg="Subscribe to receive notifications to be reminded when the reveal time comes." />;
};
export default VoteWithCommitExtraInfo;
31 changes: 29 additions & 2 deletions web/src/consts/eip712-messages.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { arbitrumSepolia } from "viem/chains";

export default {
contactDetails: (address: `0x${string}`, nonce, telegram = "", email = "") =>
contactDetails: (
address: `0x${string}`,
nonce: string,
telegram = "",
email = "",
chainId: number = arbitrumSepolia.id
) =>
({
address: address.toLowerCase() as `0x${string}`,
domain: {
name: "Kleros v2",
version: "1",
chainId: 421_613,
chainId,
},
types: {
ContactDetails: [
Expand All @@ -21,4 +29,23 @@ export default {
nonce,
},
} as const),
signingAccount: (address: `0x${string}`, chainId: number = arbitrumSepolia.id) =>
({
account: address.toLowerCase() as `0x${string}`,
domain: {
name: "Kleros v2",
version: "1",
chainId,
},
types: {
SigningAccount: [{ name: "body", type: "string" }],
},
primaryType: "SigningAccount",
message: {
body:
"To keep your data safe and to use certain features of Kleros, we ask that you sign these message to " +
"create a secret key for your account. This key is unrelated from your main Ethereum account and will " +
"not be able to send any transactions.",
},
} as const),
};
6 changes: 6 additions & 0 deletions web/src/hooks/queries/useDrawQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ const drawQuery = graphql(`
query Draw($address: String, $disputeID: String, $roundID: String) {
draws(first: 1000, where: { dispute: $disputeID, juror: $address, round: $roundID }) {
voteIDNum
vote {
... on ClassicVote {
commit
commited
}
}
}
}
`);
Expand Down
10 changes: 10 additions & 0 deletions web/src/hooks/useIsDesktop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useMemo } from "react";
import { useWindowSize } from "react-use";
import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle";

const useIsDesktop = () => {
const { width } = useWindowSize();
return useMemo(() => width > BREAKPOINT_LANDSCAPE, [width]);
};

export default useIsDesktop;
27 changes: 27 additions & 0 deletions web/src/hooks/useSigningAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useLocalStorage } from "react-use";
import { Hex, WalletClient, keccak256 } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { useWalletClient } from "wagmi";
import messages from "consts/eip712-messages";
import { isUndefined } from "utils/index";

const useSigningAccount = () => {
const { data: wallet } = useWalletClient();
const address = wallet?.account.address;
const key = `signingAccount-${address}`;
const [signingKey, setSigningKey] = useLocalStorage<Hex>(key);
return {
signingAccount: !isUndefined(signingKey) ? privateKeyToAccount(signingKey) : undefined,
generateSigningAccount: () => (!isUndefined(wallet) ? generateSigningAccount(wallet, setSigningKey) : undefined),
};
};

const generateSigningAccount = async (wallet: WalletClient, setSigningKey: (signingKey: `0x${string}`) => void) => {
if (isUndefined(wallet.account)) return;
const signature = await wallet.signTypedData(messages.signingAccount(wallet.account.address));
const signingKey = keccak256(signature);
setSigningKey(signingKey);
return privateKeyToAccount(signingKey);
};

export default useSigningAccount;
56 changes: 18 additions & 38 deletions web/src/pages/Cases/CaseDetails/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from "react";
import React, { useMemo } from "react";
import styled, { css } from "styled-components";
import { landscapeStyle } from "styles/landscapeStyle";
import { Periods } from "consts/periods";
import { DisputeDetailsQuery } from "queries/useDisputeDetailsQuery";
import { Box, Steps } from "@kleros/ui-components-library";
import { StyledSkeleton } from "components/StyledSkeleton";
import { useCountdown } from "hooks/useCountdown";
import useIsDesktop from "hooks/useIsDesktop";
import { secondsToDayHourMinute } from "utils/date";
import { responsiveSize } from "styles/responsiveSize";

Expand Down Expand Up @@ -34,29 +35,11 @@ const StyledSteps = styled(Steps)`
margin: auto;
`;

const TitleMobile = styled.span`
${landscapeStyle(
() => css`
display: none;
`
)}
`;

const TitleDesktop = styled(TitleMobile)`
display: none;

${landscapeStyle(
() => css`
display: inline-block;
`
)}
`;

const Timeline: React.FC<{
dispute: DisputeDetailsQuery["dispute"];
currentPeriodIndex: number;
}> = ({ currentPeriodIndex, dispute }) => {
const currentItemIndex = currentPeriodToCurrentItem(currentPeriodIndex, dispute?.ruled);
const currentItemIndex = currentPeriodToCurrentItem(currentPeriodIndex, dispute?.court.hiddenVotes);
const items = useTimeline(dispute, currentItemIndex, currentItemIndex);
return (
<TimeLineContainer>
Expand All @@ -65,19 +48,21 @@ const Timeline: React.FC<{
);
};

const currentPeriodToCurrentItem = (currentPeriodIndex: number, ruled?: boolean): number => {
const currentPeriodToCurrentItem = (currentPeriodIndex: number, hiddenVotes?: boolean): number => {
if (hiddenVotes) return currentPeriodIndex;
if (currentPeriodIndex <= Periods.commit) return currentPeriodIndex;
else if (currentPeriodIndex < Periods.execution) return currentPeriodIndex - 1;
else return ruled ? 5 : currentPeriodIndex - 1;
else return currentPeriodIndex - 1;
};

const useTimeline = (dispute: DisputeDetailsQuery["dispute"], currentItemIndex: number, currentPeriodIndex: number) => {
const titles = [
{ mobile: "Evidence", desktop: "Evidence Period" },
{ mobile: "Voting", desktop: "Voting Period" },
{ mobile: "Appeal", desktop: "Appeal Period" },
{ mobile: "Executed", desktop: "Executed" },
];
const isDesktop = useIsDesktop();
const titles = useMemo(() => {
const titles = ["Evidence", "Voting", "Appeal", "Executed"];
if (dispute?.court.hiddenVotes) {
titles.splice(1, 0, "Commit");
}
return titles;
}, [dispute]);
const deadlineCurrentPeriod = getDeadline(
currentPeriodIndex,
dispute?.lastPeriodChange,
Expand All @@ -86,12 +71,12 @@ const useTimeline = (dispute: DisputeDetailsQuery["dispute"], currentItemIndex:
const countdown = useCountdown(deadlineCurrentPeriod);
const getSubitems = (index: number): string[] | React.ReactNode[] => {
if (typeof countdown !== "undefined" && dispute) {
if (index === currentItemIndex && countdown === 0) {
if (index === titles.length - 1) {
return [];
} else if (index === currentItemIndex && countdown === 0) {
return ["Time's up!"];
} else if (index < currentItemIndex) {
return [];
} else if (index === 3) {
return currentItemIndex === 3 ? ["Pending"] : [];
} else if (index === currentItemIndex) {
return [secondsToDayHourMinute(countdown)];
} else {
Expand All @@ -101,12 +86,7 @@ const useTimeline = (dispute: DisputeDetailsQuery["dispute"], currentItemIndex:
return [<StyledSkeleton key={index} width={60} />];
};
return titles.map((title, i) => ({
title: (
<>
<TitleMobile>{title.mobile}</TitleMobile>
<TitleDesktop>{title.desktop}</TitleDesktop>
</>
),
title: i + 1 < titles.length && isDesktop ? `${title} Period` : title,
subitems: getSubitems(i),
}));
};
Expand Down
Loading