@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.
- package/README.md +69 -3
- package/dist/config.d.ts +4 -0
- package/dist/entities/dhedge.d.ts +2 -1
- package/dist/entities/pool.d.ts +94 -13
- package/dist/entities/utils.d.ts +15 -0
- package/dist/services/claim-balancer/claim.service.d.ts +21 -0
- package/dist/services/claim-balancer/claim.worker.d.ts +4 -0
- package/dist/services/claim-balancer/ipfs.service.d.ts +4 -0
- package/dist/services/claim-balancer/types.d.ts +54 -0
- package/dist/test/constants.d.ts +12 -0
- package/dist/types.d.ts +17 -3
- package/dist/utils/contract.d.ts +14 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/merkle.d.ts +22 -0
- package/dist/v2-sdk.cjs.development.js +5066 -669
- package/dist/v2-sdk.cjs.development.js.map +1 -1
- package/dist/v2-sdk.cjs.production.min.js +1 -1
- package/dist/v2-sdk.cjs.production.min.js.map +1 -1
- package/dist/v2-sdk.esm.js +5065 -670
- package/dist/v2-sdk.esm.js.map +1 -1
- package/package.json +11 -2
- package/src/abi/IAaveIncentivesController.json +50 -0
- package/src/abi/IBalancerMerkleOrchard.json +353 -0
- package/src/abi/IBalancertV2Vault.json +938 -0
- package/src/abi/ILendingPool.json +807 -0
- package/src/config.ts +26 -3
- package/src/entities/dhedge.ts +7 -3
- package/src/entities/pool.ts +360 -35
- package/src/entities/utils.ts +201 -1
- package/src/services/claim-balancer/MultiTokenClaim.json +115 -0
- package/src/services/claim-balancer/claim.service.ts +324 -0
- package/src/services/claim-balancer/claim.worker.ts +32 -0
- package/src/services/claim-balancer/ipfs.service.ts +12 -0
- package/src/services/claim-balancer/types.ts +66 -0
- package/src/test/aave.test.ts +73 -0
- package/src/test/balancer.test.ts +109 -0
- package/src/test/constants.ts +13 -0
- package/src/test/dhedge.test.ts +20 -8
- package/src/test/oneInch.test.ts +56 -0
- package/src/test/pool.test.ts +11 -168
- package/src/test/sushi.test.ts +173 -0
- package/src/test/utils.test.ts +43 -17
- package/src/types.ts +18 -3
- package/src/utils/contract.ts +95 -0
- package/src/utils/index.ts +38 -0
- package/src/utils/merkle.ts +172 -0
package/src/entities/utils.ts
CHANGED
|
@@ -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
|
|
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();
|