Skip to content

Commit eedb466

Browse files
authored
Wire up claiming miles (#1782)
* Add merklType to Reward interface * Wire up Miles claim * build appconfig to include updated name Miles -> Hyperdrive Miles
1 parent 07418cb commit eedb466

File tree

8 files changed

+728
-683
lines changed

8 files changed

+728
-683
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Reward } from "src/rewards/generated/HyperdriveRewardsApi";
2+
3+
export interface ClaimableReward extends Reward {
4+
merkleType: "HyperdriveMerkle" | "MerklXyz";
5+
}

apps/hyperdrive-trading/src/ui/portfolio/rewards/RewardsTableDesktop.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import classNames from "classnames";
1212
import { ReactElement } from "react";
1313
import Skeleton from "react-loading-skeleton";
14+
import { ClaimableReward } from "src/rewards/ClaimableReward";
1415
import { Reward } from "src/rewards/generated/HyperdriveRewardsApi";
1516
import { useAppConfigForConnectedChain } from "src/ui/appconfig/useAppConfigForConnectedChain";
1617
import { Pagination } from "src/ui/base/components/Pagination";
@@ -28,7 +29,7 @@ export function RewardsTableDesktop({
2829
rewards,
2930
}: {
3031
account: Address;
31-
rewards: Reward[];
32+
rewards: ClaimableReward[];
3233
}): ReactElement {
3334
const appConfig = useAppConfigForConnectedChain({ strict: false });
3435
const tableInstance = useReactTable({
@@ -139,7 +140,7 @@ export function RewardsTableDesktop({
139140
);
140141
}
141142

142-
const columnHelper = createColumnHelper<Reward>();
143+
const columnHelper = createColumnHelper<ClaimableReward>();
143144

144145
function getColumns({
145146
account,
@@ -240,7 +241,7 @@ function ClaimRewardsButton({
240241
reward,
241242
}: {
242243
account: Address | undefined;
243-
reward: Reward;
244+
reward: ClaimableReward;
244245
}): ReactElement {
245246
const connectedChainId = useChainId();
246247
const { claimed } = useClaimedRewards({

apps/hyperdrive-trading/src/ui/rewards/hooks/useClaimReward.tsx

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@ import { usePublicClient, useWriteContract } from "wagmi";
77
import { getToken } from "@delvtech/hyperdrive-appconfig";
88
import { hyperdriveRewardsAbi } from "@delvtech/hyperdrive-js";
99
import { useCallback, useState } from "react";
10+
import { assertNever } from "src/base/assertNever";
1011
import { QueryStatusWithIdle } from "src/base/queryStatus";
11-
import { Reward } from "src/rewards/generated/HyperdriveRewardsApi";
12+
import { ClaimableReward } from "src/rewards/ClaimableReward";
1213
import { useAppConfigForConnectedChain } from "src/ui/appconfig/useAppConfigForConnectedChain";
1314
import { SUCCESS_TOAST_DURATION } from "src/ui/base/toasts";
15+
import { merklDistributorAbi } from "src/ui/rewards/merklDistributorAbi";
1416
import TransactionToast from "src/ui/transactions/TransactionToast";
1517
import { Address } from "viem";
18+
import { WriteContractVariables } from "wagmi/query";
1619

1720
export function useClaimReward({
1821
account,
1922
reward,
2023
enabled = true,
2124
}: {
2225
account: Address | undefined;
23-
reward: Reward;
26+
reward: ClaimableReward;
2427
enabled?: boolean;
2528
}): {
2629
claim: (() => void) | undefined;
@@ -44,63 +47,46 @@ export function useClaimReward({
4447
return;
4548
}
4649

47-
return writeContract(
48-
{
49-
abi: hyperdriveRewardsAbi,
50-
address: reward.claimContractAddress,
51-
chainId: reward.chainId,
52-
functionName: "claim",
53-
args: [
54-
account,
55-
reward.rewardTokenAddress,
56-
BigInt(reward.claimableAmount), // must be the claimable amount, since it's baked into the merkle proof
57-
reward.merkleProof,
58-
],
59-
},
60-
{
61-
onSuccess: async (hash) => {
62-
addRecentTransaction({
63-
hash,
64-
description: "Claim Reward",
65-
});
66-
setIsTransactionMined(false);
67-
toast.loading(
68-
<TransactionToast
69-
chainId={reward.chainId}
70-
message={`Claiming ${token?.symbol} reward...`}
71-
txHash={hash}
72-
/>,
73-
{ id: hash },
74-
);
50+
const claimArgs = getClaimArgs(account, reward);
51+
writeContract(claimArgs as any, {
52+
onSuccess: async (hash) => {
53+
addRecentTransaction({
54+
hash,
55+
description: "Claim Reward",
56+
});
57+
setIsTransactionMined(false);
58+
toast.loading(
59+
<TransactionToast
60+
chainId={reward.chainId}
61+
message={`Claiming ${token?.symbol} reward...`}
62+
txHash={hash}
63+
/>,
64+
{ id: hash },
65+
);
7566

76-
await waitForTransactionAndInvalidateCache({
77-
publicClient,
78-
hash,
79-
queryClient,
80-
});
81-
setIsTransactionMined(true);
67+
await waitForTransactionAndInvalidateCache({
68+
publicClient,
69+
hash,
70+
queryClient,
71+
});
72+
setIsTransactionMined(true);
8273

83-
toast.success(
84-
<TransactionToast
85-
chainId={reward.chainId}
86-
message={`Claimed ${token?.symbol} reward`}
87-
txHash={hash}
88-
/>,
89-
{ id: hash, duration: SUCCESS_TOAST_DURATION },
90-
);
91-
},
74+
toast.success(
75+
<TransactionToast
76+
chainId={reward.chainId}
77+
message={`Claimed ${token?.symbol} reward`}
78+
txHash={hash}
79+
/>,
80+
{ id: hash, duration: SUCCESS_TOAST_DURATION },
81+
);
9282
},
93-
);
83+
});
9484
}, [
9585
account,
9686
addRecentTransaction,
9787
publicClient,
9888
queryEnabled,
99-
reward.chainId,
100-
reward.claimContractAddress,
101-
reward.claimableAmount,
102-
reward.merkleProof,
103-
reward.rewardTokenAddress,
89+
reward,
10490
token?.symbol,
10591
writeContract,
10692
]);
@@ -111,3 +97,52 @@ export function useClaimReward({
11197
isTransactionMined,
11298
};
11399
}
100+
101+
function getClaimArgs(account: Address, reward: ClaimableReward) {
102+
switch (reward.merkleType) {
103+
case "HyperdriveMerkle": {
104+
const claimArgs: WriteContractVariables<
105+
typeof hyperdriveRewardsAbi,
106+
"claim",
107+
any,
108+
any,
109+
any
110+
> = {
111+
address: reward.claimContractAddress,
112+
abi: hyperdriveRewardsAbi,
113+
functionName: "claim",
114+
args: [
115+
account,
116+
reward.rewardTokenAddress,
117+
BigInt(reward.claimableAmount),
118+
reward.merkleProof,
119+
],
120+
};
121+
122+
return claimArgs;
123+
}
124+
case "MerklXyz": {
125+
const claimArgs: WriteContractVariables<
126+
typeof merklDistributorAbi,
127+
"claim",
128+
any,
129+
any,
130+
any
131+
> = {
132+
address: reward.claimContractAddress,
133+
abi: merklDistributorAbi,
134+
functionName: "claim",
135+
args: [
136+
[account],
137+
[reward.rewardTokenAddress],
138+
[BigInt(reward.claimableAmount)],
139+
[reward.merkleProof],
140+
],
141+
};
142+
143+
return claimArgs;
144+
}
145+
default:
146+
assertNever(reward.merkleType);
147+
}
148+
}

apps/hyperdrive-trading/src/ui/rewards/hooks/useClaimableRewards.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ import { MerklApi } from "@merkl/api";
33
import { useQuery } from "@tanstack/react-query";
44
import { makeQueryKey2 } from "src/base/makeQueryKey";
55
import { rewardsFork } from "src/chains/rewardsFork";
6-
import {
7-
HyperdriveRewardsApi,
8-
Reward,
9-
} from "src/rewards/generated/HyperdriveRewardsApi";
6+
import { ClaimableReward } from "src/rewards/ClaimableReward";
7+
import { HyperdriveRewardsApi } from "src/rewards/generated/HyperdriveRewardsApi";
108
import { Address, Hash } from "viem";
119
import { gnosis } from "viem/chains";
1210
import { usePublicClient } from "wagmi";
@@ -16,7 +14,7 @@ export function useClaimableRewards({
1614
}: {
1715
account: Address | undefined;
1816
}): {
19-
rewards: Reward[] | undefined;
17+
rewards: ClaimableReward[] | undefined;
2018
rewardsStatus: "error" | "success" | "loading";
2119
} {
2220
const publicClient = usePublicClient();
@@ -49,7 +47,9 @@ export function useClaimableRewards({
4947
* Rewards that come from the Hyperdrive Rewards API server. This server also
5048
* defines the shape used for rewards everywhere else in the app.
5149
*/
52-
async function fetchHyperdriveRewardApi(account: Address): Promise<Reward[]> {
50+
async function fetchHyperdriveRewardApi(
51+
account: Address,
52+
): Promise<ClaimableReward[]> {
5353
const rewardsApi = new HyperdriveRewardsApi({
5454
baseUrl: import.meta.env.VITE_REWARDS_BASE_URL,
5555
});
@@ -60,6 +60,7 @@ async function fetchHyperdriveRewardApi(account: Address): Promise<Reward[]> {
6060
return response.rewards.map((r) => ({
6161
...r,
6262
chainId: rewardsFork.id,
63+
merkleType: "HyperdriveMerkle",
6364
claimableAmount: parseFixed(r.claimableAmount).bigint.toString(),
6465
}));
6566
} catch (error: any) {
@@ -89,7 +90,7 @@ const MerklDistributorsByChain: Record<number, Address> = {
8990
[gnosis.id]: "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae",
9091
};
9192

92-
async function fetchMileRewards(account: Address): Promise<Reward[]> {
93+
async function fetchMileRewards(account: Address): Promise<ClaimableReward[]> {
9394
// Merkl.xyz accumulates Miles across all chains and hyperdrives onto Gnosis
9495
// chain only. This makes things easier for turning them into HD later if
9596
// they're all just on one chain.
@@ -121,15 +122,18 @@ async function fetchMileRewards(account: Address): Promise<Reward[]> {
121122
// since we only request a single chain id, we can just grab the first
122123
// data item
123124
data[0].rewards.find(
125+
// Merkl.xyz has something called HYPOINTS too, but we only care about
126+
// Miles
124127
(d) => d.token.symbol === "Miles" && !!Number(d.amount),
125128
),
126129
)
127-
.map(({ data, chainId }) => {
130+
.map(({ data, chainId }): ClaimableReward => {
128131
const rewards = data![0].rewards.find(
129132
(d) => d.token.symbol === "Miles" && !!Number(d.amount),
130133
);
131134
return {
132135
chainId,
136+
merkleType: "MerklXyz",
133137
merkleProof: rewards?.proofs as Hash[],
134138
claimableAmount: rewards?.amount.toString() || "0",
135139
pendingAmount: rewards?.pending.toString() || "0",

apps/hyperdrive-trading/src/ui/rewards/merklDistributorAbi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Abi for the Merkl Distributor contract: https://app.merkl.xyz/status
22

3-
export const MerklDistributorAbi = [
3+
export const merklDistributorAbi = [
44
{ inputs: [], stateMutability: "nonpayable", type: "constructor" },
55
{ inputs: [], name: "InvalidDispute", type: "error" },
66
{ inputs: [], name: "InvalidLengths", type: "error" },

0 commit comments

Comments
 (0)