Skip to content

Commit 097bbaa

Browse files
committed
feat(web): appeal-fee-rewards-button
1 parent 24ca0d2 commit 097bbaa

File tree

7 files changed

+142
-23
lines changed

7 files changed

+142
-23
lines changed

web/src/hooks/queries/useClassicAppealQuery.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const classicAppealQuery = graphql(`
2727
winningChoice
2828
paidFees
2929
fundedChoices
30+
appealFeesDispersed
31+
totalFeeDispersed
3032
}
3133
}
3234
}

web/src/hooks/useTransactionBatcher.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ const useTransactionBatcher = (
5353
});
5454
const { writeContractAsync } = useWriteTransactionBatcherBatchSend();
5555

56-
const executeBatch = useCallback(() => writeContractAsync(batchConfig.request), [batchConfig, writeContractAsync]);
56+
const executeBatch = useCallback(
57+
(config: NonNullable<typeof batchConfig>) => writeContractAsync(config?.request),
58+
[writeContractAsync]
59+
);
60+
5761
return { executeBatch, batchConfig, isError, isLoading };
5862
};
5963

web/src/pages/Cases/CaseDetails/MaintenanceButtons/DistributeRewards.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const DistributeRewards: React.FC<IDistributeRewards> = ({ id, roundIndex, setIs
4141

4242
useEffect(() => {
4343
const rounds = maintenanceData?.dispute?.rounds;
44-
if (!id || !roundIndex || !rounds) return;
44+
if (isUndefined(id) || isUndefined(roundIndex) || isUndefined(rounds)) return;
4545

4646
const baseArgs = {
4747
abi: klerosCoreAbi,
@@ -60,6 +60,7 @@ const DistributeRewards: React.FC<IDistributeRewards> = ({ id, roundIndex, setIs
6060

6161
const {
6262
executeBatch,
63+
batchConfig,
6364
isLoading: isLoadingConfig,
6465
isError,
6566
} = useTransactionBatcher(contractConfigs, {
@@ -73,10 +74,11 @@ const DistributeRewards: React.FC<IDistributeRewards> = ({ id, roundIndex, setIs
7374
);
7475

7576
const handleClick = () => {
76-
if (!publicClient) return;
77+
if (!publicClient || !batchConfig) return;
7778
setIsSending(true);
7879

79-
wrapWithToast(async () => await executeBatch(), publicClient).finally(() => {
80+
wrapWithToast(async () => await executeBatch(batchConfig), publicClient).finally(() => {
81+
setIsSending(false);
8082
setIsOpen(false);
8183
});
8284
};

web/src/pages/Cases/CaseDetails/MaintenanceButtons/PassPeriodButton.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ const PassPeriodButton: React.FC<IPassPeriodButton> = ({ id, setIsOpen, period }
5050
const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
5151
const isDisabled = useMemo(
5252
() =>
53-
isUndefined(id) || isError || isLoading || period === Period.Execution || (period === Period.Evidence && isDrawn),
53+
isUndefined(id) ||
54+
isError ||
55+
isLoading ||
56+
period === Period.Execution ||
57+
(period === Period.Evidence && !isDrawn),
5458
[id, isError, isLoading, period, isDrawn]
5559
);
5660
const handleClick = () => {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import React, { useEffect, useMemo, useState } from "react";
2+
import styled from "styled-components";
3+
4+
import { useAccount, usePublicClient } from "wagmi";
5+
6+
import { Button } from "@kleros/ui-components-library";
7+
8+
import { DEFAULT_CHAIN } from "consts/chains";
9+
import { disputeKitClassicAbi, disputeKitClassicAddress } from "hooks/contracts/generated";
10+
import useTransactionBatcher, { type TransactionBatcherConfig } from "hooks/useTransactionBatcher";
11+
import { getLocalRounds } from "utils/getLocalRounds";
12+
import { wrapWithToast } from "utils/wrapWithToast";
13+
14+
import { useClassicAppealQuery } from "queries/useClassicAppealQuery";
15+
import useDisputeMaintenanceQuery from "queries/useDisputeMaintenanceQuery";
16+
17+
import { Period } from "src/graphql/graphql";
18+
import { isUndefined } from "src/utils";
19+
20+
import { IBaseMaintenanceButton } from ".";
21+
22+
const StyledButton = styled(Button)`
23+
width: 100%;
24+
`;
25+
26+
interface IWithdrawAppealFees extends IBaseMaintenanceButton {
27+
roundIndex?: number;
28+
period?: string;
29+
ruled?: boolean;
30+
}
31+
32+
const WithdrawAppealFees: React.FC<IWithdrawAppealFees> = ({ id, roundIndex, setIsOpen, period, ruled }) => {
33+
const [isSending, setIsSending] = useState(false);
34+
const [contractConfigs, setContractConfigs] = useState<TransactionBatcherConfig>();
35+
const publicClient = usePublicClient();
36+
const { chainId } = useAccount();
37+
38+
const { data: maintenanceData } = useDisputeMaintenanceQuery(id);
39+
const { data: appealData } = useClassicAppealQuery(id);
40+
41+
const localRounds = useMemo(() => getLocalRounds(appealData?.dispute?.disputeKitDispute), [appealData]);
42+
console.log({ localRounds });
43+
44+
const feeDispersed = useMemo(
45+
() =>
46+
localRounds ? localRounds.slice(0, localRounds.length - 1).every((round) => round.appealFeesDispersed) : false,
47+
[localRounds]
48+
);
49+
50+
const filteredContributions = useMemo(() => {
51+
const deDuplicatedContributions = [
52+
...new Set(maintenanceData?.contributions.filter((contribution) => !contribution.rewardWithdrawn)),
53+
];
54+
55+
return deDuplicatedContributions;
56+
}, [maintenanceData]);
57+
58+
useEffect(() => {
59+
if (isUndefined(id) || isUndefined(roundIndex)) return;
60+
61+
const baseArgs = {
62+
abi: disputeKitClassicAbi,
63+
address: disputeKitClassicAddress[chainId ?? DEFAULT_CHAIN],
64+
functionName: "withdrawFeesAndRewards",
65+
};
66+
67+
const argsArr: TransactionBatcherConfig = [];
68+
69+
for (const contribution of filteredContributions) {
70+
for (let round = roundIndex; round >= 0; round--) {
71+
argsArr.push({
72+
...baseArgs,
73+
args: [BigInt(id), contribution.contributor.id, BigInt(round), contribution.choice],
74+
});
75+
}
76+
}
77+
78+
setContractConfigs(argsArr);
79+
}, [id, roundIndex, chainId, filteredContributions]);
80+
81+
const {
82+
executeBatch,
83+
batchConfig,
84+
isLoading: isLoadingConfig,
85+
isError,
86+
} = useTransactionBatcher(contractConfigs, {
87+
enabled: !isUndefined(period) && period === Period.Execution && Boolean(ruled) && !feeDispersed,
88+
});
89+
90+
const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
91+
const isDisabled = useMemo(
92+
() => isUndefined(id) || isError || isLoading || period !== Period.Execution || feeDispersed || !ruled,
93+
[id, isError, isLoading, period, feeDispersed, ruled]
94+
);
95+
96+
const handleClick = () => {
97+
if (!publicClient || !batchConfig) return;
98+
setIsSending(true);
99+
100+
wrapWithToast(async () => await executeBatch(batchConfig), publicClient).finally(() => {
101+
setIsSending(false);
102+
setIsOpen(false);
103+
});
104+
};
105+
return <StyledButton text="Appeal Rewards" small isLoading={isLoading} disabled={isDisabled} onClick={handleClick} />;
106+
};
107+
108+
export default WithdrawAppealFees;

web/src/pages/Cases/CaseDetails/MaintenanceButtons/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import DrawButton from "./DrawButton";
1616
import ExecuteRulingButton from "./ExecuteRuling";
1717
import MenuButton from "./MenuButton";
1818
import PassPeriodButton from "./PassPeriodButton";
19+
import WithdrawAppealFees from "./WithdrawAppealFees";
1920

2021
const Container = styled.div`
2122
width: 36px;
@@ -110,12 +111,18 @@ const MaintenanceButtons: React.FC = () => {
110111
period={dispute?.period}
111112
/>
112113
<PassPeriodButton {...{ id, setIsOpen }} period={dispute?.period} />
114+
<ExecuteRulingButton {...{ id, setIsOpen }} period={dispute?.period} ruled={dispute?.ruled} />
113115
<DistributeRewards
114116
{...{ id, setIsOpen }}
115117
roundIndex={dispute?.currentRoundIndex}
116118
period={dispute?.period}
117119
/>
118-
<ExecuteRulingButton {...{ id, setIsOpen }} period={dispute?.period} ruled={dispute?.ruled} />
120+
<WithdrawAppealFees
121+
{...{ id, setIsOpen }}
122+
roundIndex={parseInt(dispute?.currentRoundIndex, 10)}
123+
period={dispute?.period}
124+
ruled={dispute?.ruled}
125+
/>
119126
</>
120127
</EnsureChain>
121128
</PopupContainer>

web/src/utils/getLocalRounds.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
import { ClassicAppealQuery } from "queries/useClassicAppealQuery";
2-
import { VotingHistoryQuery } from "queries/useVotingHistory";
3-
4-
type IVotingHistoryLocalRounds = NonNullable<
5-
NonNullable<VotingHistoryQuery["dispute"]>["disputeKitDispute"]
6-
>["localRounds"];
7-
8-
type IClassicAppealQueryLocalRounds = NonNullable<
9-
NonNullable<ClassicAppealQuery["dispute"]>["disputeKitDispute"]
10-
>["localRounds"];
11-
12-
type ILocalRounds = IClassicAppealQueryLocalRounds | IVotingHistoryLocalRounds;
13-
14-
interface IDisputeKitDisputes {
15-
localRounds: ILocalRounds;
1+
interface DisputeKitDispute<T> {
2+
localRounds: T[];
163
}
174

18-
export const getLocalRounds = (disputeKitDisputes: IDisputeKitDisputes | undefined | null) => {
19-
return disputeKitDisputes?.reduce<ILocalRounds>((acc: ILocalRounds, { localRounds }) => acc.concat(localRounds), []);
5+
/**
6+
* @param disputeKitDisputes an array of dispute kit disputes with field localRounds
7+
* @returns a flattened array of localRounds
8+
*/
9+
export const getLocalRounds = <T>(disputeKitDisputes: DisputeKitDispute<T>[] | undefined | null): T[] => {
10+
if (!disputeKitDisputes) return [];
11+
return disputeKitDisputes.flatMap(({ localRounds }) => localRounds);
2012
};

0 commit comments

Comments
 (0)