@dhedge/v2-sdk 2.1.7 → 2.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 +400 -53
- package/dist/config.d.ts +14 -2
- package/dist/entities/pool.d.ts +25 -86
- package/dist/entities/utils.d.ts +15 -0
- package/dist/services/cowSwap/index.d.ts +10 -0
- package/dist/services/hyperliquid/index.d.ts +22 -0
- package/dist/services/kyberSwap/index.d.ts +1 -1
- package/dist/services/oneInch/index.d.ts +1 -1
- package/dist/services/toros/easySwapper.d.ts +14 -0
- package/dist/services/toros/swapData.d.ts +5 -5
- package/dist/services/uniswap/V3Liquidity.d.ts +2 -2
- package/dist/services/velodrome/liquidity.d.ts +3 -0
- package/dist/test/constants.d.ts +48 -3
- package/dist/test/utils/testingHelper.d.ts +4 -0
- package/dist/types.d.ts +21 -5
- package/dist/utils/contract.d.ts +20 -0
- package/dist/v2-sdk.cjs.development.js +5193 -6711
- 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 +5198 -6711
- package/dist/v2-sdk.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/PoolFactory.json +414 -204
- package/src/abi/PoolLogic.json +160 -134
- package/src/config.ts +19 -9
- package/src/entities/pool.ts +103 -254
- package/src/entities/utils.ts +15 -0
- package/src/services/cowSwap/index.ts +281 -0
- package/src/services/hyperliquid/index.ts +22 -0
- package/src/services/kyberSwap/index.ts +5 -3
- package/src/services/oneInch/index.ts +5 -4
- package/src/services/toros/completeWithdrawal.ts +57 -40
- package/src/services/toros/easySwapper.ts +15 -1
- package/src/services/toros/initWithdrawal.ts +39 -31
- package/src/services/toros/swapData.ts +45 -131
- package/src/services/uniswap/V3Liquidity.ts +3 -24
- package/src/services/velodrome/liquidity.ts +3 -0
- package/src/test/aave.test.ts +99 -70
- package/src/test/aerodrome.test.ts +53 -24
- package/src/test/aerodromeCL.test.ts +64 -30
- package/src/test/arrakis.test.ts +23 -35
- package/src/test/balancer.test.ts +114 -106
- package/src/test/compoundV3.test.ts +45 -29
- package/src/test/constants.ts +57 -12
- package/src/test/cowswap.test.ts +79 -0
- package/src/test/dhedge.test.ts +45 -12
- package/src/test/flatmoney.test.ts +25 -39
- package/src/test/fluid.test.ts +33 -24
- package/src/test/hyperliquid.onchain.test.ts +131 -0
- package/src/test/kyberSwap.test.ts +37 -16
- package/src/test/lyra.test.ts +159 -150
- package/src/test/odos.test.ts +2 -2
- package/src/test/oneInch.test.ts +36 -22
- package/src/test/pancakeCL.test.ts +72 -31
- package/src/test/pendle.test.ts +94 -54
- package/src/test/{pendleMint.test.ts → pendleMint.onchain.test.ts} +22 -8
- package/src/test/pool.test.ts +152 -95
- package/src/test/toros.onchain.test.ts +92 -0
- package/src/test/toros.test.ts +74 -20
- package/src/test/torosLimitOrder.test.ts +87 -42
- package/src/test/uniswap.test.ts +77 -128
- package/src/test/utils/testingHelper.ts +120 -0
- package/src/test/velodrome.test.ts +126 -92
- package/src/test/velodromeCL.test.ts +43 -31
- package/src/test/velodromeV2.test.ts +153 -95
- package/src/types.ts +22 -6
- package/src/utils/contract.ts +20 -0
- package/dist/services/futures/constants.d.ts +0 -1
- package/dist/services/futures/index.d.ts +0 -2
- package/dist/services/futures/margin.d.ts +0 -2
- package/dist/services/futures/trade.d.ts +0 -3
- package/dist/services/ramses/vesting.d.ts +0 -4
- package/dist/services/uniswap/V3Trade.d.ts +0 -3
- package/dist/test/utils/futures.d.ts +0 -2
- package/src/abi/IRamsesNonfungiblePositionManager.json +0 -486
- package/src/abi/ISynthetiXFuturesMarketV2.json +0 -531
- package/src/abi/ISynthetix.json +0 -139
- package/src/abi/IUniswapV3Quoter.json +0 -195
- package/src/abi/IUniswapV3Router.json +0 -221
- package/src/abi/IXRam.json +0 -99
- package/src/services/futures/constants.ts +0 -1
- package/src/services/futures/index.ts +0 -2
- package/src/services/futures/margin.ts +0 -10
- package/src/services/futures/trade.ts +0 -32
- package/src/services/ramses/vesting.ts +0 -24
- package/src/services/uniswap/V3Trade.ts +0 -46
- package/src/test/futures.test.ts +0 -51
- package/src/test/hyperliquid.test.ts +0 -107
- package/src/test/ramses.test.ts +0 -190
- package/src/test/ramsesCL.test.ts +0 -155
- package/src/test/synthetix.test.ts +0 -36
- package/src/test/utils/futures.ts +0 -14
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { ethers } from "ethers";
|
|
4
|
+
import { ApiError } from "../..";
|
|
5
|
+
import { networkChainIdMap, gpv2SettlementAddress } from "../../config";
|
|
6
|
+
import { Pool } from "../../entities";
|
|
7
|
+
import BN from "bignumber.js";
|
|
8
|
+
|
|
9
|
+
export const KIND_SELL = ethers.utils.keccak256(
|
|
10
|
+
ethers.utils.toUtf8Bytes("sell")
|
|
11
|
+
);
|
|
12
|
+
export const KIND_BUY = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("buy"));
|
|
13
|
+
export const BALANCE_ERC20 = ethers.utils.keccak256(
|
|
14
|
+
ethers.utils.toUtf8Bytes("erc20")
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Matches CowSwapOrderTypeHashLib.ORDER_TYPE_HASH exactly:
|
|
18
|
+
// kind/sellTokenBalance/buyTokenBalance are "string" in the type string
|
|
19
|
+
// but encoded as bytes32 values in abi.encode — must replicate this exactly
|
|
20
|
+
const ORDER_TYPE_HASH = ethers.utils.keccak256(
|
|
21
|
+
ethers.utils.toUtf8Bytes(
|
|
22
|
+
"Order(" +
|
|
23
|
+
"address sellToken," +
|
|
24
|
+
"address buyToken," +
|
|
25
|
+
"address receiver," +
|
|
26
|
+
"uint256 sellAmount," +
|
|
27
|
+
"uint256 buyAmount," +
|
|
28
|
+
"uint32 validTo," +
|
|
29
|
+
"bytes32 appData," +
|
|
30
|
+
"uint256 feeAmount," +
|
|
31
|
+
"string kind," +
|
|
32
|
+
"bool partiallyFillable," +
|
|
33
|
+
"string sellTokenBalance," +
|
|
34
|
+
"string buyTokenBalance" +
|
|
35
|
+
")"
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Matches CowSwapOrderTypeHashLib.EIP712_DOMAIN_TYPEHASH
|
|
40
|
+
const EIP712_DOMAIN_TYPEHASH = ethers.utils.keccak256(
|
|
41
|
+
ethers.utils.toUtf8Bytes(
|
|
42
|
+
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const cowSwapApiNetworkMap: Record<string, string> = {
|
|
47
|
+
polygon: "polygon"
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const GPv2Settlement_ABI = [
|
|
51
|
+
"function setPreSignature(bytes calldata orderUid, bool signed) external"
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Replicates CowSwapOrderTypeHashLib.hashOrder
|
|
55
|
+
function hashOrder(order: {
|
|
56
|
+
sellToken: string;
|
|
57
|
+
buyToken: string;
|
|
58
|
+
receiver: string;
|
|
59
|
+
sellAmount: string;
|
|
60
|
+
buyAmount: string;
|
|
61
|
+
validTo: number;
|
|
62
|
+
appData: string;
|
|
63
|
+
feeAmount: string;
|
|
64
|
+
kind: string;
|
|
65
|
+
partiallyFillable: boolean;
|
|
66
|
+
sellTokenBalance: string;
|
|
67
|
+
buyTokenBalance: string;
|
|
68
|
+
}): string {
|
|
69
|
+
return ethers.utils.keccak256(
|
|
70
|
+
ethers.utils.defaultAbiCoder.encode(
|
|
71
|
+
[
|
|
72
|
+
"bytes32",
|
|
73
|
+
"address",
|
|
74
|
+
"address",
|
|
75
|
+
"address",
|
|
76
|
+
"uint256",
|
|
77
|
+
"uint256",
|
|
78
|
+
"uint32",
|
|
79
|
+
"bytes32",
|
|
80
|
+
"uint256",
|
|
81
|
+
"bytes32",
|
|
82
|
+
"bool",
|
|
83
|
+
"bytes32",
|
|
84
|
+
"bytes32"
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
ORDER_TYPE_HASH,
|
|
88
|
+
order.sellToken,
|
|
89
|
+
order.buyToken,
|
|
90
|
+
order.receiver,
|
|
91
|
+
order.sellAmount,
|
|
92
|
+
order.buyAmount,
|
|
93
|
+
order.validTo,
|
|
94
|
+
order.appData,
|
|
95
|
+
order.feeAmount,
|
|
96
|
+
order.kind,
|
|
97
|
+
order.partiallyFillable,
|
|
98
|
+
order.sellTokenBalance,
|
|
99
|
+
order.buyTokenBalance
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Replicates CowSwapOrderTypeHashLib.domainSeparator
|
|
106
|
+
function computeDomainSeparator(
|
|
107
|
+
name: string,
|
|
108
|
+
version: string,
|
|
109
|
+
chainId: number,
|
|
110
|
+
verifyingContract: string
|
|
111
|
+
): string {
|
|
112
|
+
return ethers.utils.keccak256(
|
|
113
|
+
ethers.utils.defaultAbiCoder.encode(
|
|
114
|
+
["bytes32", "bytes32", "bytes32", "uint256", "address"],
|
|
115
|
+
[
|
|
116
|
+
EIP712_DOMAIN_TYPEHASH,
|
|
117
|
+
ethers.utils.keccak256(ethers.utils.toUtf8Bytes(name)),
|
|
118
|
+
ethers.utils.keccak256(ethers.utils.toUtf8Bytes(version)),
|
|
119
|
+
chainId,
|
|
120
|
+
verifyingContract
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Replicates CowSwapOrderTypeHashLib.getDigest
|
|
127
|
+
function computeOrderDigest(domainSep: string, orderHash: string): string {
|
|
128
|
+
return ethers.utils.keccak256(
|
|
129
|
+
ethers.utils.solidityPack(
|
|
130
|
+
["bytes2", "bytes32", "bytes32"],
|
|
131
|
+
["0x1901", domainSep, orderHash]
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function getCowSwapTxData(
|
|
137
|
+
pool: Pool,
|
|
138
|
+
assetFrom: string,
|
|
139
|
+
assetTo: string,
|
|
140
|
+
amountIn: ethers.BigNumber | string,
|
|
141
|
+
slippage: number,
|
|
142
|
+
kind: "sell" | "buy" = "sell"
|
|
143
|
+
): Promise<{
|
|
144
|
+
encodedTypedData: string;
|
|
145
|
+
preSignTxData: string;
|
|
146
|
+
minAmountOut: string;
|
|
147
|
+
}> {
|
|
148
|
+
const chainId = networkChainIdMap[pool.network];
|
|
149
|
+
const network = cowSwapApiNetworkMap[pool.network];
|
|
150
|
+
if (!network) {
|
|
151
|
+
throw new Error(`CowSwap is not supported on network: ${pool.network}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const gpv2Settlement = gpv2SettlementAddress[pool.network];
|
|
155
|
+
if (!gpv2Settlement) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`GPv2Settlement address not configured for network: ${pool.network}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const baseUrl = `https://api.cow.fi/${network}/api/v1`;
|
|
162
|
+
|
|
163
|
+
// 1. Get quote
|
|
164
|
+
let quoteResponse: any;
|
|
165
|
+
try {
|
|
166
|
+
const quoteResult = await axios.post(`${baseUrl}/quote`, {
|
|
167
|
+
sellToken: assetFrom,
|
|
168
|
+
buyToken: assetTo,
|
|
169
|
+
...(kind === "sell"
|
|
170
|
+
? { sellAmountBeforeFee: amountIn.toString() }
|
|
171
|
+
: { buyAmountAfterFee: amountIn.toString() }),
|
|
172
|
+
from: pool.address,
|
|
173
|
+
receiver: pool.address,
|
|
174
|
+
kind,
|
|
175
|
+
signingScheme: "presign",
|
|
176
|
+
partiallyFillable: false,
|
|
177
|
+
sellTokenBalance: "erc20",
|
|
178
|
+
buyTokenBalance: "erc20"
|
|
179
|
+
});
|
|
180
|
+
quoteResponse = quoteResult.data;
|
|
181
|
+
} catch (e) {
|
|
182
|
+
throw new ApiError("CowSwap quote request failed");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { sellAmount, buyAmount, validTo } = quoteResponse.quote;
|
|
186
|
+
|
|
187
|
+
const buyAmountWithSlippage = ethers.BigNumber.from(
|
|
188
|
+
new BN(buyAmount.toString())
|
|
189
|
+
.times(new BN(1).minus(new BN(slippage).div(100)))
|
|
190
|
+
.toFixed(0, BN.ROUND_DOWN)
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const orderValues = {
|
|
194
|
+
sellToken: assetFrom,
|
|
195
|
+
buyToken: assetTo,
|
|
196
|
+
receiver: pool.address,
|
|
197
|
+
sellAmount,
|
|
198
|
+
buyAmount: buyAmountWithSlippage.toString(),
|
|
199
|
+
validTo,
|
|
200
|
+
appData: ethers.constants.HashZero,
|
|
201
|
+
feeAmount: "0",
|
|
202
|
+
kind: kind === "sell" ? KIND_SELL : KIND_BUY,
|
|
203
|
+
partiallyFillable: false,
|
|
204
|
+
sellTokenBalance: BALANCE_ERC20,
|
|
205
|
+
buyTokenBalance: BALANCE_ERC20
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// 2. Compute digest matching CowSwapOrderTypeHashLib.getDigest exactly
|
|
209
|
+
const domainSep = computeDomainSeparator(
|
|
210
|
+
"Gnosis Protocol",
|
|
211
|
+
"v2",
|
|
212
|
+
chainId,
|
|
213
|
+
gpv2Settlement
|
|
214
|
+
);
|
|
215
|
+
const orderHash = hashOrder(orderValues);
|
|
216
|
+
const orderDigest = computeOrderDigest(domainSep, orderHash);
|
|
217
|
+
|
|
218
|
+
// orderUid = abi.encodePacked(orderDigest, owner, validTo) — 32 + 20 + 4 = 56 bytes
|
|
219
|
+
const orderUid = ethers.utils.solidityPack(
|
|
220
|
+
["bytes32", "address", "uint32"],
|
|
221
|
+
[orderDigest, pool.address, validTo]
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// 3. Submit order to CowSwap API — solver waits for on-chain presign before executing
|
|
225
|
+
try {
|
|
226
|
+
await axios.post(`${baseUrl}/orders`, {
|
|
227
|
+
sellToken: assetFrom,
|
|
228
|
+
buyToken: assetTo,
|
|
229
|
+
receiver: pool.address,
|
|
230
|
+
sellAmount,
|
|
231
|
+
buyAmount: buyAmountWithSlippage.toString(),
|
|
232
|
+
validTo,
|
|
233
|
+
appData: ethers.constants.HashZero,
|
|
234
|
+
feeAmount: "0",
|
|
235
|
+
kind,
|
|
236
|
+
partiallyFillable: false,
|
|
237
|
+
signingScheme: "presign",
|
|
238
|
+
signature: pool.address,
|
|
239
|
+
sellTokenBalance: "erc20",
|
|
240
|
+
buyTokenBalance: "erc20",
|
|
241
|
+
from: pool.address,
|
|
242
|
+
quoteId: quoteResponse.id
|
|
243
|
+
});
|
|
244
|
+
} catch (e) {
|
|
245
|
+
throw new ApiError("CowSwap order submission failed");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 4. Encode CowSwapTypedData for submit() on TypedStructuredDataValidator
|
|
249
|
+
const typedData = {
|
|
250
|
+
domain: {
|
|
251
|
+
name: "Gnosis Protocol",
|
|
252
|
+
version: "v2",
|
|
253
|
+
chainId,
|
|
254
|
+
verifyingContract: gpv2Settlement
|
|
255
|
+
},
|
|
256
|
+
order: orderValues
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const encodedTypedData = ethers.utils.defaultAbiCoder.encode(
|
|
260
|
+
[
|
|
261
|
+
"tuple(" +
|
|
262
|
+
"tuple(string name, string version, uint256 chainId, address verifyingContract) domain," +
|
|
263
|
+
"tuple(address sellToken, address buyToken, address receiver, uint256 sellAmount, uint256 buyAmount, uint32 validTo, bytes32 appData, uint256 feeAmount, bytes32 kind, bool partiallyFillable, bytes32 sellTokenBalance, bytes32 buyTokenBalance) order" +
|
|
264
|
+
")"
|
|
265
|
+
],
|
|
266
|
+
[typedData]
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// 5. Encode setPreSignature() — guard checks isValidatedHash(pool, orderDigest)
|
|
270
|
+
const settlementIface = new ethers.utils.Interface(GPv2Settlement_ABI);
|
|
271
|
+
const preSignTxData = settlementIface.encodeFunctionData("setPreSignature", [
|
|
272
|
+
orderUid,
|
|
273
|
+
true
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
encodedTypedData,
|
|
278
|
+
preSignTxData,
|
|
279
|
+
minAmountOut: buyAmountWithSlippage.toString()
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -24,6 +24,7 @@ import { getPositionSize } from "./positionData";
|
|
|
24
24
|
const depositWallet = new ethers.utils.Interface(ICoreDepositWalletAbi);
|
|
25
25
|
const coreWriter = new ethers.utils.Interface(ICoreWriterAbi);
|
|
26
26
|
|
|
27
|
+
/** Encode `deposit(amount, dexId)` on the Hyperliquid CoreDepositWallet to bridge USDC into HyperCore. */
|
|
27
28
|
export const getDepositHyperliquidTxData = (
|
|
28
29
|
dexId: number,
|
|
29
30
|
amount: ethers.BigNumber | string
|
|
@@ -31,6 +32,11 @@ export const getDepositHyperliquidTxData = (
|
|
|
31
32
|
return depositWallet.encodeFunctionData("deposit", [amount, dexId]);
|
|
32
33
|
};
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Encode a CoreWriter `sendRawAction` for SPOT_SEND that bridges USDC from the
|
|
37
|
+
* HyperCore Spot wallet back to EVM. Amount is supplied in 6 decimals (EVM USDC) and
|
|
38
|
+
* scaled up by 100 for HyperCore's 8-decimal USDC.
|
|
39
|
+
*/
|
|
34
40
|
export const getWithdrawSpotHyperliquidTxData = (
|
|
35
41
|
amount: ethers.BigNumber | string
|
|
36
42
|
): string => {
|
|
@@ -49,6 +55,11 @@ export const getWithdrawSpotHyperliquidTxData = (
|
|
|
49
55
|
);
|
|
50
56
|
return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
|
|
51
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Encode a CoreWriter `sendRawAction` for SEND_ASSET that moves USDC between
|
|
60
|
+
* HyperCore dexes (perp/spot/xyz). Amount is supplied in 6 decimals (EVM USDC) and
|
|
61
|
+
* scaled to HyperCore's 8 decimals.
|
|
62
|
+
*/
|
|
52
63
|
export const getSendAssetHyperliquidTxData = (
|
|
53
64
|
sourceDex: number,
|
|
54
65
|
destinationDex: number,
|
|
@@ -80,6 +91,11 @@ export const getSendAssetHyperliquidTxData = (
|
|
|
80
91
|
return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
|
|
81
92
|
};
|
|
82
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Encode a CoreWriter `sendRawAction` for an IOC limit order on a Hyperliquid asset.
|
|
96
|
+
* Auto-flips side and sets `reduceOnly` when `changeAmount` is negative (perp close).
|
|
97
|
+
* Price is offset by `slippage`% from the current mid in the direction of execution.
|
|
98
|
+
*/
|
|
83
99
|
export const getLimitOrderHyperliquidTxData = async (
|
|
84
100
|
assetId: number,
|
|
85
101
|
isLong: boolean,
|
|
@@ -128,6 +144,12 @@ export const getLimitOrderHyperliquidTxData = async (
|
|
|
128
144
|
return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
|
|
129
145
|
};
|
|
130
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Encode a CoreWriter `sendRawAction` for an IOC limit order that closes
|
|
149
|
+
* `percentageToClose`% of the pool's current Hyperliquid position. Reads the
|
|
150
|
+
* existing position size on-chain to determine the side (buy to close shorts,
|
|
151
|
+
* sell to close longs).
|
|
152
|
+
*/
|
|
131
153
|
export const getClosePositionHyperliquidTxData = async (
|
|
132
154
|
assetId: number,
|
|
133
155
|
percentageToClose: number,
|
|
@@ -9,7 +9,9 @@ export async function getKyberSwapTxData(
|
|
|
9
9
|
tokenIn: string,
|
|
10
10
|
tokenOut: string,
|
|
11
11
|
amountIn: ethers.BigNumber | string,
|
|
12
|
-
slippage: number
|
|
12
|
+
slippage: number,
|
|
13
|
+
sender?: string,
|
|
14
|
+
receiver?: string
|
|
13
15
|
): Promise<{ swapTxData: string; minAmountOut: string }> {
|
|
14
16
|
const params = {
|
|
15
17
|
tokenIn,
|
|
@@ -29,8 +31,8 @@ export async function getKyberSwapTxData(
|
|
|
29
31
|
|
|
30
32
|
const buildParams = {
|
|
31
33
|
routeSummary: quoteResult.data.data.routeSummary,
|
|
32
|
-
sender: pool.address,
|
|
33
|
-
recipient: pool.address,
|
|
34
|
+
sender: sender || pool.address,
|
|
35
|
+
recipient: receiver || pool.address,
|
|
34
36
|
slippageTolerance: slippage * 100 // in basis points
|
|
35
37
|
};
|
|
36
38
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { ApiError, ethers } from "../..";
|
|
4
|
-
import { networkChainIdMap
|
|
4
|
+
import { networkChainIdMap } from "../../config";
|
|
5
5
|
import { Pool } from "../../entities";
|
|
6
6
|
|
|
7
7
|
const oneInchBaseUrl = "https://api.1inch.dev/swap/v6.0/";
|
|
@@ -12,7 +12,8 @@ export async function getOneInchSwapTxData(
|
|
|
12
12
|
assetTo: string,
|
|
13
13
|
amountIn: ethers.BigNumber | string,
|
|
14
14
|
slippage: number,
|
|
15
|
-
|
|
15
|
+
sender?: string,
|
|
16
|
+
receiver?: string
|
|
16
17
|
): Promise<{ swapTxData: string; dstAmount: string }> {
|
|
17
18
|
if (!process.env.ONEINCH_API_KEY)
|
|
18
19
|
throw new Error("ONEINCH_API_KEY not configured in .env file");
|
|
@@ -23,8 +24,8 @@ export async function getOneInchSwapTxData(
|
|
|
23
24
|
src: assetFrom,
|
|
24
25
|
dst: assetTo,
|
|
25
26
|
amount: amountIn.toString(),
|
|
26
|
-
from:
|
|
27
|
-
receiver:
|
|
27
|
+
from: sender || pool.address,
|
|
28
|
+
receiver: receiver || pool.address,
|
|
28
29
|
slippage: slippage,
|
|
29
30
|
disableEstimate: true,
|
|
30
31
|
usePermit2: false
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { Dapp, ethers, Pool } from "../..";
|
|
3
|
-
import {
|
|
3
|
+
import { routerAddress } from "../../config";
|
|
4
4
|
import IEasySwapperV2 from "../../abi/IEasySwapperV2.json";
|
|
5
5
|
import BigNumber from "bignumber.js";
|
|
6
6
|
import AssetHandlerAbi from "../../abi/AssetHandler.json";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SLIPPAGE_FOR_LOW_VALUE_SWAP
|
|
11
11
|
} from "./easySwapper";
|
|
12
12
|
import { retry } from "./retry";
|
|
13
|
-
import {
|
|
13
|
+
import { getSwapData, ROUTER_KEYS } from "./swapData";
|
|
14
14
|
|
|
15
15
|
export interface TrackedAsset {
|
|
16
16
|
token: string;
|
|
@@ -19,46 +19,56 @@ export interface TrackedAsset {
|
|
|
19
19
|
|
|
20
20
|
const getSwapWithdrawData = async (
|
|
21
21
|
pool: Pool,
|
|
22
|
-
trackedAssets:
|
|
22
|
+
trackedAssets: {
|
|
23
|
+
token: string;
|
|
24
|
+
balance: ethers.BigNumber;
|
|
25
|
+
slippage: number;
|
|
26
|
+
}[],
|
|
23
27
|
receiveToken: string,
|
|
24
|
-
slippage: number,
|
|
25
28
|
swapDestMinDestAmount: BigNumber
|
|
26
29
|
) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
for (const routerKeyString of ROUTER_KEYS) {
|
|
31
|
+
try {
|
|
32
|
+
const srcData = [];
|
|
33
|
+
const routerKey = ethers.utils.formatBytes32String(routerKeyString);
|
|
34
|
+
for (const { token, balance, slippage } of trackedAssets) {
|
|
35
|
+
if (token.toLowerCase() === receiveToken.toLowerCase()) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const swapData = await retry({
|
|
39
|
+
fn: () => {
|
|
40
|
+
return getSwapData(
|
|
41
|
+
pool,
|
|
42
|
+
{
|
|
43
|
+
srcAsset: token,
|
|
44
|
+
srcAmount: balance.toString(),
|
|
45
|
+
dstAsset: receiveToken,
|
|
46
|
+
slippage
|
|
47
|
+
},
|
|
48
|
+
routerKeyString
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
delayMs: 1500,
|
|
52
|
+
maxRetries: 7
|
|
53
|
+
});
|
|
54
|
+
srcData.push({
|
|
55
|
+
token,
|
|
56
|
+
amount: balance,
|
|
57
|
+
aggregatorData: { routerKey, swapData }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
srcData,
|
|
62
|
+
destData: {
|
|
63
|
+
destToken: receiveToken,
|
|
64
|
+
minDestAmount: swapDestMinDestAmount.toString()
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
} catch {
|
|
32
68
|
continue;
|
|
33
69
|
}
|
|
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
70
|
}
|
|
55
|
-
|
|
56
|
-
srcData,
|
|
57
|
-
destData: {
|
|
58
|
-
destToken: receiveToken,
|
|
59
|
-
minDestAmount: swapDestMinDestAmount.toString()
|
|
60
|
-
}
|
|
61
|
-
};
|
|
71
|
+
throw new Error("All swap routers failed for complete withdrawal");
|
|
62
72
|
};
|
|
63
73
|
export const createCompleteWithdrawalTxArguments = async (
|
|
64
74
|
pool: Pool,
|
|
@@ -138,12 +148,16 @@ export const createCompleteWithdrawalTxArguments = async (
|
|
|
138
148
|
.div(receiveTokenPriceD18)
|
|
139
149
|
.div(10 ** Number(swapTAssetDecimals.toString()))
|
|
140
150
|
.times(10 ** Number(receiveTokenDecimals.toString()))
|
|
141
|
-
|
|
151
|
+
// Outer floor stays strict on the user slippage — this is the
|
|
152
|
+
// withdrawer's safety check. Dust leniency applies only to the
|
|
153
|
+
// aggregator calldata below.
|
|
154
|
+
.times(1 - slippage / 10000) // slippage is in basis points, so divide by 10000
|
|
142
155
|
.decimalPlaces(0, BigNumber.ROUND_DOWN);
|
|
143
156
|
|
|
144
157
|
return {
|
|
145
158
|
token: swapTAsset.token,
|
|
146
159
|
balance: swapTAsset.balance,
|
|
160
|
+
slippage: adjustedSlippage,
|
|
147
161
|
estimatedMinReceiveAmount
|
|
148
162
|
};
|
|
149
163
|
})
|
|
@@ -167,10 +181,13 @@ export const createCompleteWithdrawalTxArguments = async (
|
|
|
167
181
|
|
|
168
182
|
const swapData = await getSwapWithdrawData(
|
|
169
183
|
pool,
|
|
170
|
-
|
|
184
|
+
tAssetInfos.map(({ token, balance, slippage }) => ({
|
|
185
|
+
token,
|
|
186
|
+
balance,
|
|
187
|
+
slippage
|
|
188
|
+
})),
|
|
171
189
|
receiveToken,
|
|
172
|
-
|
|
173
|
-
estimatedMinReceiveAmount
|
|
190
|
+
swapDestMinDestAmount
|
|
174
191
|
);
|
|
175
192
|
|
|
176
193
|
return {
|
|
@@ -8,9 +8,16 @@ import { isPool, loadPool } from "./pool";
|
|
|
8
8
|
|
|
9
9
|
import { getInitWithdrawalTxData } from "./initWithdrawal";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/** Minimum USD value of a leftover token to bother swapping during a withdrawal. */
|
|
12
|
+
export const LOW_USD_VALUE_FOR_WITHDRAWAL = 1;
|
|
13
|
+
/** Slippage (bps) applied to the swap of small-value leftover tokens during withdrawal. */
|
|
12
14
|
export const SLIPPAGE_FOR_LOW_VALUE_SWAP = 500;
|
|
13
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Resolve which asset should be used to deposit into a Toros pool. Returns the
|
|
18
|
+
* caller's `investAsset` if the pool already accepts it, otherwise the pool's
|
|
19
|
+
* primary deposit asset.
|
|
20
|
+
*/
|
|
14
21
|
export async function getPoolDepositAsset(
|
|
15
22
|
pool: Pool,
|
|
16
23
|
poolAddress: string,
|
|
@@ -26,6 +33,7 @@ export async function getPoolDepositAsset(
|
|
|
26
33
|
return composition.find(e => e.isDeposit)?.asset;
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
/** Read the current per-token price of a Toros vault from its PoolLogic. */
|
|
29
37
|
export async function getTorosPoolTokenPrice(
|
|
30
38
|
pool: Pool,
|
|
31
39
|
poolAddress: string
|
|
@@ -34,6 +42,7 @@ export async function getTorosPoolTokenPrice(
|
|
|
34
42
|
return await torosPool.poolLogic.tokenPrice();
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
/** Quote how many Toros vault tokens a deposit of `investAsset` would mint. */
|
|
37
46
|
export async function getEasySwapperDepositQuote(
|
|
38
47
|
pool: Pool,
|
|
39
48
|
torosAsset: string,
|
|
@@ -49,6 +58,11 @@ export async function getEasySwapperDepositQuote(
|
|
|
49
58
|
return await easySwapper.depositQuote(torosAsset, investAsset, amountIn);
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Build the EasySwapper calldata for a Toros deposit or the start of a withdrawal.
|
|
63
|
+
* If `assetFrom` is a Toros pool, this is treated as a withdrawal and routes through
|
|
64
|
+
* `getInitWithdrawalTxData`. Otherwise it builds a `depositWithCustomCooldown` call.
|
|
65
|
+
*/
|
|
52
66
|
export async function getEasySwapperTxData(
|
|
53
67
|
pool: Pool,
|
|
54
68
|
assetFrom: string,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { Dapp, ethers, Pool } from "../..";
|
|
3
|
-
import {
|
|
3
|
+
import { routerAddress } from "../../config";
|
|
4
4
|
import { retry } from "./retry";
|
|
5
5
|
import AaveLendingPoolAssetGuardAbi from "../../abi/IAaveLendingPoolAssetGuard.json";
|
|
6
6
|
import IEasySwapperV2 from "../../abi/IEasySwapperV2.json";
|
|
7
7
|
import { loadPool } from "./pool";
|
|
8
|
-
import {
|
|
8
|
+
import { getSwapData, ROUTER_KEYS } from "./swapData";
|
|
9
9
|
const AAVE_WITHDRAW_ONCHAIN_SWAP_SLIPPAGE = 150; // 1.5% slippage for onchain swap in Aave withdrawal
|
|
10
10
|
|
|
11
11
|
const getCalculateSwapDataParams = async (
|
|
@@ -45,38 +45,46 @@ const getAaveAssetWithdrawData = async (
|
|
|
45
45
|
) => {
|
|
46
46
|
const { srcData, dstData } = swapDataParams;
|
|
47
47
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
for (const routerKeyString of ROUTER_KEYS) {
|
|
49
|
+
try {
|
|
50
|
+
const srcDataToEncode: unknown[] = [];
|
|
51
|
+
const routerKey = ethers.utils.formatBytes32String(routerKeyString);
|
|
52
|
+
for (const { asset, amount } of srcData) {
|
|
53
|
+
const swapData = await retry({
|
|
54
|
+
fn: () => {
|
|
55
|
+
return getSwapData(
|
|
56
|
+
pool,
|
|
57
|
+
{
|
|
58
|
+
srcAsset: asset,
|
|
59
|
+
srcAmount: amount.toString(),
|
|
60
|
+
dstAsset: dstData.asset,
|
|
61
|
+
slippage
|
|
62
|
+
},
|
|
63
|
+
routerKeyString
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
delayMs: 1500,
|
|
67
|
+
maxRetries: 7
|
|
61
68
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
srcDataToEncode.push([asset, amount, [routerKey, swapData]]);
|
|
67
|
-
}
|
|
68
|
-
const coder = ethers.utils.defaultAbiCoder;
|
|
69
|
+
srcDataToEncode.push([asset, amount, [routerKey, swapData]]);
|
|
70
|
+
}
|
|
71
|
+
const coder = ethers.utils.defaultAbiCoder;
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
const encodedSrcData = coder.encode(
|
|
74
|
+
["tuple(address, uint256, tuple(bytes32, bytes))[]"],
|
|
75
|
+
[srcDataToEncode]
|
|
76
|
+
);
|
|
77
|
+
const withdrawData = coder.encode(
|
|
78
|
+
["tuple(bytes, tuple(address, uint256), uint256)"],
|
|
79
|
+
[[encodedSrcData, [dstData.asset, dstData.amount], slippage]]
|
|
80
|
+
);
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
return withdrawData;
|
|
83
|
+
} catch {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error("All swap routers failed for init withdrawal");
|
|
80
88
|
};
|
|
81
89
|
|
|
82
90
|
export const createWithdrawTxArguments = async (
|