Skip to content
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

feat(web): dispute-maintenance-buttons #1645

Merged
merged 20 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd5ecbb
feat(web): dispute-maintenance-buttons
Harman-singh-waraich Jul 4, 2024
7599a23
refactor(web): refactor-logic-statements
Harman-singh-waraich Jul 4, 2024
4a4b27e
fix(web): ensure-chain-and-close-on-icon-click
Harman-singh-waraich Jul 4, 2024
d5cb3f5
refactor(web): initial-ripple-check
Harman-singh-waraich Jul 4, 2024
2d2c605
style(web): update-menu-popup-padding
Harman-singh-waraich Jul 5, 2024
037bc7f
feat(web): transaction-batcher
Harman-singh-waraich Jul 8, 2024
c99ea3a
Merge branch 'dev' into feat/case-dev-buttons
kemuru Jul 15, 2024
a9e3732
chore(web): deploy-transaction-batcher-on-arbitrum-and-gnosis
Harman-singh-waraich Jul 22, 2024
c0f0d29
chore: transaction batcher deployment
jaybuidl Jul 24, 2024
90144c2
Merge branch 'dev' into feat/case-dev-buttons
jaybuidl Jul 24, 2024
1413980
fix: z-index for popupcontainer higher than the overlay
kemuru Jul 24, 2024
fe737d4
fix(web): fix-typo
Harman-singh-waraich Jul 24, 2024
dd0e335
Merge branch 'dev' into feat/case-dev-buttons
kemuru Jul 24, 2024
7cdc962
chore(web): update-transaction-batcher-hook
Harman-singh-waraich Jul 25, 2024
1c02948
refactor(web): better-disabling-conditions-for-buttons
Harman-singh-waraich Jul 25, 2024
3d268a7
feat(subgraph): add-juror-rewards-and-drawn-indicators
Harman-singh-waraich Aug 1, 2024
1149b6f
refactor(web): update-draw-jurors-call-condition
Harman-singh-waraich Aug 1, 2024
32672d2
refactor(web): update-juror-rewards-button-logic
Harman-singh-waraich Aug 1, 2024
24ca0d2
feat(subgraph): add-appeal-fee-dispersed-indicator-and-fix-fee-reward…
Harman-singh-waraich Aug 5, 2024
097bbaa
feat(web): appeal-fee-rewards-button
Harman-singh-waraich Aug 5, 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
1 change: 1 addition & 0 deletions web/src/assets/svgs/icons/dotted-menu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions web/src/hooks/queries/useDisputeDetailsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const disputeDetailsQuery = graphql(`
tied
currentRound {
id
nbVotes
}
currentRoundIndex
}
Expand Down
76 changes: 76 additions & 0 deletions web/src/hooks/useTransactionBatcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { encodeFunctionData, type SimulateContractParameters } from "viem";
import { useAccount, useWriteContract } from "wagmi";
import { createUseSimulateContract, createUseWriteContract } from "wagmi/codegen";

import { DEFAULT_CHAIN } from "consts/chains";

import { isUndefined } from "src/utils";

const batcherAbi = [
{
inputs: [
{
internalType: "address[]",
name: "targets",
type: "address[]",
},
{
internalType: "uint256[]",
name: "values",
type: "uint256[]",
},
{
internalType: "bytes[]",
name: "datas",
type: "bytes[]",
},
],
name: "batchSend",
outputs: [],
stateMutability: "payable",
type: "function",
},
] as const;

const useBatchSimulate = createUseSimulateContract({
abi: batcherAbi,
functionName: "batchSend",
});

export const useBatchWrite = createUseWriteContract({
abi: batcherAbi,
functionName: "batchSend",
});

const batcherAddress = {
421614: "0xe8061d185D865ce2B2FbCfDa628b5F147d8eB8Ab",
};

export type TransactionBatcherConfig = SimulateContractParameters[];

const useTransactionBatcher = (configs?: TransactionBatcherConfig) => {
const { chainId } = useAccount();

const validConfigs = configs ?? [];
const {
data: batchConfig,
isLoading,
isError,
} = useBatchSimulate({
query: {
enabled: !isUndefined(configs),
},
address: batcherAddress[chainId ?? DEFAULT_CHAIN],
args: [
validConfigs.map((config) => config?.address),
validConfigs.map((config) => config?.value ?? BigInt(0)),
validConfigs.map((config) => encodeFunctionData(config)),
],
});
const { writeContractAsync } = useWriteContract();

const executeBatch = () => batchConfig && writeContractAsync(batchConfig.request);
return { executeBatch, batchConfig, isError, isLoading };
};

export default useTransactionBatcher;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useEffect, useMemo, useState } from "react";
import styled from "styled-components";

import { useAccount, usePublicClient } from "wagmi";

import { Button } from "@kleros/ui-components-library";

import { DEFAULT_CHAIN } from "consts/chains";
import { klerosCoreAbi, klerosCoreAddress } from "hooks/contracts/generated";
import useTransactionBatcher, { type TransactionBatcherConfig, useBatchWrite } from "hooks/useTransactionBatcher";
import { wrapWithToast } from "utils/wrapWithToast";

import { isUndefined } from "src/utils";

import { IBaseMaintenaceButton } from ".";

const StyledButton = styled(Button)`
width: 100%;
`;

interface IDistributeRewards extends IBaseMaintenaceButton {
numberOfVotes?: string;
roundIndex?: string;
}

const DistributeRewards: React.FC<IDistributeRewards> = ({ id, numberOfVotes, roundIndex, setIsOpen }) => {
const [isSending, setIsSending] = useState(false);
const [contractConfigs, setContractConfigs] = useState<TransactionBatcherConfig>();
const publicClient = usePublicClient();
const { chainId } = useAccount();

useEffect(() => {
if (!id || !roundIndex || !numberOfVotes) return;

const baseArgs = {
abi: klerosCoreAbi,
address: klerosCoreAddress[chainId ?? DEFAULT_CHAIN],
functionName: "execute",
};

const argsArr: TransactionBatcherConfig = [];
let nbVotes = parseInt(numberOfVotes);

// each previous round has (n - 1)/2 jurors
for (let i = parseInt(roundIndex); i >= 0; i--) {
argsArr.push({ ...baseArgs, args: [BigInt(id), BigInt(i), BigInt(nbVotes)] });

nbVotes = (nbVotes - 1) / 2;
}

setContractConfigs(argsArr);
}, [id, roundIndex, numberOfVotes, chainId]);

const { batchConfig, isLoading: isLoadingConfig, isError } = useTransactionBatcher(contractConfigs);

const { writeContractAsync: executeBatch } = useBatchWrite();

const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
const isDisabled = useMemo(
() => isUndefined(id) || isUndefined(numberOfVotes) || isError || isLoading,
[id, numberOfVotes, isError, isLoading]
);

const handleClick = () => {
if (!batchConfig) return;

setIsSending(true);

wrapWithToast(async () => await executeBatch(batchConfig.request), publicClient).finally(() => {
setIsOpen(false);
});
};
return <StyledButton text="Rewards" small isLoading={isLoading} disabled={isDisabled} onClick={handleClick} />;
};

export default DistributeRewards;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useMemo, useState } from "react";
import styled from "styled-components";

import { usePublicClient } from "wagmi";

import { Button } from "@kleros/ui-components-library";

import { useSimulateKlerosCoreDraw, useWriteKlerosCoreDraw } from "hooks/contracts/generated";
import { wrapWithToast } from "utils/wrapWithToast";

import { isUndefined } from "src/utils";

import { IBaseMaintenaceButton } from ".";

const StyledButton = styled(Button)`
width: 100%;
`;

interface IDrawButton extends IBaseMaintenaceButton {
numberOfVotes?: string;
}

const DrawButton: React.FC<IDrawButton> = ({ id, numberOfVotes, setIsOpen }) => {
const [isSending, setIsSending] = useState(false);
const publicClient = usePublicClient();

const {
data: drawConfig,
isLoading: isLoadingConfig,
isError,
} = useSimulateKlerosCoreDraw({
query: {
enabled: !isUndefined(id) && !isUndefined(numberOfVotes),
},
args: [BigInt(id ?? 0), BigInt(numberOfVotes ?? 0)],
});

const { writeContractAsync: draw } = useWriteKlerosCoreDraw();

const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
const isDisabled = useMemo(
() => isUndefined(id) || isUndefined(numberOfVotes) || isError || isLoading,
[id, numberOfVotes, isError, isLoading]
);
const handleClick = () => {
if (!drawConfig) return;

setIsSending(true);

wrapWithToast(async () => await draw(drawConfig.request), publicClient).finally(() => {
setIsSending(false);
setIsOpen(false);
});
};
return <StyledButton text="Draw" small isLoading={isLoading} disabled={isDisabled} onClick={handleClick} />;
};

export default DrawButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useMemo, useState } from "react";
import styled from "styled-components";

import { usePublicClient } from "wagmi";

import { Button } from "@kleros/ui-components-library";

import { useSimulateKlerosCoreExecuteRuling, useWriteKlerosCoreExecuteRuling } from "hooks/contracts/generated";
import { wrapWithToast } from "utils/wrapWithToast";

import { isUndefined } from "src/utils";

import { IBaseMaintenaceButton } from ".";

const StyledButton = styled(Button)`
width: 100%;
`;

type IExecuteRulingButton = IBaseMaintenaceButton;

const ExecuteRulingButton: React.FC<IExecuteRulingButton> = ({ id, setIsOpen }) => {
const [isSending, setIsSending] = useState(false);
const publicClient = usePublicClient();

const {
data: ruleConfig,
isLoading: isLoadingConfig,
isError,
} = useSimulateKlerosCoreExecuteRuling({
query: {
enabled: !isUndefined(id),
},
args: [BigInt(id ?? 0)],
});

const { writeContractAsync: rule } = useWriteKlerosCoreExecuteRuling();

const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
const isDisabled = useMemo(() => isUndefined(id) || isError || isLoading, [id, isError, isLoading]);
const handleClick = () => {
if (!ruleConfig) return;

setIsSending(true);

wrapWithToast(async () => await rule(ruleConfig.request), publicClient).finally(() => {
setIsSending(false);
setIsOpen(false);
});
};
return <StyledButton text="Rule" small isLoading={isLoading} disabled={isDisabled} onClick={handleClick} />;
};

export default ExecuteRulingButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from "react";
import styled, { css, keyframes } from "styled-components";

import DottedMenu from "svgs/icons/dotted-menu.svg";

const ripple = keyframes`
0% {
opacity: 0;
transform: scale3d(0.5, 0.5, 1);
}
10% {
opacity: 0.5;
transform: scale3d(0.75, 0.75, 1);
}

100% {
opacity: 0;
transform: scale3d(1.75, 1.75, 1);
}
`;

const ring = (duration: string, delay: string) => css`
opacity: 0;
position: absolute;
top: 0;
left: 0;
transform: translate(50%);
content: "";
height: 36px;
width: 36px;
border: 3px solid ${({ theme }) => theme.primaryBlue};
border-radius: 100%;
animation-name: ${ripple};
animation-duration: ${duration};
animation-delay: ${delay};
animation-iteration-count: infinite;
animation-timing-function: cubic-bezier(0.65, 0, 0.34, 1);
z-index: 0;
`;

const Container = styled.div<{ displayRipple: boolean }>`
display: flex;
justify-content: center;
align-items: center;
${({ displayRipple }) =>
displayRipple &&
css`
&::after {
${ring("3s", "0s")}
}
&::before {
${ring("3s", "0.5s")}
}
`}
`;

const ButtonContainer = styled.div`
border-radius: 50%;
z-index: 1;
background-color: ${({ theme }) => theme.lightBackground};
`;

const StyledDottedMenu = styled(DottedMenu)`
cursor: pointer;
width: 36px;
height: 36px;
fill: ${({ theme }) => theme.primaryBlue};
`;

interface IMenuButton {
toggle: () => void;
displayRipple: boolean;
}

const MenuButton: React.FC<IMenuButton> = ({ toggle, displayRipple }) => {
return (
<Container {...{ displayRipple }}>
<ButtonContainer>
<StyledDottedMenu onClick={toggle} />
</ButtonContainer>
</Container>
);
};

export default MenuButton;
Loading
Loading