@dhedge/v2-sdk 1.0.0 → 1.2.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 (46) hide show
  1. package/README.md +69 -3
  2. package/dist/config.d.ts +4 -0
  3. package/dist/entities/dhedge.d.ts +2 -1
  4. package/dist/entities/pool.d.ts +94 -13
  5. package/dist/entities/utils.d.ts +15 -0
  6. package/dist/services/claim-balancer/claim.service.d.ts +21 -0
  7. package/dist/services/claim-balancer/claim.worker.d.ts +4 -0
  8. package/dist/services/claim-balancer/ipfs.service.d.ts +4 -0
  9. package/dist/services/claim-balancer/types.d.ts +54 -0
  10. package/dist/test/constants.d.ts +12 -0
  11. package/dist/types.d.ts +17 -3
  12. package/dist/utils/contract.d.ts +14 -0
  13. package/dist/utils/index.d.ts +7 -0
  14. package/dist/utils/merkle.d.ts +22 -0
  15. package/dist/v2-sdk.cjs.development.js +5066 -669
  16. package/dist/v2-sdk.cjs.development.js.map +1 -1
  17. package/dist/v2-sdk.cjs.production.min.js +1 -1
  18. package/dist/v2-sdk.cjs.production.min.js.map +1 -1
  19. package/dist/v2-sdk.esm.js +5065 -670
  20. package/dist/v2-sdk.esm.js.map +1 -1
  21. package/package.json +11 -2
  22. package/src/abi/IAaveIncentivesController.json +50 -0
  23. package/src/abi/IBalancerMerkleOrchard.json +353 -0
  24. package/src/abi/IBalancertV2Vault.json +938 -0
  25. package/src/abi/ILendingPool.json +807 -0
  26. package/src/config.ts +26 -3
  27. package/src/entities/dhedge.ts +7 -3
  28. package/src/entities/pool.ts +360 -35
  29. package/src/entities/utils.ts +201 -1
  30. package/src/services/claim-balancer/MultiTokenClaim.json +115 -0
  31. package/src/services/claim-balancer/claim.service.ts +324 -0
  32. package/src/services/claim-balancer/claim.worker.ts +32 -0
  33. package/src/services/claim-balancer/ipfs.service.ts +12 -0
  34. package/src/services/claim-balancer/types.ts +66 -0
  35. package/src/test/aave.test.ts +73 -0
  36. package/src/test/balancer.test.ts +109 -0
  37. package/src/test/constants.ts +13 -0
  38. package/src/test/dhedge.test.ts +20 -8
  39. package/src/test/oneInch.test.ts +56 -0
  40. package/src/test/pool.test.ts +11 -168
  41. package/src/test/sushi.test.ts +173 -0
  42. package/src/test/utils.test.ts +43 -17
  43. package/src/types.ts +18 -3
  44. package/src/utils/contract.ts +95 -0
  45. package/src/utils/index.ts +38 -0
  46. package/src/utils/merkle.ts +172 -0
@@ -1,11 +1,29 @@
1
1
  import { Contract, ethers, Wallet } from "ethers";
2
+ import {
3
+ Token,
4
+ TokenAmount,
5
+ Pair,
6
+ TradeType,
7
+ Route,
8
+ Trade,
9
+ Percent
10
+ } from "@sushiswap/sdk";
11
+ import { SOR, SwapTypes } from "@balancer-labs/sor";
12
+ import { BaseProvider } from "@ethersproject/providers";
2
13
 
3
14
  import IERC20 from "../abi/IERC20.json";
4
15
  import IMiniChefV2 from "../abi/IMiniChefV2.json";
5
16
  import UniswapV2Factory from "../abi/IUniswapV2Factory.json";
6
17
  import UniswapV2Pair from "../abi/IUniswapV2Pair.json";
7
- import { dappFactoryAddress, stakingAddress } from "../config";
18
+ import IBalancerV2Vault from "../abi/IBalancertV2Vault.json";
19
+ import {
20
+ balancerSubgraph,
21
+ dappFactoryAddress,
22
+ networkChainIdMap,
23
+ stakingAddress
24
+ } from "../config";
8
25
  import { Dapp, Network, Reserves } from "../types";
26
+ import { Pool } from ".";
9
27
 
10
28
  export class Utils {
11
29
  network: Network;
@@ -106,4 +124,186 @@ export class Utils {
106
124
  const balance = await iERC20.balanceOf(owner);
107
125
  return balance;
108
126
  }
127
+
128
+ /**
129
+ * Return the minimum amount out for a trade between two assets
130
+ * given the trade amount and slippage
131
+ * @param {Dapp} dApp DApp like Uniswap or Sushiswap
132
+ * @param {string} assetFrom Asset to trade from
133
+ * @param {string} assetTo Asset to trade into
134
+ * @param {string | ethers.BigNumber} amountIn Trade amount
135
+ * @param { number} slippage Maximum slippage allowed
136
+ * @returns {Promise<ethers.BigNumber>} Reserves of the assets in BigNumber
137
+ */
138
+ async getMinAmountOut(
139
+ dapp: Dapp,
140
+ assetFrom: string,
141
+ assetTo: string,
142
+ amountIn: string | ethers.BigNumber,
143
+ slippage: number
144
+ ): Promise<ethers.BigNumber> {
145
+ const assetFromChecked = ethers.utils.getAddress(assetFrom);
146
+ const assetToChecked = ethers.utils.getAddress(assetTo);
147
+ const reserves = await this.getLpReserves(
148
+ dapp,
149
+ assetFromChecked,
150
+ assetToChecked
151
+ );
152
+ const tokenA = new Token(
153
+ networkChainIdMap[this.network],
154
+ assetFromChecked,
155
+ 18
156
+ );
157
+ const tokenB = new Token(
158
+ networkChainIdMap[this.network],
159
+ assetToChecked,
160
+ 18
161
+ );
162
+ const pair = new Pair(
163
+ new TokenAmount(tokenA, reserves.assetA.toString()),
164
+ new TokenAmount(tokenB, reserves.assetB.toString())
165
+ );
166
+ const route = new Route([pair], tokenA, tokenB);
167
+
168
+ const trade = new Trade(
169
+ route,
170
+ new TokenAmount(tokenA, amountIn.toString()),
171
+ TradeType.EXACT_INPUT
172
+ );
173
+ return ethers.BigNumber.from(
174
+ trade
175
+ .minimumAmountOut(new Percent((slippage * 100).toFixed(), "10000"))
176
+ .raw.toString()
177
+ );
178
+ }
179
+
180
+ async getBalancerSwapTx(
181
+ pool: Pool,
182
+ assetFrom: string,
183
+ assetTo: string,
184
+ amountIn: ethers.BigNumber | string,
185
+ slippage: number
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
+ ): Promise<any> {
188
+ const sor = new SOR(
189
+ this.signer.provider as BaseProvider,
190
+ networkChainIdMap[this.network],
191
+ balancerSubgraph[this.network]
192
+ );
193
+ // isFetched will be true on success
194
+ const isFetched = await sor.fetchPools();
195
+
196
+ if (!isFetched) throw new Error("Error fetching balancer pools");
197
+
198
+ const swapType = SwapTypes.SwapExactIn;
199
+ const { swaps, tokenAddresses, returnAmount } = await sor.getSwaps(
200
+ assetFrom,
201
+ assetTo,
202
+ swapType,
203
+ ethers.BigNumber.from(amountIn)
204
+ );
205
+
206
+ const minimumAmountOut = returnAmount
207
+ .mul(10000 - slippage * 100)
208
+ .div(10000);
209
+
210
+ const iBalancerV2Vault = new ethers.utils.Interface(IBalancerV2Vault.abi);
211
+
212
+ if (swaps.length === 1) {
213
+ const swap = swaps[0];
214
+ //do single swap
215
+ const swapTx = iBalancerV2Vault.encodeFunctionData("swap", [
216
+ [
217
+ swap.poolId,
218
+ SwapTypes.SwapExactIn,
219
+ tokenAddresses[swap.assetInIndex],
220
+ tokenAddresses[swap.assetOutIndex],
221
+ swap.amount,
222
+ swap.userData
223
+ ],
224
+ [pool.address, false, pool.address, false],
225
+ minimumAmountOut,
226
+ Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes from the current Unix time
227
+ ]);
228
+ return swapTx;
229
+ } else {
230
+ // Limits:
231
+ // +ve means max to send
232
+ // -ve mean min to receive
233
+ // For a multihop the intermediate tokens should be 0
234
+ const limits: string[] = [];
235
+ tokenAddresses.forEach((token, i) => {
236
+ if (token.toLowerCase() === assetFrom.toLowerCase()) {
237
+ limits[i] = amountIn.toString();
238
+ } else if (token.toLowerCase() === assetTo.toLowerCase()) {
239
+ limits[i] = minimumAmountOut.mul(-1).toString();
240
+ } else {
241
+ limits[i] = "0";
242
+ }
243
+ });
244
+ const swapTx = iBalancerV2Vault.encodeFunctionData("batchSwap", [
245
+ SwapTypes.SwapExactIn,
246
+ swaps,
247
+ tokenAddresses,
248
+ [pool.address, false, pool.address, false],
249
+ limits,
250
+ Math.floor(Date.now() / 1000) + 60 * 20 // 20 minutes from the current Unix time
251
+ ]);
252
+ return swapTx;
253
+ }
254
+ }
255
+
256
+ async getBalancerJoinPoolTx(
257
+ pool: Pool,
258
+ balancerPoolId: string,
259
+ assets: string[],
260
+ amountsIn: string[] | ethers.BigNumber[]
261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
262
+ ): Promise<any> {
263
+ const iBalancerV2Vault = new ethers.utils.Interface(IBalancerV2Vault.abi);
264
+ const txData = [
265
+ balancerPoolId,
266
+ pool.address,
267
+ pool.address,
268
+ [
269
+ assets,
270
+ amountsIn,
271
+ ethers.utils.defaultAbiCoder.encode(
272
+ ["uint256", "uint256[]", "uint256"],
273
+ [1, amountsIn, 0]
274
+ ),
275
+ false
276
+ ]
277
+ ];
278
+ const joinPoolTx = iBalancerV2Vault.encodeFunctionData("joinPool", txData);
279
+ return joinPoolTx;
280
+ }
281
+
282
+ async getBalancerExitPoolTx(
283
+ pool: Pool,
284
+ balancerPoolId: string,
285
+ assets: string[],
286
+ amount: string | ethers.BigNumber
287
+
288
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
+ ): Promise<any> {
290
+ const minimumAmountOut = new Array(assets.length).fill(0);
291
+ const iBalancerV2Vault = new ethers.utils.Interface(IBalancerV2Vault.abi);
292
+ const txData = [
293
+ balancerPoolId,
294
+ pool.address,
295
+ pool.address,
296
+ [
297
+ assets,
298
+ minimumAmountOut,
299
+ ethers.utils.defaultAbiCoder.encode(
300
+ ["uint256", "uint256"],
301
+ [1, amount]
302
+ ),
303
+ false
304
+ ]
305
+ ];
306
+ const exitPoolTx = iBalancerV2Vault.encodeFunctionData("exitPool", txData);
307
+ return exitPoolTx;
308
+ }
109
309
  }
@@ -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,324 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import axios from "axios";
3
+ import { chunk, flatten, groupBy } 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 MultiTokenClaim from "./MultiTokenClaim.json";
15
+
16
+ import {
17
+ ClaimProofTuple,
18
+ ClaimStatus,
19
+ ComputeClaimProofPayload,
20
+ MultiTokenCurrentRewardsEstimate,
21
+ MultiTokenCurrentRewardsEstimateResponse,
22
+ MultiTokenPendingClaims,
23
+ Report,
24
+ Snapshot,
25
+ TokenClaimInfo
26
+ } from "./types";
27
+
28
+ import { Network } from "../../types";
29
+ import { stakingAddress } from "../../config";
30
+ import { soliditySha3 } from "web3-utils";
31
+ import { Dapp } from "../..";
32
+
33
+ export class ClaimService {
34
+ network: Network;
35
+ signer: ethers.Wallet;
36
+ public constructor(network: Network, signer: Wallet) {
37
+ this.network = network;
38
+ this.signer = signer;
39
+ }
40
+ public async getMultiTokensPendingClaims(
41
+ account: string
42
+ ): Promise<MultiTokenPendingClaims[]> {
43
+ const tokenClaimsInfo = this.getTokenClaimsInfo();
44
+ if (tokenClaimsInfo != null) {
45
+ const multiTokenPendingClaims = await Promise.all(
46
+ tokenClaimsInfo.map(tokenClaimInfo =>
47
+ this.getTokenPendingClaims(tokenClaimInfo, getAddress(account))
48
+ )
49
+ );
50
+
51
+ const multiTokenPendingClaimsWithRewards = multiTokenPendingClaims.filter(
52
+ pendingClaim => Number(pendingClaim.availableToClaim) > 0
53
+ );
54
+
55
+ return multiTokenPendingClaimsWithRewards;
56
+ }
57
+ return [];
58
+ }
59
+
60
+ public async getTokenPendingClaims(
61
+ tokenClaimInfo: TokenClaimInfo,
62
+ account: string
63
+ ): Promise<MultiTokenPendingClaims> {
64
+ const snapshot = await this.getSnapshot(tokenClaimInfo.manifest);
65
+ const weekStart = tokenClaimInfo.weekStart;
66
+ const claimStatus = await this.getClaimStatus(
67
+ Object.keys(snapshot).length,
68
+ account,
69
+ tokenClaimInfo
70
+ );
71
+
72
+ const pendingWeeks = claimStatus
73
+ .map((status, i) => [i + weekStart, status])
74
+ .filter(([, status]) => !status)
75
+ .map(([i]) => i) as number[];
76
+
77
+ const reports = await this.getReports(snapshot, pendingWeeks);
78
+
79
+ const claims = Object.entries(reports)
80
+ .filter((report: Report) => report[1][account])
81
+ .map((report: Report) => {
82
+ return {
83
+ id: report[0],
84
+ amount: report[1][account]
85
+ };
86
+ });
87
+
88
+ //console.log("claims", claims);
89
+
90
+ const availableToClaim = claims
91
+ .map(claim => parseFloat(claim.amount))
92
+ .reduce((total, amount) => total.plus(amount), bnum(0))
93
+ .toString();
94
+
95
+ return {
96
+ claims,
97
+ reports,
98
+ tokenClaimInfo,
99
+ availableToClaim
100
+ };
101
+ }
102
+
103
+ public async getMultiTokensCurrentRewardsEstimate(
104
+ account: string
105
+ ): Promise<{
106
+ data: MultiTokenCurrentRewardsEstimate[];
107
+ timestamp: string | null;
108
+ }> {
109
+ try {
110
+ const response = await axios.get<
111
+ MultiTokenCurrentRewardsEstimateResponse
112
+ >(
113
+ `https://api.balancer.finance/liquidity-mining/v1/liquidity-provider-multitoken/${account}`
114
+ );
115
+ if (response.data.success) {
116
+ const multiTokenLiquidityProviders = response.data.result[
117
+ "liquidity-providers"
118
+ ]
119
+ .filter(incentive => incentive.chain_id === 137)
120
+ .map(incentive => ({
121
+ ...incentive,
122
+ token_address: getAddress(incentive.token_address)
123
+ }));
124
+
125
+ const multiTokenCurrentRewardsEstimate: MultiTokenCurrentRewardsEstimate[] = [];
126
+
127
+ const multiTokenLiquidityProvidersByToken = Object.entries(
128
+ groupBy(multiTokenLiquidityProviders, "token_address")
129
+ );
130
+
131
+ for (const [
132
+ token,
133
+ liquidityProvider
134
+ ] of multiTokenLiquidityProvidersByToken) {
135
+ const rewards = liquidityProvider
136
+ .reduce(
137
+ (total, { current_estimate }) => total.plus(current_estimate),
138
+ bnum(0)
139
+ )
140
+ .toString();
141
+
142
+ const velocity =
143
+ liquidityProvider
144
+ .find(liquidityProvider => Number(liquidityProvider.velocity) > 0)
145
+ ?.velocity.toString() ?? "0";
146
+
147
+ if (Number(rewards) > 0) {
148
+ multiTokenCurrentRewardsEstimate.push({
149
+ rewards,
150
+ velocity,
151
+ token: getAddress(token)
152
+ });
153
+ }
154
+ }
155
+
156
+ return {
157
+ data: multiTokenCurrentRewardsEstimate,
158
+ timestamp: response.data.result.current_timestamp
159
+ };
160
+ }
161
+ } catch (e) {
162
+ console.log("[Claim] Current Rewards Estimate Error", e);
163
+ }
164
+ return {
165
+ data: [],
166
+ timestamp: null
167
+ };
168
+ }
169
+
170
+ public async multiTokenClaimRewards(
171
+ account: string,
172
+ multiTokenPendingClaims: MultiTokenPendingClaims[]
173
+ ): Promise<any> {
174
+ try {
175
+ const multiTokenClaims = await Promise.all(
176
+ multiTokenPendingClaims.map((tokenPendingClaims, tokenIndex) =>
177
+ this.computeClaimProofs(
178
+ tokenPendingClaims,
179
+ getAddress(account),
180
+ tokenIndex
181
+ )
182
+ )
183
+ );
184
+
185
+ return flatten(multiTokenClaims);
186
+ } catch (e) {
187
+ console.log("[Claim] Claim Rewards Error:", e);
188
+ return Promise.reject(e);
189
+ }
190
+ }
191
+
192
+ private async computeClaimProofs(
193
+ tokenPendingClaims: MultiTokenPendingClaims,
194
+ account: string,
195
+ tokenIndex: number
196
+ ): Promise<Promise<ClaimProofTuple[]>> {
197
+ return Promise.all(
198
+ tokenPendingClaims.claims.map(claim => {
199
+ const payload: ComputeClaimProofPayload = {
200
+ account,
201
+ distributor: tokenPendingClaims.tokenClaimInfo.distributor,
202
+ tokenIndex,
203
+ decimals: tokenPendingClaims.tokenClaimInfo.decimals,
204
+ // objects must be cloned
205
+ report: { ...tokenPendingClaims.reports[claim.id] },
206
+ claim: { ...claim }
207
+ };
208
+
209
+ return this.computeClaimProof(payload);
210
+ })
211
+ );
212
+ }
213
+
214
+ private computeClaimProof(
215
+ payload: ComputeClaimProofPayload
216
+ ): ClaimProofTuple {
217
+ const {
218
+ report,
219
+ account,
220
+ claim,
221
+ distributor,
222
+ tokenIndex,
223
+ decimals
224
+ } = payload;
225
+
226
+ const claimAmount = claim.amount;
227
+ const merkleTree = loadTree(report, decimals);
228
+
229
+ const scaledBalance = scale(claimAmount, decimals).toString(10);
230
+
231
+ const proof = merkleTree.getHexProof(
232
+ soliditySha3(
233
+ { t: "address", v: account },
234
+ { t: "uint", v: scaledBalance }
235
+ )
236
+ );
237
+ return [
238
+ parseInt(claim.id),
239
+ scaledBalance,
240
+ distributor,
241
+ tokenIndex,
242
+ proof
243
+ ] as ClaimProofTuple;
244
+ }
245
+
246
+ private getTokenClaimsInfo() {
247
+ const tokenClaims = MultiTokenClaim["137"];
248
+
249
+ if (tokenClaims != null) {
250
+ return (tokenClaims as TokenClaimInfo[]).map(tokenClaim => ({
251
+ ...tokenClaim,
252
+ token: getAddress(tokenClaim.token),
253
+ decimals: 18
254
+ }));
255
+ }
256
+
257
+ return null;
258
+ }
259
+
260
+ private async getSnapshot(manifest: string) {
261
+ try {
262
+ const response = await axios.get<Snapshot>(manifest);
263
+ return response.data || {};
264
+ } catch (error) {
265
+ return {};
266
+ }
267
+ }
268
+
269
+ private async getClaimStatus(
270
+ totalWeeks: number,
271
+ account: string,
272
+ tokenClaimInfo: TokenClaimInfo
273
+ ): Promise<ClaimStatus[]> {
274
+ const { token, distributor, weekStart } = tokenClaimInfo;
275
+
276
+ const claimStatusCalls = Array.from({ length: totalWeeks }).map((_, i) => [
277
+ stakingAddress[this.network][Dapp.BALANCER],
278
+ "isClaimed",
279
+ [token, distributor, weekStart + i, account]
280
+ ]);
281
+
282
+ const rootCalls = Array.from({ length: totalWeeks }).map((_, i) => [
283
+ stakingAddress[this.network][Dapp.BALANCER],
284
+ "getDistributionRoot",
285
+ [token, distributor, weekStart + i]
286
+ ]);
287
+
288
+ try {
289
+ const result = (await multicall<boolean | string>(
290
+ this.network,
291
+ this.signer,
292
+ merkleOrchardAbi.abi,
293
+ [...claimStatusCalls, ...rootCalls],
294
+ {},
295
+ true
296
+ )) as (boolean | string)[];
297
+
298
+ if (result.length > 0) {
299
+ const chunks = chunk(flatten(result), totalWeeks);
300
+
301
+ const claimedResult = chunks[0] as boolean[];
302
+ const distributionRootResult = chunks[1] as string[];
303
+
304
+ return claimedResult.filter(
305
+ (_, index) =>
306
+ distributionRootResult[index] !== ethers.constants.HashZero
307
+ );
308
+ }
309
+ } catch (e) {
310
+ console.log("[Claim] Claim Status Error:", e);
311
+ }
312
+
313
+ return [];
314
+ }
315
+
316
+ private async getReports(snapshot: Snapshot, weeks: number[]) {
317
+ const reports = await Promise.all<Report>(
318
+ weeks
319
+ .filter(week => snapshot[week] != null)
320
+ .map(week => ipfsService.get(snapshot[week]))
321
+ );
322
+ return Object.fromEntries(reports.map((report, i) => [weeks[i], report]));
323
+ }
324
+ }
@@ -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();