@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.
- package/dist/entities/pool.d.ts +91 -91
- package/dist/services/flatmoney/stableLp.d.ts +4 -4
- package/dist/services/odos/index.d.ts +1 -0
- package/dist/services/toros/completeWithdrawal.d.ts +7 -0
- package/dist/services/toros/easySwapper.d.ts +2 -2
- package/dist/services/toros/initWithdrawal.d.ts +3 -0
- package/dist/services/toros/retry.d.ts +5 -0
- package/dist/services/toros/swapData.d.ts +11 -0
- package/dist/types.d.ts +5 -0
- package/dist/utils/contract.d.ts +3 -2
- package/dist/v2-sdk.cjs.development.js +3905 -1015
- 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 +3905 -1015
- package/dist/v2-sdk.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/IAaveLendingPoolAssetGuard.json +645 -0
- package/src/abi/IEasySwapperV2.json +1507 -0
- package/src/entities/pool.ts +231 -141
- package/src/services/flatmoney/stableLp.ts +27 -21
- package/src/services/odos/index.ts +1 -1
- package/src/services/toros/completeWithdrawal.ts +209 -0
- package/src/services/toros/easySwapper.ts +16 -84
- package/src/services/toros/initWithdrawal.ts +166 -0
- package/src/services/toros/retry.ts +28 -0
- package/src/services/toros/swapData.ts +70 -0
- package/src/test/constants.ts +1 -1
- package/src/test/toros.test.ts +10 -8
- package/src/types.ts +8 -0
- package/src/utils/contract.ts +71 -43
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
4
|
+
import IEasySwapperV2 from "../../abi/IEasySwapperV2.json";
|
|
5
5
|
import { routerAddress } from "../../config";
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
import { isPool, loadPool } from "./pool";
|
|
8
|
-
|
|
9
|
-
import {
|
|
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
|
-
|
|
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
|
|
64
|
+
const iEasySwapperV2 = new ethers.utils.Interface(IEasySwapperV2);
|
|
81
65
|
if (isWithdrawal) {
|
|
82
|
-
return
|
|
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
|
|
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
|
+
}
|