@dhedge/v2-sdk 2.0.0 → 2.0.1

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.
@@ -1,11 +1,14 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
3
  import BigNumber from "bignumber.js";
4
- import { Pool, ethers } from "../..";
4
+ import { Pool, SDKOptions, ethers } from "../..";
5
5
  import DelayedOrderAbi from "../../abi/flatmoney/DelayedOrder.json";
6
6
  import IOrderExecutionModuleAbi from "../../abi/flatmoney/v2/IOrderExecutionModule.json";
7
7
  import { flatMoneyContractAddresses } from "../../config";
8
- import { getPoolTxOrGasEstimate } from "../../utils/contract";
8
+ import {
9
+ getPoolTxOrGasEstimate,
10
+ isSdkOptionsBoolean
11
+ } from "../../utils/contract";
9
12
  import { getStableDepositQuote, getStableWithdrawQuote } from "./stableModule";
10
13
  import { getKeeperFee } from "./keeperFee";
11
14
 
@@ -55,7 +58,9 @@ export async function mintUnitViaFlatMoney(
55
58
  slippage: number, // 0.5 means 0.5%
56
59
  maxKeeperFeeInUsd: number | null,
57
60
  options: any = null,
58
- estimateGas = false
61
+ sdkOptions: SDKOptions = {
62
+ estimateGas: false
63
+ }
59
64
  ): Promise<any> {
60
65
  const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
61
66
  if (!flatMoneyContracts) {
@@ -85,7 +90,7 @@ export async function mintUnitViaFlatMoney(
85
90
  const tx = await getPoolTxOrGasEstimate(
86
91
  pool,
87
92
  [flatMoneyContracts.DelayedOrder, mintUnitTxData, options],
88
- estimateGas
93
+ sdkOptions
89
94
  );
90
95
  return tx;
91
96
  }
@@ -96,7 +101,9 @@ export async function redeemUnitViaFlatMoney(
96
101
  slippage: number, // 0.5 means 0.5%
97
102
  maxKeeperFeeInUsd: number | null,
98
103
  options: any = null,
99
- estimateGas = false
104
+ sdkOptions: SDKOptions = {
105
+ estimateGas: false
106
+ }
100
107
  ): Promise<any> {
101
108
  const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
102
109
  if (!flatMoneyContracts) {
@@ -118,7 +125,7 @@ export async function redeemUnitViaFlatMoney(
118
125
  const tx = await getPoolTxOrGasEstimate(
119
126
  pool,
120
127
  [flatMoneyContracts.DelayedOrder, redeemUnitTxData, options],
121
- estimateGas
128
+ sdkOptions
122
129
  );
123
130
  return tx;
124
131
  }
@@ -126,7 +133,9 @@ export async function redeemUnitViaFlatMoney(
126
133
  export async function cancelOrderViaFlatMoney(
127
134
  pool: Pool,
128
135
  options: any = null,
129
- estimateGas = false
136
+ sdkOptions: SDKOptions = {
137
+ estimateGas: false
138
+ }
130
139
  ): Promise<any> {
131
140
  const flatMoneyContracts = flatMoneyContractAddresses[pool.network];
132
141
  if (!flatMoneyContracts) {
@@ -140,18 +149,15 @@ export async function cancelOrderViaFlatMoney(
140
149
  toAddress = flatMoneyContracts.OrderExecution;
141
150
  cancelOrderTxData = await getCancelExistingOrderTxDataForV2(pool.address);
142
151
  }
143
- // use trader address to cancel order
144
- if (estimateGas) {
145
- return pool.signer.estimateGas({
146
- to: toAddress,
147
- data: cancelOrderTxData
148
- });
149
- } else {
150
- const tx = await pool.signer.sendTransaction({
151
- to: toAddress,
152
- data: cancelOrderTxData,
153
- ...options
154
- });
155
- return tx;
156
- }
152
+ const tx = await getPoolTxOrGasEstimate(
153
+ pool,
154
+ [toAddress, cancelOrderTxData, options],
155
+ {
156
+ ...(isSdkOptionsBoolean(sdkOptions)
157
+ ? { estimateGas: sdkOptions }
158
+ : sdkOptions),
159
+ useTraderAddressAsFrom: true
160
+ }
161
+ );
162
+ return tx;
157
163
  }
@@ -4,7 +4,7 @@ import { ApiError, ethers } from "../..";
4
4
  import { networkChainIdMap } from "../../config";
5
5
  import { Pool } from "../../entities";
6
6
 
7
- const odosBaseUrl = "https://api.odos.xyz/sor";
7
+ export const odosBaseUrl = "https://api.odos.xyz/sor";
8
8
 
9
9
  export async function getOdosSwapTxData(
10
10
  pool: Pool,
@@ -0,0 +1,209 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { Dapp, ethers, Pool } from "../..";
3
+ import { networkChainIdMap, routerAddress } from "../../config";
4
+ import IEasySwapperV2 from "../../abi/IEasySwapperV2.json";
5
+ import BigNumber from "bignumber.js";
6
+ import AssetHandlerAbi from "../../abi/AssetHandler.json";
7
+ import IERC20Abi from "../../abi/IERC20.json";
8
+ import {
9
+ LOW_USD_VALUE_FOR_WITHDRAWAL,
10
+ SLIPPAGE_FOR_LOW_VALUE_SWAP
11
+ } from "./easySwapper";
12
+ import { retry } from "./retry";
13
+ import { getSwapDataViaOdos, SWAPPER_ADDERSS } from "./swapData";
14
+
15
+ export interface TrackedAsset {
16
+ token: string;
17
+ balance: ethers.BigNumber;
18
+ }
19
+
20
+ const getSwapWithdrawData = async (
21
+ pool: Pool,
22
+ trackedAssets: TrackedAsset[],
23
+ receiveToken: string,
24
+ slippage: number,
25
+ swapDestMinDestAmount: BigNumber
26
+ ) => {
27
+ const srcData = [];
28
+ const routerKey = ethers.utils.formatBytes32String("ODOS_V2");
29
+ // const destData
30
+ for (const { token, balance } of trackedAssets) {
31
+ if (token.toLowerCase() === receiveToken.toLowerCase()) {
32
+ continue;
33
+ }
34
+ const swapData = await retry({
35
+ fn: () => {
36
+ return getSwapDataViaOdos({
37
+ srcAsset: token,
38
+ srcAmount: balance.toString(),
39
+ dstAsset: receiveToken,
40
+ chainId: networkChainIdMap[pool.network],
41
+ from: SWAPPER_ADDERSS,
42
+ receiver: SWAPPER_ADDERSS,
43
+ slippage
44
+ });
45
+ },
46
+ delayMs: 1500,
47
+ maxRetries: 7
48
+ });
49
+ srcData.push({
50
+ token,
51
+ amount: balance,
52
+ aggregatorData: { routerKey, swapData }
53
+ });
54
+ }
55
+ return {
56
+ srcData,
57
+ destData: {
58
+ destToken: receiveToken,
59
+ minDestAmount: swapDestMinDestAmount.toString()
60
+ }
61
+ };
62
+ };
63
+ export const createCompleteWithdrawalTxArguments = async (
64
+ pool: Pool,
65
+ receiveToken: string,
66
+ slippage: number
67
+ ): Promise<any> => {
68
+ const easySwapper = new ethers.Contract(
69
+ routerAddress[pool.network][Dapp.TOROS] as string,
70
+ IEasySwapperV2,
71
+ pool.signer
72
+ );
73
+ const trackedAssets: TrackedAsset[] = await easySwapper.getTrackedAssets(
74
+ pool.address
75
+ );
76
+
77
+ if (
78
+ trackedAssets.length === 0 ||
79
+ trackedAssets.every(
80
+ ({ token }) => token.toLowerCase() === receiveToken.toLowerCase()
81
+ )
82
+ ) {
83
+ // just do simple complete withdraw
84
+ return {
85
+ isSwapNeeded: false,
86
+ swapData: null,
87
+ estimatedMinReceiveAmount: null
88
+ };
89
+ }
90
+
91
+ const swapTrackedAssets = trackedAssets.filter(
92
+ ({ token }) => token.toLowerCase() !== receiveToken.toLowerCase()
93
+ );
94
+
95
+ const assetHandlerAddress = await pool.factory.callStatic.getAssetHandler();
96
+ const assetHandler = new ethers.Contract(
97
+ assetHandlerAddress,
98
+ AssetHandlerAbi.abi,
99
+ pool.signer
100
+ );
101
+ const receiveTokenPriceD18 = new BigNumber(
102
+ (await assetHandler.getUSDPrice(receiveToken)).toString()
103
+ );
104
+ const receiveTokenErc20 = await new ethers.Contract(
105
+ receiveToken,
106
+ IERC20Abi.abi,
107
+ pool.signer
108
+ );
109
+ const receiveTokenDecimals = await receiveTokenErc20.decimals();
110
+ // swap dest minDestAmount
111
+ const tAssetInfos = await Promise.all(
112
+ swapTrackedAssets.map(async swapTAsset => {
113
+ const swapTAssetPriceD18 = new BigNumber(
114
+ (await assetHandler.getUSDPrice(swapTAsset.token)).toString()
115
+ );
116
+ const swapTAssetTokenErc20 = await new ethers.Contract(
117
+ swapTAsset.token,
118
+ IERC20Abi.abi,
119
+ pool.signer
120
+ );
121
+ const swapTAssetDecimals = await swapTAssetTokenErc20.decimals();
122
+ const tokenBalanceBN = new BigNumber(swapTAsset.balance.toString());
123
+ const estimatedValueToSwapD0 = tokenBalanceBN
124
+ .times(swapTAssetPriceD18)
125
+ .div(10 ** 18)
126
+ .div(10 ** Number(swapTAssetDecimals.toString()));
127
+
128
+ // --- caution: if the estimated value to swap is less than the low USD value for withdrawal, use a higher slippage
129
+ const adjustedSlippage = estimatedValueToSwapD0.lte(
130
+ LOW_USD_VALUE_FOR_WITHDRAWAL
131
+ )
132
+ ? SLIPPAGE_FOR_LOW_VALUE_SWAP
133
+ : slippage;
134
+ // -----
135
+
136
+ const estimatedMinReceiveAmount = tokenBalanceBN
137
+ .times(swapTAssetPriceD18)
138
+ .div(receiveTokenPriceD18)
139
+ .div(10 ** Number(swapTAssetDecimals.toString()))
140
+ .times(10 ** Number(receiveTokenDecimals.toString()))
141
+ .times(1 - adjustedSlippage / 10000) // slippage is in basis points, so divide by 10000
142
+ .decimalPlaces(0, BigNumber.ROUND_DOWN);
143
+
144
+ return {
145
+ token: swapTAsset.token,
146
+ balance: swapTAsset.balance,
147
+ estimatedMinReceiveAmount
148
+ };
149
+ })
150
+ );
151
+ const swapDestMinDestAmount = tAssetInfos.reduce(
152
+ (acc, { estimatedMinReceiveAmount }) => acc.plus(estimatedMinReceiveAmount),
153
+ new BigNumber(0)
154
+ );
155
+
156
+ const withdrawalVaultAddress = await easySwapper.withdrawalContracts(
157
+ pool.address
158
+ );
159
+ const balanceOfReceiveToken = await receiveTokenErc20.balanceOf(
160
+ withdrawalVaultAddress
161
+ );
162
+
163
+ // complete withdraw _expectedDestTokenAmount
164
+ const estimatedMinReceiveAmount = swapDestMinDestAmount.plus(
165
+ balanceOfReceiveToken.toString()
166
+ );
167
+
168
+ const swapData = await getSwapWithdrawData(
169
+ pool,
170
+ swapTrackedAssets,
171
+ receiveToken,
172
+ slippage,
173
+ estimatedMinReceiveAmount
174
+ );
175
+
176
+ return {
177
+ isSwapNeeded: true,
178
+ swapData,
179
+ estimatedMinReceiveAmount: estimatedMinReceiveAmount.toFixed(0)
180
+ };
181
+ };
182
+
183
+ export const getCompleteWithdrawalTxData = async (
184
+ pool: Pool,
185
+ receiveToken: string,
186
+ slippage: number,
187
+ useOnChainSwap: boolean
188
+ ): Promise<string> => {
189
+ const completeWithdrawTxArguments = await createCompleteWithdrawalTxArguments(
190
+ pool,
191
+ receiveToken,
192
+ slippage
193
+ );
194
+
195
+ const isSwapNeeded = completeWithdrawTxArguments.isSwapNeeded;
196
+ const isOffchainSwap = !useOnChainSwap && isSwapNeeded;
197
+ const iEasySwapperV2 = new ethers.utils.Interface(IEasySwapperV2);
198
+ if (isOffchainSwap) {
199
+ return iEasySwapperV2.encodeFunctionData(
200
+ "completeWithdrawal(((address,uint256,(bytes32,bytes))[],(address,uint256)),uint256)",
201
+ [
202
+ completeWithdrawTxArguments.swapData,
203
+ completeWithdrawTxArguments.estimatedMinReceiveAmount
204
+ ]
205
+ );
206
+ } else {
207
+ return iEasySwapperV2.encodeFunctionData("completeWithdrawal()");
208
+ }
209
+ };
@@ -1,12 +1,15 @@
1
1
  import { ethers } from "ethers";
2
2
  import { Dapp, Pool } from "../..";
3
3
 
4
- import IDhedgeEasySwapper from "../../abi/IDhedgeEasySwapper.json";
4
+ import IEasySwapperV2 from "../../abi/IEasySwapperV2.json";
5
5
  import { routerAddress } from "../../config";
6
- import { getChainlinkPriceInUsd } from "../chainLink/price";
6
+
7
7
  import { isPool, loadPool } from "./pool";
8
- import { getOneInchSwapTxData } from "../oneInch";
9
- import { wait } from "../../test/utils/testingHelper";
8
+
9
+ import { getInitWithdrawalTxData } from "./initWithdrawal";
10
+
11
+ export const LOW_USD_VALUE_FOR_WITHDRAWAL = 1; // 1 USD minimum value for withdrawal
12
+ export const SLIPPAGE_FOR_LOW_VALUE_SWAP = 500;
10
13
 
11
14
  export async function getPoolDepositAsset(
12
15
  pool: Pool,
@@ -39,32 +42,13 @@ export async function getEasySwapperDepositQuote(
39
42
  ): Promise<ethers.BigNumber> {
40
43
  const easySwapper = new ethers.Contract(
41
44
  routerAddress[pool.network][Dapp.TOROS] as string,
42
- IDhedgeEasySwapper,
45
+ IEasySwapperV2,
43
46
  pool.signer
44
47
  );
45
48
 
46
49
  return await easySwapper.depositQuote(torosAsset, investAsset, amountIn);
47
50
  }
48
51
 
49
- export async function getEasySwapperWithdrawalQuote(
50
- pool: Pool,
51
- torosAsset: string,
52
- investAsset: string,
53
- amountIn: ethers.BigNumber
54
- ): Promise<ethers.BigNumber> {
55
- const [torosTokenPrice, assetPrice, assetDecimals] = await Promise.all([
56
- getTorosPoolTokenPrice(pool, torosAsset),
57
- getChainlinkPriceInUsd(pool, investAsset),
58
- pool.utils.getDecimals(investAsset)
59
- ]);
60
-
61
- return amountIn
62
- .mul(torosTokenPrice)
63
- .div(assetPrice)
64
- .div(1e10)
65
- .div(10 ** (18 - assetDecimals));
66
- }
67
-
68
52
  export async function getEasySwapperTxData(
69
53
  pool: Pool,
70
54
  assetFrom: string,
@@ -77,13 +61,15 @@ export async function getEasySwapperTxData(
77
61
  const [torosAsset, investAsset] = isWithdrawal
78
62
  ? [assetFrom, assetTo]
79
63
  : [assetTo, assetFrom];
80
- const iDhedgeEasySwapper = new ethers.utils.Interface(IDhedgeEasySwapper);
64
+ const iEasySwapperV2 = new ethers.utils.Interface(IEasySwapperV2);
81
65
  if (isWithdrawal) {
82
- return iDhedgeEasySwapper.encodeFunctionData("initWithdrawal", [
66
+ return getInitWithdrawalTxData(
67
+ pool,
83
68
  torosAsset,
84
- amountIn,
85
- slippage * 100
86
- ]);
69
+ amountIn.toString(),
70
+ slippage * 100,
71
+ false
72
+ );
87
73
  } else {
88
74
  const depositAsset = await getPoolDepositAsset(
89
75
  pool,
@@ -99,7 +85,7 @@ export async function getEasySwapperTxData(
99
85
  investAsset,
100
86
  amountIn
101
87
  );
102
- return iDhedgeEasySwapper.encodeFunctionData("depositWithCustomCooldown", [
88
+ return iEasySwapperV2.encodeFunctionData("depositWithCustomCooldown", [
103
89
  torosAsset,
104
90
  depositAsset,
105
91
  amountIn,
@@ -107,57 +93,3 @@ export async function getEasySwapperTxData(
107
93
  ]);
108
94
  }
109
95
  }
110
-
111
- export async function getCompleteWithdrawalTxData(
112
- pool: Pool,
113
- destToken: string,
114
- slippage: number
115
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
- ): Promise<string> {
117
- const easySwapper = new ethers.Contract(
118
- routerAddress[pool.network][Dapp.TOROS] as string,
119
- IDhedgeEasySwapper,
120
- pool.signer
121
- );
122
- const trackedAssets: {
123
- token: string;
124
- balance: ethers.BigNumber;
125
- }[] = await easySwapper.getTrackedAssets(pool.address);
126
- const trackedAssetsExcludingDestToken = trackedAssets.filter(
127
- ({ token }) => token.toLowerCase() !== destToken.toLowerCase()
128
- );
129
-
130
- const srcData = [];
131
- let minDestAmount = ethers.BigNumber.from(0);
132
- for (const { token, balance } of trackedAssetsExcludingDestToken) {
133
- const { swapTxData, dstAmount } = await getOneInchSwapTxData(
134
- pool,
135
- token,
136
- destToken,
137
- balance,
138
- slippage,
139
- true
140
- );
141
- srcData.push({
142
- token,
143
- amount: balance,
144
- aggregatorData: {
145
- routerKey: ethers.utils.formatBytes32String("ONE_INCH"),
146
- swapData: swapTxData
147
- }
148
- });
149
- minDestAmount = minDestAmount.add(dstAmount);
150
- await wait(2);
151
- }
152
-
153
- return easySwapper.interface.encodeFunctionData("completeWithdrawal", [
154
- {
155
- srcData,
156
- destData: {
157
- destToken,
158
- minDestAmount
159
- }
160
- },
161
- minDestAmount.mul(10000 - slippage * 100).div(10000)
162
- ]);
163
- }
@@ -0,0 +1,166 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { Dapp, ethers, Pool } from "../..";
3
+ import { networkChainIdMap, routerAddress } from "../../config";
4
+ import { retry } from "./retry";
5
+ import AaveLendingPoolAssetGuardAbi from "../../abi/IAaveLendingPoolAssetGuard.json";
6
+ import IEasySwapperV2 from "../../abi/IEasySwapperV2.json";
7
+ import { loadPool } from "./pool";
8
+ import { getSwapDataViaOdos, SWAPPER_ADDERSS } from "./swapData";
9
+ const AAVE_WITHDRAW_ONCHAIN_SWAP_SLIPPAGE = 150; // 1.5% slippage for onchain swap in Aave withdrawal
10
+
11
+ const getCalculateSwapDataParams = async (
12
+ pool: Pool,
13
+ torosAsset: string,
14
+ amountIn: string,
15
+ slippage: number
16
+ ): Promise<{
17
+ offchainSwapNeeded: boolean;
18
+ swapDataParams: any;
19
+ }> => {
20
+ const aaveAssetGuardAddress = await pool.factory.getAssetGuard(
21
+ routerAddress[pool.network][Dapp.AAVEV3] as string
22
+ );
23
+
24
+ const aaveAssetGuard = new ethers.Contract(
25
+ aaveAssetGuardAddress,
26
+ AaveLendingPoolAssetGuardAbi,
27
+ pool.signer
28
+ );
29
+ const swapDataParams = await aaveAssetGuard.callStatic.calculateSwapDataParams(
30
+ torosAsset,
31
+ amountIn,
32
+ slippage
33
+ );
34
+
35
+ return {
36
+ offchainSwapNeeded: swapDataParams.srcData.length !== 0,
37
+ swapDataParams
38
+ };
39
+ };
40
+
41
+ const getAaveAssetWithdrawData = async (
42
+ pool: Pool,
43
+ swapDataParams: any,
44
+ slippage: number
45
+ ) => {
46
+ const { srcData, dstData } = swapDataParams;
47
+
48
+ const srcDataToEncode: unknown[] = [];
49
+ const routerKey = ethers.utils.formatBytes32String("ODOS_V2");
50
+ for (const { asset, amount } of srcData) {
51
+ const swapData = await retry({
52
+ fn: () => {
53
+ return getSwapDataViaOdos({
54
+ srcAsset: asset,
55
+ srcAmount: amount.toString(),
56
+ dstAsset: dstData.asset,
57
+ chainId: networkChainIdMap[pool.network],
58
+ from: SWAPPER_ADDERSS,
59
+ receiver: SWAPPER_ADDERSS,
60
+ slippage
61
+ });
62
+ },
63
+ delayMs: 1500,
64
+ maxRetries: 7
65
+ });
66
+ srcDataToEncode.push([asset, amount, [routerKey, swapData]]);
67
+ }
68
+ const coder = ethers.utils.defaultAbiCoder;
69
+
70
+ const encodedSrcData = coder.encode(
71
+ ["tuple(address, uint256, tuple(bytes32, bytes))[]"],
72
+ [srcDataToEncode]
73
+ );
74
+ const withdrawData = coder.encode(
75
+ ["tuple(bytes, tuple(address, uint256), uint256)"],
76
+ [[encodedSrcData, [dstData.asset, dstData.amount], slippage]]
77
+ );
78
+
79
+ return withdrawData;
80
+ };
81
+
82
+ export const createWithdrawTxArguments = async (
83
+ pool: Pool,
84
+ torosAsset: string,
85
+ amountIn: string,
86
+ slippage: number,
87
+ useOnChainSwap: boolean
88
+ ): Promise<any> => {
89
+ const torosPool = await loadPool(pool, torosAsset);
90
+ const supportedAssets: {
91
+ asset: string;
92
+ }[] = await torosPool.managerLogic.getSupportedAssets();
93
+
94
+ if (useOnChainSwap) {
95
+ return supportedAssets.map(assetObj => {
96
+ return {
97
+ supportedAsset: assetObj.asset,
98
+ withdrawData: "0x",
99
+ slippageTolerance: AAVE_WITHDRAW_ONCHAIN_SWAP_SLIPPAGE
100
+ };
101
+ });
102
+ }
103
+
104
+ // for off-chain swap
105
+ const aaveLendingPoolAddress = routerAddress[pool.network][
106
+ Dapp.AAVEV3
107
+ ] as string;
108
+ return Promise.all(
109
+ supportedAssets.map(async assetObj => {
110
+ if (
111
+ assetObj.asset.toLowerCase() === aaveLendingPoolAddress.toLowerCase()
112
+ ) {
113
+ const {
114
+ offchainSwapNeeded,
115
+ swapDataParams
116
+ } = await getCalculateSwapDataParams(
117
+ pool,
118
+ torosAsset,
119
+ amountIn,
120
+ slippage
121
+ );
122
+ if (offchainSwapNeeded) {
123
+ const withdrawData = await getAaveAssetWithdrawData(
124
+ pool,
125
+ swapDataParams,
126
+ slippage
127
+ );
128
+
129
+ return {
130
+ supportedAsset: assetObj.asset,
131
+ withdrawData,
132
+ slippageTolerance: slippage
133
+ };
134
+ }
135
+ }
136
+
137
+ return {
138
+ supportedAsset: assetObj.asset,
139
+ withdrawData: "0x",
140
+ slippageTolerance: slippage
141
+ };
142
+ })
143
+ );
144
+ };
145
+
146
+ export const getInitWithdrawalTxData = async (
147
+ pool: Pool,
148
+ torosAsset: string,
149
+ amountIn: string,
150
+ slippage: number,
151
+ useOnChainSwap: boolean
152
+ ): Promise<string> => {
153
+ const complexAssetsData = await createWithdrawTxArguments(
154
+ pool,
155
+ torosAsset,
156
+ amountIn,
157
+ slippage,
158
+ useOnChainSwap
159
+ );
160
+ const iEasySwapperV2 = new ethers.utils.Interface(IEasySwapperV2);
161
+ return iEasySwapperV2.encodeFunctionData("initWithdrawal", [
162
+ torosAsset,
163
+ amountIn,
164
+ complexAssetsData
165
+ ]);
166
+ };
@@ -0,0 +1,28 @@
1
+ export async function retry<T>({
2
+ fn,
3
+ maxRetries = 3,
4
+ delayMs = 1000
5
+ }: {
6
+ fn: () => Promise<T>;
7
+ maxRetries?: number;
8
+ delayMs?: number;
9
+ }): Promise<T> {
10
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
11
+ try {
12
+ return await fn();
13
+ } catch (err) {
14
+ if (attempt === maxRetries) {
15
+ throw new Error(`Retry failed after ${maxRetries} attempts: ${err}`);
16
+ }
17
+
18
+ console.warn(
19
+ `Retry ${attempt}/${maxRetries} failed, retrying in ${delayMs}ms`,
20
+ err
21
+ );
22
+ await new Promise(res => setTimeout(res, delayMs));
23
+ }
24
+ }
25
+
26
+ // Should never reach here
27
+ throw new Error("Unexpected retry failure");
28
+ }