Skip to content

Commit 0500272

Browse files
committed
feat: add section for claiming staking rewards in v2, tested in arb sepolia
1 parent 8978cd9 commit 0500272

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useState, useEffect } from "react";
2+
import { useAccount, useWriteContract } from "wagmi";
3+
import { formatEther, parseAbi } from "viem";
4+
import { DEFAULT_CHAIN } from "consts/chains";
5+
6+
const ipfsEndpoint = "https://cdn.kleros.link";
7+
8+
const chainIdToParams = {
9+
421614: {
10+
contractAddress: "0x9DdAeD4e2Ba34d59025c1A549311F621a8ff9b7b",
11+
snapshots: ["QmQBupnUD9zt2dzZcB6tNAENiWtmwfWeKDuZbWEWoKs7s2/arbSepolia-snapshot-2025-02.json"],
12+
startMonth: 2,
13+
},
14+
42161: {
15+
contractAddress: "",
16+
snapshots: [],
17+
startMonth: 2,
18+
},
19+
};
20+
21+
const claimMonthsAbi = parseAbi([
22+
"function claimMonths(address _liquidityProvider, (uint256 month, uint256 balance, bytes32[] merkleProof)[] claims)",
23+
]);
24+
25+
const ClaimModal = () => {
26+
const { address: account } = useAccount();
27+
const chainId = DEFAULT_CHAIN;
28+
const chainParams = chainIdToParams[chainId] ?? chainIdToParams[DEFAULT_CHAIN];
29+
30+
const [claims, setClaims] = useState([]);
31+
const [loading, setLoading] = useState(false);
32+
const [claimed, setClaimed] = useState(false);
33+
34+
useEffect(() => {
35+
const fetchClaims = async () => {
36+
if (!account || !chainParams) return;
37+
38+
const userClaims = [];
39+
for (let index = 0; index < chainParams.snapshots.length; index++) {
40+
const response = await fetch(`${ipfsEndpoint}/ipfs/${chainParams.snapshots[index]}`);
41+
const snapshot = await response.json();
42+
const claim = snapshot.merkleTree.claims[account];
43+
44+
if (claim) {
45+
userClaims.push({
46+
month: chainParams.startMonth + index,
47+
balance: BigInt(claim.value.hex),
48+
merkleProof: claim.proof,
49+
});
50+
}
51+
}
52+
setClaims(userClaims);
53+
};
54+
55+
fetchClaims();
56+
}, [account, chainParams]);
57+
58+
const { writeContractAsync } = useWriteContract();
59+
60+
const handleClaim = async () => {
61+
if (!claims.length || !account) return;
62+
setLoading(true);
63+
64+
try {
65+
await writeContractAsync({
66+
abi: claimMonthsAbi,
67+
address: chainParams.contractAddress,
68+
functionName: "claimMonths",
69+
args: [account, claims],
70+
gasLimit: 500000,
71+
});
72+
73+
setClaimed(true);
74+
} catch (error) {
75+
console.error("Transaction failed:", error);
76+
setClaimed(false);
77+
}
78+
79+
setLoading(false);
80+
};
81+
82+
const totalClaimableTokens = claims.reduce((acc, claim) => acc + claim.balance, BigInt(0));
83+
84+
return (
85+
<div>
86+
{loading && <div>Loading...</div>}
87+
88+
{!loading && !claimed && (
89+
<>
90+
<p>Claimable Tokens: {formatEther(totalClaimableTokens)} PNK</p>
91+
<button onClick={handleClaim} disabled={!claims.length}>
92+
Claim Tokens
93+
</button>
94+
</>
95+
)}
96+
97+
{claimed && <p>🎉 Tokens Claimed 🎉</p>}
98+
</div>
99+
);
100+
};
101+
102+
export default ClaimModal;

web/src/pages/Profile/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import FavoriteCases from "components/FavoriteCases";
1919
import ScrollTop from "components/ScrollTop";
2020
import Courts from "./Courts";
2121
import JurorInfo from "./JurorInfo";
22+
import StakingRewardsClaimModal from "./StakingRewardsClaimModal";
2223

2324
const Container = styled.div`
2425
width: 100%;
@@ -94,6 +95,7 @@ const Profile: React.FC = () => {
9495
}
9596
{...{ casesPerPage }}
9697
/>
98+
<StakingRewardsClaimModal />
9799
</>
98100
) : (
99101
<ConnectWalletContainer>

0 commit comments

Comments
 (0)