@dhedge/v2-sdk 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +60 -3
  2. package/dist/config.d.ts +7 -0
  3. package/dist/entities/pool.d.ts +101 -5
  4. package/dist/entities/utils.d.ts +10 -0
  5. package/dist/services/claim-balancer/claim.service.d.ts +17 -0
  6. package/dist/services/claim-balancer/claim.worker.d.ts +4 -0
  7. package/dist/services/claim-balancer/ipfs.service.d.ts +4 -0
  8. package/dist/services/claim-balancer/types.d.ts +54 -0
  9. package/dist/services/uniswap/V3Liquidity.d.ts +9 -0
  10. package/dist/services/uniswap/V3Trade.d.ts +4 -0
  11. package/dist/services/uniswap/types.d.ts +3 -0
  12. package/dist/test/constants.d.ts +12 -0
  13. package/dist/test/txOptions.d.ts +1 -0
  14. package/dist/types.d.ts +20 -6
  15. package/dist/utils/contract.d.ts +14 -0
  16. package/dist/utils/index.d.ts +7 -0
  17. package/dist/utils/merkle.d.ts +22 -0
  18. package/dist/v2-sdk.cjs.development.js +6086 -918
  19. package/dist/v2-sdk.cjs.development.js.map +1 -1
  20. package/dist/v2-sdk.cjs.production.min.js +1 -1
  21. package/dist/v2-sdk.cjs.production.min.js.map +1 -1
  22. package/dist/v2-sdk.esm.js +6090 -922
  23. package/dist/v2-sdk.esm.js.map +1 -1
  24. package/package.json +12 -2
  25. package/src/abi/IAaveIncentivesController.json +50 -0
  26. package/src/abi/IBalancerMerkleOrchard.json +353 -0
  27. package/src/abi/IBalancertV2Vault.json +938 -0
  28. package/src/abi/IERC20.json +15 -1
  29. package/src/abi/INonfungiblePositionManager.json +1221 -0
  30. package/src/abi/ISynthetix.json +139 -0
  31. package/src/abi/IUniswapV3Quoter.json +195 -0
  32. package/src/abi/IUniswapV3Router.json +221 -0
  33. package/src/config.ts +48 -8
  34. package/src/entities/dhedge.ts +4 -2
  35. package/src/entities/pool.ts +398 -27
  36. package/src/entities/utils.ts +147 -0
  37. package/src/services/claim-balancer/MultiTokenClaim.json +115 -0
  38. package/src/services/claim-balancer/claim.service.ts +262 -0
  39. package/src/services/claim-balancer/claim.worker.ts +32 -0
  40. package/src/services/claim-balancer/ipfs.service.ts +12 -0
  41. package/src/services/claim-balancer/types.ts +66 -0
  42. package/src/services/uniswap/V3Liquidity.ts +134 -0
  43. package/src/services/uniswap/V3Trade.ts +47 -0
  44. package/src/services/uniswap/types.ts +16 -0
  45. package/src/test/aave.test.ts +73 -0
  46. package/src/test/balancer.test.ts +109 -0
  47. package/src/test/constants.ts +17 -0
  48. package/src/test/oneInch.test.ts +56 -0
  49. package/src/test/pool.test.ts +6 -250
  50. package/src/test/sushi.test.ts +173 -0
  51. package/src/test/synthetix.test.ts +34 -0
  52. package/src/test/txOptions.ts +15 -0
  53. package/src/test/uniswap.test.ts +127 -0
  54. package/src/test/utils.test.ts +41 -26
  55. package/src/test/wallet.ts +5 -1
  56. package/src/types.ts +17 -3
  57. package/src/utils/contract.ts +95 -0
  58. package/src/utils/index.ts +38 -0
  59. package/src/utils/merkle.ts +172 -0
@@ -8,17 +8,22 @@ import {
8
8
  Trade,
9
9
  Percent
10
10
  } from "@sushiswap/sdk";
11
+ import { SOR, SwapTypes } from "@balancer-labs/sor";
12
+ import { BaseProvider } from "@ethersproject/providers";
11
13
 
12
14
  import IERC20 from "../abi/IERC20.json";
13
15
  import IMiniChefV2 from "../abi/IMiniChefV2.json";
14
16
  import UniswapV2Factory from "../abi/IUniswapV2Factory.json";
15
17
  import UniswapV2Pair from "../abi/IUniswapV2Pair.json";
18
+ import IBalancerV2Vault from "../abi/IBalancertV2Vault.json";
16
19
  import {
20
+ balancerSubgraph,
17
21
  dappFactoryAddress,
18
22
  networkChainIdMap,
19
23
  stakingAddress
20
24
  } from "../config";
21
25
  import { Dapp, Network, Reserves } from "../types";
26
+ import { Pool } from ".";
22
27
 
23
28
  export class Utils {
24
29
  network: Network;
@@ -120,6 +125,18 @@ export class Utils {
120
125
  return balance;
121
126
  }
122
127
 
128
+ /**
129
+ * Returns the decimals of an asset (ERC20) token
130
+ * @param {string} asset string token address
131
+ * @returns { number } Balance of asset
132
+ */
133
+
134
+ async getDecimals(asset: string): Promise<number> {
135
+ const iERC20 = new ethers.Contract(asset, IERC20.abi, this.signer);
136
+ const decimals = await iERC20.decimals();
137
+ return decimals;
138
+ }
139
+
123
140
  /**
124
141
  * Return the minimum amount out for a trade between two assets
125
142
  * given the trade amount and slippage
@@ -171,4 +188,134 @@ export class Utils {
171
188
  .raw.toString()
172
189
  );
173
190
  }
191
+
192
+ async getBalancerSwapTx(
193
+ pool: Pool,
194
+ assetFrom: string,
195
+ assetTo: string,
196
+ amountIn: ethers.BigNumber | string,
197
+ slippage: number
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ ): Promise<any> {
200
+ const sor = new SOR(
201
+ this.signer.provider as BaseProvider,
202
+ networkChainIdMap[this.network],
203
+ balancerSubgraph[this.network]
204
+ );
205
+ // isFetched will be true on success
206
+ const isFetched = await sor.fetchPools();
207
+
208
+ if (!isFetched) throw new Error("Error fetching balancer pools");
209
+
210
+ const swapType = SwapTypes.SwapExactIn;
211
+ const { swaps, tokenAddresses, returnAmount } = await sor.getSwaps(
212
+ assetFrom,
213
+ assetTo,
214
+ swapType,
215
+ ethers.BigNumber.from(amountIn)
216
+ );
217
+
218
+ const minimumAmountOut = returnAmount
219
+ .mul(10000 - slippage * 100)
220
+ .div(10000);
221
+
222
+ const iBalancerV2Vault = new ethers.utils.Interface(IBalancerV2Vault.abi);
223
+
224
+ if (swaps.length === 1) {
225
+ const swap = swaps[0];
226
+ //do single swap
227
+ const swapTx = iBalancerV2Vault.encodeFunctionData("swap", [
228
+ [
229
+ swap.poolId,
230
+ SwapTypes.SwapExactIn,
231
+ tokenAddresses[swap.assetInIndex],
232
+ tokenAddresses[swap.assetOutIndex],
233
+ swap.amount,
234
+ swap.userData
235
+ ],
236
+ [pool.address, false, pool.address, false],
237
+ minimumAmountOut,
238
+ Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes from the current Unix time
239
+ ]);
240
+ return swapTx;
241
+ } else {
242
+ // Limits:
243
+ // +ve means max to send
244
+ // -ve mean min to receive
245
+ // For a multihop the intermediate tokens should be 0
246
+ const limits: string[] = [];
247
+ tokenAddresses.forEach((token, i) => {
248
+ if (token.toLowerCase() === assetFrom.toLowerCase()) {
249
+ limits[i] = amountIn.toString();
250
+ } else if (token.toLowerCase() === assetTo.toLowerCase()) {
251
+ limits[i] = minimumAmountOut.mul(-1).toString();
252
+ } else {
253
+ limits[i] = "0";
254
+ }
255
+ });
256
+ const swapTx = iBalancerV2Vault.encodeFunctionData("batchSwap", [
257
+ SwapTypes.SwapExactIn,
258
+ swaps,
259
+ tokenAddresses,
260
+ [pool.address, false, pool.address, false],
261
+ limits,
262
+ Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes from the current Unix time
263
+ ]);
264
+ return swapTx;
265
+ }
266
+ }
267
+
268
+ async getBalancerJoinPoolTx(
269
+ pool: Pool,
270
+ balancerPoolId: string,
271
+ assets: string[],
272
+ amountsIn: string[] | ethers.BigNumber[]
273
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
274
+ ): Promise<any> {
275
+ const iBalancerV2Vault = new ethers.utils.Interface(IBalancerV2Vault.abi);
276
+ const txData = [
277
+ balancerPoolId,
278
+ pool.address,
279
+ pool.address,
280
+ [
281
+ assets,
282
+ amountsIn,
283
+ ethers.utils.defaultAbiCoder.encode(
284
+ ["uint256", "uint256[]", "uint256"],
285
+ [1, amountsIn, 0]
286
+ ),
287
+ false
288
+ ]
289
+ ];
290
+ const joinPoolTx = iBalancerV2Vault.encodeFunctionData("joinPool", txData);
291
+ return joinPoolTx;
292
+ }
293
+
294
+ async getBalancerExitPoolTx(
295
+ pool: Pool,
296
+ balancerPoolId: string,
297
+ assets: string[],
298
+ amount: string | ethers.BigNumber
299
+
300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
+ ): Promise<any> {
302
+ const minimumAmountOut = new Array(assets.length).fill(0);
303
+ const iBalancerV2Vault = new ethers.utils.Interface(IBalancerV2Vault.abi);
304
+ const txData = [
305
+ balancerPoolId,
306
+ pool.address,
307
+ pool.address,
308
+ [
309
+ assets,
310
+ minimumAmountOut,
311
+ ethers.utils.defaultAbiCoder.encode(
312
+ ["uint256", "uint256"],
313
+ [1, amount]
314
+ ),
315
+ false
316
+ ]
317
+ ];
318
+ const exitPoolTx = iBalancerV2Vault.encodeFunctionData("exitPool", txData);
319
+ return exitPoolTx;
320
+ }
174
321
  }
@@ -0,0 +1,115 @@
1
+ {
2
+ "1": [
3
+ {
4
+ "label": "BAL",
5
+ "distributor": "0xd2EB7Bd802A7CA68d9AcD209bEc4E664A9abDD7b",
6
+ "token": "0xba100000625a3754423978a60c9317c58a424e3d",
7
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current.json",
8
+ "weekStart": 52
9
+ },
10
+ {
11
+ "label": "UNN",
12
+ "distributor": "0xBfbd6e720ffdF0497f69C95E5C03a4861C65A6E7",
13
+ "token": "0x226f7b842E0F0120b7E194D05432b3fd14773a9D",
14
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-union.json",
15
+ "weekStart": 1
16
+ },
17
+ {
18
+ "label": "BANK",
19
+ "distributor": "0x9d20FE66eC5Dd15a3D3213556534C77cA20318bE",
20
+ "token": "0x2d94AA3e47d9D5024503Ca8491fcE9A2fB4DA198",
21
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-bankless.json",
22
+ "weekStart": 1
23
+ },
24
+ {
25
+ "label": "NOTE",
26
+ "distributor": "0xF1C2dD9bD863f2444086B739383F1043E6b88F69",
27
+ "token": "0xcfeaead4947f0705a14ec42ac3d44129e1ef3ed5",
28
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-note.json",
29
+ "weekStart": 1
30
+ },
31
+ {
32
+ "label": "NEXO",
33
+ "distributor": "0x0000000000000000000000000000000000000000",
34
+ "token": "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206",
35
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-nexo.json",
36
+ "weekStart": 1
37
+ }
38
+ ],
39
+ "42": [
40
+ {
41
+ "label": "BAL",
42
+ "distributor": "0x95FaE1C936B4Cd6c5099d7A705D792ee6aC9FEc3",
43
+ "token": "0x41286Bb1D3E870f3F750eB7E1C25d7E48c8A1Ac7",
44
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/sample-tree/reports-kovan/_current.json",
45
+ "weekStart": 52
46
+ },
47
+ {
48
+ "label": "DAI",
49
+ "distributor": "0x95FaE1C936B4Cd6c5099d7A705D792ee6aC9FEc3",
50
+ "token": "0x04DF6e4121c27713ED22341E7c7Df330F56f289B",
51
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/sample-tree/reports-kovan/_current-dai.json",
52
+ "weekStart": 1
53
+ }
54
+ ],
55
+ "137": [
56
+ {
57
+ "label": "BAL",
58
+ "distributor": "0xd2EB7Bd802A7CA68d9AcD209bEc4E664A9abDD7b",
59
+ "token": "0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3",
60
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-polygon.json",
61
+ "weekStart": 1
62
+ },
63
+ {
64
+ "label": "WMATIC",
65
+ "distributor": "0x087A7AFB6975A2837453BE685EB6272576c0bC06",
66
+ "token": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
67
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-wmatic-polygon.json",
68
+ "weekStart": 1
69
+ },
70
+ {
71
+ "label": "WMATIC",
72
+ "distributor": "0xBd44C01EC7d623372B4572247afB6231eDD8486F",
73
+ "token": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
74
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-wmatic-polygon.json",
75
+ "weekStart": 4
76
+ },
77
+ {
78
+ "label": "WMATIC",
79
+ "distributor": "0x632208491602Dd205da8Cb9C0BA98620fc19854A",
80
+ "token": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
81
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-wmatic-polygon.json",
82
+ "weekStart": 5
83
+ },
84
+ {
85
+ "label": "TUSD",
86
+ "distributor": "0x09f3010ec0f6d72ef8ff2008f8e756d60482c9a8",
87
+ "token": "0x2e1ad108ff1d8c782fcbbb89aad783ac49586756",
88
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-tusd-polygon.json",
89
+ "weekStart": 1
90
+ }
91
+ ],
92
+ "42161": [
93
+ {
94
+ "label": "BAL",
95
+ "distributor": "0xd2EB7Bd802A7CA68d9AcD209bEc4E664A9abDD7b",
96
+ "token": "0x040d1EdC9569d4Bab2D15287Dc5A4F10F56a56B8",
97
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-arbitrum.json",
98
+ "weekStart": 6
99
+ },
100
+ {
101
+ "label": "MCB",
102
+ "distributor": "0x25c646adf184051b35a405b9aaeba321e8d5342a",
103
+ "token": "0x4e352cf164e64adcbad318c3a1e222e9eba4ce42",
104
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-mcdex-arbitrum.json",
105
+ "weekStart": 4
106
+ },
107
+ {
108
+ "label": "PICKLE",
109
+ "distributor": "0xf02CeB58d549E4b403e8F85FBBaEe4c5dfA47c01",
110
+ "token": "0x965772e0e9c84b6f359c8597c891108dcf1c5b1a",
111
+ "manifest": "https://raw.githubusercontent.com/balancer-labs/bal-mining-scripts/master/reports/_current-pickle-arbitrum.json",
112
+ "weekStart": 4
113
+ }
114
+ ]
115
+ }
@@ -0,0 +1,262 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import axios from "axios";
3
+ import { chunk, flatten } from "lodash";
4
+ import merkleOrchardAbi from "../../abi/IBalancerMerkleOrchard.json";
5
+ import { ethers, Wallet } from "ethers";
6
+
7
+ import { getAddress } from "@ethersproject/address";
8
+
9
+ import { multicall } from "../../utils/contract";
10
+ import { bnum, loadTree, scale } from "../../utils";
11
+
12
+ import { ipfsService } from "./ipfs.service";
13
+
14
+ import {
15
+ ClaimProofTuple,
16
+ ClaimStatus,
17
+ ComputeClaimProofPayload,
18
+ MultiTokenPendingClaims,
19
+ Report,
20
+ Snapshot,
21
+ TokenClaimInfo
22
+ } from "./types";
23
+
24
+ import { Network } from "../../types";
25
+ import { networkChainIdMap, stakingAddress } from "../../config";
26
+ import { soliditySha3 } from "web3-utils";
27
+ import { Dapp } from "../..";
28
+
29
+ export class ClaimService {
30
+ network: Network;
31
+ signer: ethers.Wallet;
32
+ public constructor(network: Network, signer: Wallet) {
33
+ this.network = network;
34
+ this.signer = signer;
35
+ }
36
+ public async getMultiTokensPendingClaims(
37
+ account: string
38
+ ): Promise<MultiTokenPendingClaims[]> {
39
+ const tokenClaimsInfo = await this.getTokenClaimsInfo();
40
+ if (tokenClaimsInfo != null) {
41
+ const multiTokenPendingClaims = await Promise.all(
42
+ tokenClaimsInfo.map(tokenClaimInfo =>
43
+ this.getTokenPendingClaims(tokenClaimInfo, getAddress(account))
44
+ )
45
+ );
46
+
47
+ const multiTokenPendingClaimsWithRewards = multiTokenPendingClaims.filter(
48
+ pendingClaim => Number(pendingClaim.availableToClaim) > 0
49
+ );
50
+
51
+ return multiTokenPendingClaimsWithRewards;
52
+ }
53
+ return [];
54
+ }
55
+
56
+ public async getTokenPendingClaims(
57
+ tokenClaimInfo: TokenClaimInfo,
58
+ account: string
59
+ ): Promise<MultiTokenPendingClaims> {
60
+ const snapshot = await this.getSnapshot(tokenClaimInfo.manifest);
61
+ const weekStart = tokenClaimInfo.weekStart;
62
+ const claimStatus = await this.getClaimStatus(
63
+ Object.keys(snapshot).length,
64
+ account,
65
+ tokenClaimInfo
66
+ );
67
+
68
+ const pendingWeeks = claimStatus
69
+ .map((status, i) => [i + weekStart, status])
70
+ .filter(([, status]) => !status)
71
+ .map(([i]) => i) as number[];
72
+
73
+ const reports = await this.getReports(snapshot, pendingWeeks);
74
+
75
+ const claims = Object.entries(reports)
76
+ .filter((report: Report) => report[1][account])
77
+ .map((report: Report) => {
78
+ return {
79
+ id: report[0],
80
+ amount: report[1][account]
81
+ };
82
+ });
83
+
84
+ //console.log("claims", claims);
85
+
86
+ const availableToClaim = claims
87
+ .map(claim => parseFloat(claim.amount))
88
+ .reduce((total, amount) => total.plus(amount), bnum(0))
89
+ .toString();
90
+
91
+ return {
92
+ claims,
93
+ reports,
94
+ tokenClaimInfo,
95
+ availableToClaim
96
+ };
97
+ }
98
+
99
+ public async multiTokenClaimRewards(
100
+ account: string,
101
+ multiTokenPendingClaims: MultiTokenPendingClaims[]
102
+ ): Promise<any> {
103
+ try {
104
+ const multiTokenClaims = await Promise.all(
105
+ multiTokenPendingClaims.map((tokenPendingClaims, tokenIndex) =>
106
+ this.computeClaimProofs(
107
+ tokenPendingClaims,
108
+ getAddress(account),
109
+ tokenIndex
110
+ )
111
+ )
112
+ );
113
+
114
+ return flatten(multiTokenClaims);
115
+ } catch (e) {
116
+ console.log("[Claim] Claim Rewards Error:", e);
117
+ return Promise.reject(e);
118
+ }
119
+ }
120
+
121
+ private async computeClaimProofs(
122
+ tokenPendingClaims: MultiTokenPendingClaims,
123
+ account: string,
124
+ tokenIndex: number
125
+ ): Promise<Promise<ClaimProofTuple[]>> {
126
+ return Promise.all(
127
+ tokenPendingClaims.claims.map(claim => {
128
+ const payload: ComputeClaimProofPayload = {
129
+ account,
130
+ distributor: tokenPendingClaims.tokenClaimInfo.distributor,
131
+ tokenIndex,
132
+ decimals: tokenPendingClaims.tokenClaimInfo.decimals,
133
+ // objects must be cloned
134
+ report: { ...tokenPendingClaims.reports[claim.id] },
135
+ claim: { ...claim }
136
+ };
137
+
138
+ return this.computeClaimProof(payload);
139
+ })
140
+ );
141
+ }
142
+
143
+ private computeClaimProof(
144
+ payload: ComputeClaimProofPayload
145
+ ): ClaimProofTuple {
146
+ const {
147
+ report,
148
+ account,
149
+ claim,
150
+ distributor,
151
+ tokenIndex,
152
+ decimals
153
+ } = payload;
154
+
155
+ const claimAmount = claim.amount;
156
+ const merkleTree = loadTree(report, decimals);
157
+
158
+ const scaledBalance = scale(claimAmount, decimals).toString(10);
159
+
160
+ const proof = merkleTree.getHexProof(
161
+ soliditySha3(
162
+ { t: "address", v: account },
163
+ { t: "uint", v: scaledBalance }
164
+ )
165
+ );
166
+ return [
167
+ parseInt(claim.id),
168
+ scaledBalance,
169
+ distributor,
170
+ tokenIndex,
171
+ proof
172
+ ] as ClaimProofTuple;
173
+ }
174
+
175
+ private async getTokenClaimsInfo() {
176
+ let tokenClaims;
177
+ try {
178
+ const multiTokenClaim = await axios.get(
179
+ "https://raw.githubusercontent.com/balancer-labs/frontend-v2/develop/src/services/claim/MultiTokenClaim.json"
180
+ );
181
+ const chainId = networkChainIdMap[this.network];
182
+ tokenClaims = multiTokenClaim.data[chainId];
183
+ } catch (e) {
184
+ console.log("balancer multi token info error", e);
185
+ }
186
+
187
+ if (tokenClaims != null) {
188
+ return (tokenClaims as TokenClaimInfo[]).map(tokenClaim => ({
189
+ ...tokenClaim,
190
+ token: getAddress(tokenClaim.token),
191
+ decimals: 18
192
+ }));
193
+ }
194
+
195
+ return null;
196
+ }
197
+
198
+ private async getSnapshot(manifest: string) {
199
+ try {
200
+ const response = await axios.get<Snapshot>(manifest);
201
+ return response.data || {};
202
+ } catch (error) {
203
+ return {};
204
+ }
205
+ }
206
+
207
+ private async getClaimStatus(
208
+ totalWeeks: number,
209
+ account: string,
210
+ tokenClaimInfo: TokenClaimInfo
211
+ ): Promise<ClaimStatus[]> {
212
+ const { token, distributor, weekStart } = tokenClaimInfo;
213
+
214
+ const claimStatusCalls = Array.from({ length: totalWeeks }).map((_, i) => [
215
+ stakingAddress[this.network][Dapp.BALANCER],
216
+ "isClaimed",
217
+ [token, distributor, weekStart + i, account]
218
+ ]);
219
+
220
+ const rootCalls = Array.from({ length: totalWeeks }).map((_, i) => [
221
+ stakingAddress[this.network][Dapp.BALANCER],
222
+ "getDistributionRoot",
223
+ [token, distributor, weekStart + i]
224
+ ]);
225
+
226
+ try {
227
+ const result = (await multicall<boolean | string>(
228
+ this.network,
229
+ this.signer,
230
+ merkleOrchardAbi.abi,
231
+ [...claimStatusCalls, ...rootCalls],
232
+ {},
233
+ true
234
+ )) as (boolean | string)[];
235
+
236
+ if (result.length > 0) {
237
+ const chunks = chunk(flatten(result), totalWeeks);
238
+
239
+ const claimedResult = chunks[0] as boolean[];
240
+ const distributionRootResult = chunks[1] as string[];
241
+
242
+ return claimedResult.filter(
243
+ (_, index) =>
244
+ distributionRootResult[index] !== ethers.constants.HashZero
245
+ );
246
+ }
247
+ } catch (e) {
248
+ console.log("[Claim] Claim Status Error:", e);
249
+ }
250
+
251
+ return [];
252
+ }
253
+
254
+ private async getReports(snapshot: Snapshot, weeks: number[]) {
255
+ const reports = await Promise.all<Report>(
256
+ weeks
257
+ .filter(week => snapshot[week] != null)
258
+ .map(week => ipfsService.get(snapshot[week]))
259
+ );
260
+ return Object.fromEntries(reports.map((report, i) => [weeks[i], report]));
261
+ }
262
+ }
@@ -0,0 +1,32 @@
1
+ // Shamelessly adapted from OpenZeppelin-contracts test utils
2
+ import { soliditySha3 } from "web3-utils";
3
+ import { loadTree, scale } from "../../utils";
4
+
5
+ import { ComputeClaimProofPayload } from "./types";
6
+
7
+ export class ClaimWorker {
8
+ public calcClaimProof(payload: ComputeClaimProofPayload): any {
9
+ const {
10
+ report,
11
+ account,
12
+ claim,
13
+ distributor,
14
+ tokenIndex,
15
+ decimals
16
+ } = payload;
17
+
18
+ const claimAmount = claim.amount;
19
+ const merkleTree = loadTree(report, decimals);
20
+
21
+ const scaledBalance = scale(claimAmount, decimals).toString(10);
22
+
23
+ const proof = merkleTree.getHexProof(
24
+ soliditySha3(
25
+ { t: "address", v: account },
26
+ { t: "uint", v: scaledBalance }
27
+ )
28
+ );
29
+
30
+ return [parseInt(claim.id), scaledBalance, distributor, tokenIndex, proof];
31
+ }
32
+ }
@@ -0,0 +1,12 @@
1
+ import axios from "axios";
2
+
3
+ export default class IpfsService {
4
+ async get<T>(hash: string, protocol = "ipfs"): Promise<T> {
5
+ const { data } = await axios.get(
6
+ `https://cloudflare-ipfs.com/${protocol}/${hash}`
7
+ );
8
+ return data;
9
+ }
10
+ }
11
+
12
+ export const ipfsService = new IpfsService();
@@ -0,0 +1,66 @@
1
+ export interface Claim {
2
+ id: string;
3
+ amount: string;
4
+ }
5
+
6
+ export type Snapshot = Record<number, string>;
7
+
8
+ export type TokenClaimInfo = {
9
+ label: string;
10
+ distributor: string;
11
+ token: string;
12
+ decimals: number;
13
+ manifest: string;
14
+ weekStart: number;
15
+ };
16
+
17
+ export type MultiTokenPendingClaims = {
18
+ claims: Claim[];
19
+ reports: Report;
20
+ tokenClaimInfo: TokenClaimInfo;
21
+ availableToClaim: string;
22
+ };
23
+
24
+ export type ClaimStatus = boolean;
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ export type Report = Record<string, any>;
28
+
29
+ export type MultiTokenCurrentRewardsEstimateResponse = {
30
+ success: boolean;
31
+ result: {
32
+ current_timestamp: string;
33
+ "liquidity-providers": Array<{
34
+ snapshot_timestamp: string;
35
+ address: string;
36
+ token_address: string;
37
+ chain_id: number;
38
+ current_estimate: string;
39
+ velocity: string;
40
+ week: number;
41
+ }>;
42
+ };
43
+ };
44
+
45
+ export type MultiTokenCurrentRewardsEstimate = {
46
+ rewards: string;
47
+ velocity: string;
48
+ token: string;
49
+ };
50
+
51
+ export type ClaimProofTuple = [number, string, string, number, string[]]; // claimId, claimAmount, distributor, tokenIndex, proof
52
+
53
+ export type ComputeClaimProofPayload = {
54
+ report: Report;
55
+ account: string;
56
+ claim: Claim;
57
+ distributor: string;
58
+ tokenIndex: number;
59
+ decimals: number;
60
+ };
61
+
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ export type ClaimWorkerMessage<P = any> = {
64
+ type: "computeClaimProof";
65
+ payload: P;
66
+ };