@dhedge/v2-sdk 2.1.3 → 2.1.5
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/config.d.ts +8 -0
- package/dist/entities/pool.d.ts +11 -0
- package/dist/services/odos/index.d.ts +15 -1
- package/dist/services/pendle/index.d.ts +4 -0
- package/dist/test/constants.d.ts +1 -0
- package/dist/v2-sdk.cjs.development.js +1704 -88
- 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 +1704 -88
- package/dist/v2-sdk.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/odos/OdosRouterV3.json +1351 -0
- package/src/abi/pendle/PT.json +13 -0
- package/src/config.ts +14 -5
- package/src/entities/pool.ts +41 -1
- package/src/services/odos/index.ts +97 -13
- package/src/services/pendle/index.ts +33 -0
- package/src/services/toros/completeWithdrawal.ts +1 -1
- package/src/services/toros/initWithdrawal.ts +1 -1
- package/src/services/toros/swapData.ts +83 -12
- package/src/test/constants.ts +2 -1
- package/src/test/odos.test.ts +43 -12
- package/src/test/pendleMint.test.ts +59 -0
package/src/abi/pendle/PT.json
CHANGED
|
@@ -11,5 +11,18 @@
|
|
|
11
11
|
],
|
|
12
12
|
"stateMutability": "view",
|
|
13
13
|
"type": "function"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"inputs": [],
|
|
17
|
+
"name": "YT",
|
|
18
|
+
"outputs": [
|
|
19
|
+
{
|
|
20
|
+
"internalType": "address",
|
|
21
|
+
"name": "",
|
|
22
|
+
"type": "address"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"stateMutability": "view",
|
|
26
|
+
"type": "function"
|
|
14
27
|
}
|
|
15
28
|
]
|
package/src/config.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const routerAddress: AddressDappNetworkMap = {
|
|
|
33
33
|
[Dapp.UNISWAPV3]: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45",
|
|
34
34
|
[Dapp.ARRAKIS]: "0xc73fb100a995b33f9fa181d420f4c8d74506df66",
|
|
35
35
|
[Dapp.TOROS]: "0x45b90480D6F643dE2f128db091A357C3c90399f2",
|
|
36
|
-
[Dapp.ODOS]: "
|
|
36
|
+
[Dapp.ODOS]: "0x0D05a7D3448512B78fa8A9e46c4872C88C4a0D05",
|
|
37
37
|
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
|
|
38
38
|
},
|
|
39
39
|
[Network.OPTIMISM]: {
|
|
@@ -46,7 +46,7 @@ export const routerAddress: AddressDappNetworkMap = {
|
|
|
46
46
|
[Dapp.VELODROMEV2]: "0xa062ae8a9c5e11aaa026fc2670b0d65ccc8b2858",
|
|
47
47
|
[Dapp.LYRA]: "0xCCE7819d65f348c64B7Beb205BA367b3fE33763B",
|
|
48
48
|
[Dapp.ARRAKIS]: "0x9ce88a56d120300061593eF7AD074A1B710094d5",
|
|
49
|
-
[Dapp.ODOS]: "
|
|
49
|
+
[Dapp.ODOS]: "0x0D05a7D3448512B78fa8A9e46c4872C88C4a0D05",
|
|
50
50
|
[Dapp.PENDLE]: "0x888888888889758F76e7103c6CbF23ABbF58F946",
|
|
51
51
|
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
|
|
52
52
|
},
|
|
@@ -57,7 +57,7 @@ export const routerAddress: AddressDappNetworkMap = {
|
|
|
57
57
|
[Dapp.BALANCER]: "0xBA12222222228d8Ba445958a75a0704d566BF2C8",
|
|
58
58
|
[Dapp.RAMSES]: "0xaaa87963efeb6f7e0a2711f397663105acb1805e",
|
|
59
59
|
[Dapp.TOROS]: "0xA5679C4272A056Bb83f039961fae7D99C48529F5",
|
|
60
|
-
[Dapp.ODOS]: "
|
|
60
|
+
[Dapp.ODOS]: "0x0D05a7D3448512B78fa8A9e46c4872C88C4a0D05",
|
|
61
61
|
[Dapp.PENDLE]: "0x888888888889758F76e7103c6CbF23ABbF58F946",
|
|
62
62
|
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
|
|
63
63
|
},
|
|
@@ -66,13 +66,13 @@ export const routerAddress: AddressDappNetworkMap = {
|
|
|
66
66
|
[Dapp.AERODROME]: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
|
|
67
67
|
[Dapp.AAVEV3]: "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
|
|
68
68
|
[Dapp.TOROS]: "0xf067575Eb60c7587C11e867907AA7284833704d1",
|
|
69
|
-
[Dapp.ODOS]: "
|
|
69
|
+
[Dapp.ODOS]: "0x0D05a7D3448512B78fa8A9e46c4872C88C4a0D05",
|
|
70
70
|
[Dapp.PENDLE]: "0x888888888889758F76e7103c6CbF23ABbF58F946",
|
|
71
71
|
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
|
|
72
72
|
},
|
|
73
73
|
[Network.ETHEREUM]: {
|
|
74
74
|
[Dapp.AAVEV3]: "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
|
|
75
|
-
[Dapp.ODOS]: "
|
|
75
|
+
[Dapp.ODOS]: "0x0D05a7D3448512B78fa8A9e46c4872C88C4a0D05",
|
|
76
76
|
[Dapp.PENDLE]: "0x888888888889758F76e7103c6CbF23ABbF58F946",
|
|
77
77
|
[Dapp.ONEINCH]: "0x111111125421ca6dc452d289314280a0f8842a65",
|
|
78
78
|
[Dapp.KYBERSWAP]: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
|
|
@@ -234,3 +234,12 @@ export const flatMoneyContractAddresses: Readonly<Partial<
|
|
|
234
234
|
StableModule: "0xcD3657cB0E851b6a734c4D1e7FC2640Bcd9f6B2d"
|
|
235
235
|
}
|
|
236
236
|
};
|
|
237
|
+
|
|
238
|
+
export const OdosSwapFeeRecipient = {
|
|
239
|
+
[Network.POLYGON]: "0x090e7fbD87A673eE3D0B6ccACf0e1d94fB90DA59",
|
|
240
|
+
[Network.OPTIMISM]: "0x813123A13d01d3F07d434673Fdc89cBBA523f14d",
|
|
241
|
+
[Network.ARBITRUM]: "0xfbD2B4216f422DC1eEe1Cff4Fb64B726F099dEF5",
|
|
242
|
+
[Network.BASE]: "0x5619AD05b0253a7e647Bd2E4C01c7f40CEaB0879",
|
|
243
|
+
[Network.ETHEREUM]: "0xfbD2B4216f422DC1eEe1Cff4Fb64B726F099dEF5",
|
|
244
|
+
[Network.PLASMA]: ""
|
|
245
|
+
};
|
package/src/entities/pool.ts
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
getPancakeUnStakeTxData
|
|
85
85
|
} from "../services/pancake/staking";
|
|
86
86
|
import { getOdosSwapTxData } from "../services/odos";
|
|
87
|
-
import { getPendleSwapTxData } from "../services/pendle";
|
|
87
|
+
import { getPendleMintTxData, getPendleSwapTxData } from "../services/pendle";
|
|
88
88
|
import { getCompleteWithdrawalTxData } from "../services/toros/completeWithdrawal";
|
|
89
89
|
import { getKyberSwapTxData } from "../services/kyberSwap";
|
|
90
90
|
|
|
@@ -2107,4 +2107,44 @@ export class Pool {
|
|
|
2107
2107
|
);
|
|
2108
2108
|
return tx;
|
|
2109
2109
|
}
|
|
2110
|
+
|
|
2111
|
+
/**
|
|
2112
|
+
* Mint PT and YT tokens on Pendle
|
|
2113
|
+
* @param {string} assetFrom Asset to mint from (only underlying asset)
|
|
2114
|
+
* @param {string} pt PT address
|
|
2115
|
+
* @param {BigNumber | string} amountIn Amount underlying asset
|
|
2116
|
+
* @param {number} slippage Slippage tolerance in %
|
|
2117
|
+
* @param {any} options Transaction options
|
|
2118
|
+
* @param {SDKOptions} sdkOptions SDK options including estimateGas
|
|
2119
|
+
* @returns {Promise<any>} Transaction
|
|
2120
|
+
*/
|
|
2121
|
+
async mintPendle(
|
|
2122
|
+
assetFrom: string,
|
|
2123
|
+
pt: string,
|
|
2124
|
+
amountIn: BigNumber | string,
|
|
2125
|
+
slippage = 0.5,
|
|
2126
|
+
options: any = null,
|
|
2127
|
+
sdkOptions: SDKOptions = {
|
|
2128
|
+
estimateGas: false
|
|
2129
|
+
}
|
|
2130
|
+
): Promise<any> {
|
|
2131
|
+
const { swapTxData, minAmountOut } = await getPendleMintTxData(
|
|
2132
|
+
this,
|
|
2133
|
+
assetFrom,
|
|
2134
|
+
pt,
|
|
2135
|
+
amountIn,
|
|
2136
|
+
slippage
|
|
2137
|
+
);
|
|
2138
|
+
const tx = await getPoolTxOrGasEstimate(
|
|
2139
|
+
this,
|
|
2140
|
+
[
|
|
2141
|
+
routerAddress[this.network][Dapp.PENDLE],
|
|
2142
|
+
swapTxData,
|
|
2143
|
+
options,
|
|
2144
|
+
minAmountOut
|
|
2145
|
+
],
|
|
2146
|
+
sdkOptions
|
|
2147
|
+
);
|
|
2148
|
+
return tx;
|
|
2149
|
+
}
|
|
2110
2150
|
}
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { ApiError, ethers } from "../..";
|
|
4
|
-
import { networkChainIdMap } from "../../config";
|
|
4
|
+
import { networkChainIdMap, OdosSwapFeeRecipient } from "../../config";
|
|
5
5
|
import { Pool } from "../../entities";
|
|
6
|
+
import OdosRouterV3Abi from "../../abi/odos/OdosRouterV3.json";
|
|
7
|
+
import BigNumber from "bignumber.js";
|
|
6
8
|
|
|
7
|
-
export const odosBaseUrl = "https://api.odos.xyz/sor";
|
|
9
|
+
export const odosBaseUrl = "https://enterprise-api.odos.xyz/sor";
|
|
10
|
+
|
|
11
|
+
// Types for Odos Router V3 swap function parameters
|
|
12
|
+
export interface SwapTokenInfo {
|
|
13
|
+
inputToken: string;
|
|
14
|
+
inputAmount: ethers.BigNumber;
|
|
15
|
+
inputReceiver: string;
|
|
16
|
+
outputToken: string;
|
|
17
|
+
outputQuote: ethers.BigNumber;
|
|
18
|
+
outputMin: ethers.BigNumber;
|
|
19
|
+
outputReceiver: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SwapReferralInfo {
|
|
23
|
+
code: ethers.BigNumber;
|
|
24
|
+
fee: ethers.BigNumber;
|
|
25
|
+
feeRecipient: string;
|
|
26
|
+
}
|
|
8
27
|
|
|
9
28
|
export async function getOdosSwapTxData(
|
|
10
29
|
pool: Pool,
|
|
@@ -13,13 +32,13 @@ export async function getOdosSwapTxData(
|
|
|
13
32
|
amountIn: ethers.BigNumber | string,
|
|
14
33
|
slippage: number
|
|
15
34
|
): Promise<{ swapTxData: string; minAmountOut: string }> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
process.env.ODOS_REFERAL_CODE &&
|
|
19
|
-
Number(process.env.ODOS_REFERAL_CODE) > 0
|
|
20
|
-
) {
|
|
21
|
-
referralCode = Number(process.env.ODOS_REFERAL_CODE);
|
|
35
|
+
if (!process.env.ODOS_API_KEY) {
|
|
36
|
+
throw new Error("ODOS_API_KEY is not set");
|
|
22
37
|
}
|
|
38
|
+
const ODOS_API_KEY = process.env.ODOS_API_KEY;
|
|
39
|
+
|
|
40
|
+
const referralFeeBips = 2; // 2 basis points = 0.02%
|
|
41
|
+
|
|
23
42
|
const quoteParams = {
|
|
24
43
|
chainId: networkChainIdMap[pool.network],
|
|
25
44
|
inputTokens: [
|
|
@@ -36,12 +55,20 @@ export async function getOdosSwapTxData(
|
|
|
36
55
|
],
|
|
37
56
|
slippageLimitPercent: slippage,
|
|
38
57
|
userAddr: pool.address,
|
|
39
|
-
|
|
58
|
+
compact: false,
|
|
59
|
+
referralFeeRecipient: OdosSwapFeeRecipient[pool.network],
|
|
60
|
+
referralFee: referralFeeBips // 0.02% fee
|
|
40
61
|
};
|
|
41
62
|
try {
|
|
42
63
|
const quoteResult = await axios.post(
|
|
43
|
-
`${odosBaseUrl}/quote/
|
|
44
|
-
quoteParams
|
|
64
|
+
`${odosBaseUrl}/quote/v3`,
|
|
65
|
+
quoteParams,
|
|
66
|
+
{
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
"x-api-key": ODOS_API_KEY
|
|
70
|
+
}
|
|
71
|
+
}
|
|
45
72
|
);
|
|
46
73
|
|
|
47
74
|
const assembleParams = {
|
|
@@ -51,10 +78,67 @@ export async function getOdosSwapTxData(
|
|
|
51
78
|
|
|
52
79
|
const assembleResult = await axios.post(
|
|
53
80
|
`${odosBaseUrl}/assemble`,
|
|
54
|
-
assembleParams
|
|
81
|
+
assembleParams,
|
|
82
|
+
{
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
"x-api-key": ODOS_API_KEY
|
|
86
|
+
}
|
|
87
|
+
}
|
|
55
88
|
);
|
|
89
|
+
|
|
90
|
+
const txData = assembleResult.data.transaction.data;
|
|
91
|
+
|
|
92
|
+
// Decode the transaction data
|
|
93
|
+
const iface = new ethers.utils.Interface(OdosRouterV3Abi.abi);
|
|
94
|
+
const decodedData = iface.parseTransaction({ data: txData });
|
|
95
|
+
|
|
96
|
+
const tokenInfo = decodedData.args[0] as SwapTokenInfo;
|
|
97
|
+
const pathDefinition = decodedData.args[1] as string;
|
|
98
|
+
const executor = decodedData.args[2] as string;
|
|
99
|
+
const referralInfo = decodedData.args[3] as SwapReferralInfo;
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
referralInfo.fee.lte(
|
|
103
|
+
ethers.BigNumber.from((referralFeeBips * 1e18) / 10000)
|
|
104
|
+
)
|
|
105
|
+
) {
|
|
106
|
+
// Referral fee is already correct, return original txData
|
|
107
|
+
return {
|
|
108
|
+
swapTxData: assembleResult.data.transaction.data,
|
|
109
|
+
minAmountOut: assembleResult.data.outputTokens[0].amount
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const FEE_DENOM = new BigNumber(1e18);
|
|
114
|
+
const correctedFee = new BigNumber((referralFeeBips * 1e18) / 10000);
|
|
115
|
+
const factor = 1.1;
|
|
116
|
+
const correctedOutputQuote = new BigNumber(tokenInfo.outputQuote.toString())
|
|
117
|
+
.times(
|
|
118
|
+
FEE_DENOM.minus(correctedFee).div(
|
|
119
|
+
FEE_DENOM.minus(referralInfo.fee.toString())
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
.times(factor);
|
|
123
|
+
|
|
124
|
+
// example referralInfo.fee could be 0.0005 * 1e18 = 500000000000000, which is 0.05%
|
|
125
|
+
// Create corrected referral info
|
|
126
|
+
const correctedTxData = iface.encodeFunctionData(decodedData.name, [
|
|
127
|
+
{
|
|
128
|
+
...tokenInfo,
|
|
129
|
+
outputQuote: correctedOutputQuote.toFixed(0)
|
|
130
|
+
},
|
|
131
|
+
pathDefinition,
|
|
132
|
+
executor,
|
|
133
|
+
{
|
|
134
|
+
code: referralInfo.code,
|
|
135
|
+
fee: correctedFee.toFixed(0), // align with referralFeeBips
|
|
136
|
+
feeRecipient: referralInfo.feeRecipient
|
|
137
|
+
}
|
|
138
|
+
]);
|
|
139
|
+
|
|
56
140
|
return {
|
|
57
|
-
swapTxData:
|
|
141
|
+
swapTxData: correctedTxData,
|
|
58
142
|
minAmountOut: assembleResult.data.outputTokens[0].amount
|
|
59
143
|
};
|
|
60
144
|
} catch (e) {
|
|
@@ -56,6 +56,39 @@ export async function getPendleSwapTxData(
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export async function getPendleMintTxData(
|
|
60
|
+
pool: Pool,
|
|
61
|
+
tokenIn: string,
|
|
62
|
+
pt: string,
|
|
63
|
+
amountIn: ethers.BigNumber | string,
|
|
64
|
+
slippage: number
|
|
65
|
+
): Promise<{ swapTxData: string; minAmountOut: string | null }> {
|
|
66
|
+
const PTcontract = new ethers.Contract(pt, PTAbi, pool.signer);
|
|
67
|
+
const ytAddress = await PTcontract.YT();
|
|
68
|
+
const params = {
|
|
69
|
+
receiver: pool.address,
|
|
70
|
+
tokensIn: tokenIn,
|
|
71
|
+
tokensOut: `${pt},${ytAddress}`,
|
|
72
|
+
amountsIn: amountIn.toString(),
|
|
73
|
+
slippage: slippage / 100
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
const swapResult = await axios.get(
|
|
77
|
+
`${pendleBaseUrl}/v2/sdk/${networkChainIdMap[pool.network]}/convert`,
|
|
78
|
+
{ params }
|
|
79
|
+
);
|
|
80
|
+
return {
|
|
81
|
+
swapTxData: swapResult.data.routes[0].tx.data,
|
|
82
|
+
minAmountOut: swapResult.data.routes[0].outputs.filter(
|
|
83
|
+
(e: { token: string }) => e.token === pt.toLowerCase()
|
|
84
|
+
)[0].amount
|
|
85
|
+
};
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error("Error in Pendle API request:", e);
|
|
88
|
+
throw new ApiError("Pendle api request failed");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
59
92
|
const checkUnderlying = (market: any, token: string, networkId: number) => {
|
|
60
93
|
if (market.underlyingAsset !== `${networkId}-${token.toLocaleLowerCase()}`) {
|
|
61
94
|
throw new Error("Can only trade in or out of the underlying asset");
|
|
@@ -25,7 +25,7 @@ const getSwapWithdrawData = async (
|
|
|
25
25
|
swapDestMinDestAmount: BigNumber
|
|
26
26
|
) => {
|
|
27
27
|
const srcData = [];
|
|
28
|
-
const routerKey = ethers.utils.formatBytes32String("
|
|
28
|
+
const routerKey = ethers.utils.formatBytes32String("ODOS_V3");
|
|
29
29
|
// const destData
|
|
30
30
|
for (const { token, balance } of trackedAssets) {
|
|
31
31
|
if (token.toLowerCase() === receiveToken.toLowerCase()) {
|
|
@@ -46,7 +46,7 @@ const getAaveAssetWithdrawData = async (
|
|
|
46
46
|
const { srcData, dstData } = swapDataParams;
|
|
47
47
|
|
|
48
48
|
const srcDataToEncode: unknown[] = [];
|
|
49
|
-
const routerKey = ethers.utils.formatBytes32String("
|
|
49
|
+
const routerKey = ethers.utils.formatBytes32String("ODOS_V3");
|
|
50
50
|
for (const { asset, amount } of srcData) {
|
|
51
51
|
const swapData = await retry({
|
|
52
52
|
fn: () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import BigNumber from "bignumber.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ethers } from "ethers";
|
|
4
|
+
import { odosBaseUrl, SwapReferralInfo, SwapTokenInfo } from "../odos";
|
|
5
|
+
import { networkChainIdMap, OdosSwapFeeRecipient } from "../../config";
|
|
6
|
+
import OdosRouterV3Abi from "../../abi/odos/OdosRouterV3.json";
|
|
4
7
|
|
|
5
8
|
export const SWAPPER_ADDERSS = "0x4F754e0F0924afD74980886b0B479Fa1D7C58D0D";
|
|
6
9
|
|
|
@@ -22,13 +25,19 @@ export const getSwapDataViaOdos = async ({
|
|
|
22
25
|
from,
|
|
23
26
|
slippage
|
|
24
27
|
}: SwapParams): Promise<string> => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
process.env.ODOS_REFERAL_CODE &&
|
|
28
|
-
Number(process.env.ODOS_REFERAL_CODE) > 0
|
|
29
|
-
) {
|
|
30
|
-
referralCode = Number(process.env.ODOS_REFERAL_CODE);
|
|
28
|
+
if (!process.env.ODOS_API_KEY) {
|
|
29
|
+
throw new Error("ODOS_API_KEY is not set");
|
|
31
30
|
}
|
|
31
|
+
const ODOS_API_KEY = process.env.ODOS_API_KEY;
|
|
32
|
+
const network = (Object.keys(networkChainIdMap) as Array<
|
|
33
|
+
keyof typeof networkChainIdMap
|
|
34
|
+
>).find(key => networkChainIdMap[key] === chainId);
|
|
35
|
+
if (!network) {
|
|
36
|
+
throw new Error(`Unsupported chainId: ${chainId}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const referralFeeBips = 2; // 2 basis points = 0.02%
|
|
40
|
+
|
|
32
41
|
const quoteParams = {
|
|
33
42
|
chainId: chainId,
|
|
34
43
|
inputTokens: [
|
|
@@ -45,12 +54,20 @@ export const getSwapDataViaOdos = async ({
|
|
|
45
54
|
],
|
|
46
55
|
slippageLimitPercent: new BigNumber(slippage).div(100).toString(), // Convert basis points to percentage
|
|
47
56
|
userAddr: from,
|
|
48
|
-
|
|
57
|
+
referralFee: referralFeeBips, // 0.02% fee
|
|
58
|
+
referralFeeRecipient: OdosSwapFeeRecipient[network],
|
|
59
|
+
compact: false
|
|
49
60
|
};
|
|
50
61
|
try {
|
|
51
62
|
const quoteResult = await axios.post(
|
|
52
|
-
`${odosBaseUrl}/quote/
|
|
53
|
-
quoteParams
|
|
63
|
+
`${odosBaseUrl}/quote/v3`,
|
|
64
|
+
quoteParams,
|
|
65
|
+
{
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
"x-api-key": ODOS_API_KEY
|
|
69
|
+
}
|
|
70
|
+
}
|
|
54
71
|
);
|
|
55
72
|
|
|
56
73
|
const assembleParams = {
|
|
@@ -60,9 +77,63 @@ export const getSwapDataViaOdos = async ({
|
|
|
60
77
|
|
|
61
78
|
const assembleResult = await axios.post(
|
|
62
79
|
`${odosBaseUrl}/assemble`,
|
|
63
|
-
assembleParams
|
|
80
|
+
assembleParams,
|
|
81
|
+
{
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
"x-api-key": ODOS_API_KEY
|
|
85
|
+
}
|
|
86
|
+
}
|
|
64
87
|
);
|
|
65
|
-
|
|
88
|
+
|
|
89
|
+
const txData = assembleResult.data.transaction.data;
|
|
90
|
+
|
|
91
|
+
// Decode the transaction data
|
|
92
|
+
const iface = new ethers.utils.Interface(OdosRouterV3Abi.abi);
|
|
93
|
+
const decodedData = iface.parseTransaction({ data: txData });
|
|
94
|
+
|
|
95
|
+
const tokenInfo = decodedData.args[0] as SwapTokenInfo;
|
|
96
|
+
const pathDefinition = decodedData.args[1] as string;
|
|
97
|
+
const executor = decodedData.args[2] as string;
|
|
98
|
+
const referralInfo = decodedData.args[3] as SwapReferralInfo;
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
referralInfo.fee.lte(
|
|
102
|
+
ethers.BigNumber.from((referralFeeBips * 1e18) / 10000)
|
|
103
|
+
)
|
|
104
|
+
) {
|
|
105
|
+
// Referral fee is already correct, return original txData
|
|
106
|
+
return txData;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const FEE_DENOM = new BigNumber(1e18);
|
|
110
|
+
const correctedFee = new BigNumber((referralFeeBips * 1e18) / 10000);
|
|
111
|
+
const factor = 1.1;
|
|
112
|
+
const correctedOutputQuote = new BigNumber(tokenInfo.outputQuote.toString())
|
|
113
|
+
.times(
|
|
114
|
+
FEE_DENOM.minus(correctedFee).div(
|
|
115
|
+
FEE_DENOM.minus(referralInfo.fee.toString())
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
.times(factor);
|
|
119
|
+
|
|
120
|
+
// example referralInfo.fee could be 0.0005 * 1e18 = 500000000000000, which is 0.05%
|
|
121
|
+
// Create corrected referral info
|
|
122
|
+
const correctedTxData = iface.encodeFunctionData(decodedData.name, [
|
|
123
|
+
{
|
|
124
|
+
...tokenInfo,
|
|
125
|
+
outputQuote: correctedOutputQuote.toFixed(0)
|
|
126
|
+
},
|
|
127
|
+
pathDefinition,
|
|
128
|
+
executor,
|
|
129
|
+
{
|
|
130
|
+
code: referralInfo.code,
|
|
131
|
+
fee: correctedFee.toFixed(0), // align with referralFeeBips
|
|
132
|
+
feeRecipient: referralInfo.feeRecipient
|
|
133
|
+
}
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
return correctedTxData;
|
|
66
137
|
} catch (e) {
|
|
67
138
|
console.error("Error in Odos API request:", e);
|
|
68
139
|
throw new Error("Swap api request of Odos failed");
|
package/src/test/constants.ts
CHANGED
|
@@ -170,7 +170,8 @@ export const CONTRACT_ADDRESS = {
|
|
|
170
170
|
USDC: "",
|
|
171
171
|
WETH: "0x9895d81bb462a195b4922ed7de0e3acd007c32cb",
|
|
172
172
|
USDT: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
|
|
173
|
-
USDE: "0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34"
|
|
173
|
+
USDE: "0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34",
|
|
174
|
+
TOROS: ""
|
|
174
175
|
}
|
|
175
176
|
};
|
|
176
177
|
|
package/src/test/odos.test.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { allowanceDelta, balanceDelta } from "./utils/token";
|
|
14
14
|
import { getTxOptions } from "./txOptions";
|
|
15
15
|
import BigNumber from "bignumber.js";
|
|
16
|
-
import { routerAddress } from "../config";
|
|
16
|
+
import { OdosSwapFeeRecipient, routerAddress } from "../config";
|
|
17
17
|
|
|
18
18
|
const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
|
|
19
19
|
const USDC = CONTRACT_ADDRESS[network].USDC;
|
|
@@ -35,7 +35,7 @@ const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
|
|
|
35
35
|
await provider.send("evm_mine", []);
|
|
36
36
|
// top up USDC
|
|
37
37
|
await setUSDCAmount({
|
|
38
|
-
amount: new BigNumber(
|
|
38
|
+
amount: new BigNumber(20).times(1e6).toFixed(0),
|
|
39
39
|
userAddress: pool.address,
|
|
40
40
|
network,
|
|
41
41
|
provider
|
|
@@ -53,12 +53,12 @@ const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
|
|
|
53
53
|
await expect(usdcAllowanceDelta.gt(0));
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it("gets gas estimation for
|
|
56
|
+
it("gets gas estimation for 10 USDC into WETH on Odos", async () => {
|
|
57
57
|
const gasEstimate = await pool.trade(
|
|
58
58
|
Dapp.ODOS,
|
|
59
59
|
USDC,
|
|
60
60
|
WETH,
|
|
61
|
-
"
|
|
61
|
+
"10000000",
|
|
62
62
|
1,
|
|
63
63
|
await getTxOptions(network),
|
|
64
64
|
true
|
|
@@ -67,13 +67,13 @@ const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
|
|
|
67
67
|
expect(gasEstimate.minAmountOut).not.toBeNull();
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it("trades
|
|
70
|
+
it("trades 10 USDC into WETH on Odos", async () => {
|
|
71
71
|
await wait(1);
|
|
72
72
|
await pool.trade(
|
|
73
73
|
Dapp.ODOS,
|
|
74
74
|
USDC,
|
|
75
75
|
WETH,
|
|
76
|
-
"
|
|
76
|
+
"10000000",
|
|
77
77
|
0.5,
|
|
78
78
|
await getTxOptions(network)
|
|
79
79
|
);
|
|
@@ -83,20 +83,51 @@ const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
|
|
|
83
83
|
pool.signer
|
|
84
84
|
);
|
|
85
85
|
expect(wethBalanceDelta.gt(0));
|
|
86
|
+
const wethBalanceDeltaForFeeRecipient = await balanceDelta(
|
|
87
|
+
OdosSwapFeeRecipient[network],
|
|
88
|
+
WETH,
|
|
89
|
+
pool.signer
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// diffRatio = (1 - fee) / (0.8 * fee)
|
|
93
|
+
// 0.8 is the split percentage for fee recipient
|
|
94
|
+
// e.g. for 0.02% fee, diffRatio = 0.9998 / 0.00016 = 6248.75
|
|
95
|
+
// e.g. for 0.03% fee, diffRatio = 0.9997 / 0.00024 = 4165.42
|
|
96
|
+
// e.g. for 0.04% fee, diffRatio = 0.9996 / 0.00032 = 3123.75
|
|
97
|
+
// e.g. for 0.05% fee, diffRatio = 0.9995 / 0.00040 = 2498.75
|
|
98
|
+
const diffRatio = wethBalanceDelta.div(wethBalanceDeltaForFeeRecipient);
|
|
99
|
+
console.log("diff ratio:", diffRatio.toString());
|
|
100
|
+
expect(diffRatio.gt(6200)).toBe(true);
|
|
101
|
+
expect(diffRatio.lt(6260)).toBe(true);
|
|
102
|
+
const wethBalanceDeltaForRouter = await balanceDelta(
|
|
103
|
+
routerAddress[network]["odos"]!,
|
|
104
|
+
WETH,
|
|
105
|
+
pool.signer
|
|
106
|
+
);
|
|
107
|
+
// diffRatio = (1 - fee) / (0.2 * fee)
|
|
108
|
+
// 0.2 is the split percentage for router
|
|
109
|
+
// e.g. for 0.02% fee, diffRatio = 0.9998 / 0.00004 = 24995
|
|
110
|
+
// e.g. for 0.03% fee, diffRatio = 0.9997 / 0.00006 = 16661.67
|
|
111
|
+
// e.g. for 0.04% fee, diffRatio = 0.9996 / 0.00008 = 12495
|
|
112
|
+
// e.g. for 0.05% fee, diffRatio = 0.9995 / 0.00010 = 9995
|
|
113
|
+
const diffRatioRouter = wethBalanceDelta.div(wethBalanceDeltaForRouter);
|
|
114
|
+
console.log("diff ratio router:", diffRatioRouter.toString());
|
|
115
|
+
expect(diffRatioRouter.gt(24000)).toBe(true);
|
|
116
|
+
expect(diffRatioRouter.lt(26000)).toBe(true);
|
|
86
117
|
});
|
|
87
118
|
});
|
|
88
119
|
};
|
|
89
120
|
|
|
90
|
-
testingHelper({
|
|
91
|
-
network: Network.OPTIMISM,
|
|
92
|
-
testingRun: testOdos
|
|
93
|
-
});
|
|
94
|
-
|
|
95
121
|
// testingHelper({
|
|
96
|
-
// network: Network.
|
|
122
|
+
// network: Network.OPTIMISM,
|
|
97
123
|
// testingRun: testOdos
|
|
98
124
|
// });
|
|
99
125
|
|
|
126
|
+
testingHelper({
|
|
127
|
+
network: Network.ARBITRUM,
|
|
128
|
+
testingRun: testOdos
|
|
129
|
+
});
|
|
130
|
+
|
|
100
131
|
// testingHelper({
|
|
101
132
|
// network: Network.POLYGON,
|
|
102
133
|
// onFork: false,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
|
|
3
|
+
import { Dhedge, Pool } from "..";
|
|
4
|
+
|
|
5
|
+
import { Network } from "../types";
|
|
6
|
+
import { CONTRACT_ADDRESS } from "./constants";
|
|
7
|
+
import { getTxOptions } from "./txOptions";
|
|
8
|
+
import { TestingRunParams, testingHelper } from "./utils/testingHelper";
|
|
9
|
+
|
|
10
|
+
const testPendle = ({ wallet, network }: TestingRunParams) => {
|
|
11
|
+
const USDE = CONTRACT_ADDRESS[network].USDE;
|
|
12
|
+
const PTJan26Usde = "0x93b544c330f60a2aa05ced87aeeffb8d38fd8c9a";
|
|
13
|
+
|
|
14
|
+
let dhedge: Dhedge;
|
|
15
|
+
let pool: Pool;
|
|
16
|
+
jest.setTimeout(100000);
|
|
17
|
+
|
|
18
|
+
describe(`pool on ${network}`, () => {
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
dhedge = new Dhedge(wallet, network);
|
|
21
|
+
pool = await dhedge.loadPool(
|
|
22
|
+
"0xdad21646ebb0997eb59de1f6a68a67059daf4c31"
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("can get TX Data for mint PT and SY", async () => {
|
|
27
|
+
const usdeBalance = await pool.utils.getBalance(USDE, pool.address);
|
|
28
|
+
const { txData, minAmountOut } = await pool.mintPendle(
|
|
29
|
+
USDE,
|
|
30
|
+
PTJan26Usde,
|
|
31
|
+
usdeBalance,
|
|
32
|
+
0.5,
|
|
33
|
+
null,
|
|
34
|
+
{ onlyGetTxData: true, estimateGas: true }
|
|
35
|
+
);
|
|
36
|
+
expect(txData).not.toBeNull();
|
|
37
|
+
expect(minAmountOut).not.toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("can get for mint PT and SY", async () => {
|
|
41
|
+
const usdeBalance = await pool.utils.getBalance(USDE, pool.address);
|
|
42
|
+
await pool.mintPendle(
|
|
43
|
+
USDE,
|
|
44
|
+
PTJan26Usde,
|
|
45
|
+
usdeBalance,
|
|
46
|
+
0.5,
|
|
47
|
+
await getTxOptions(network)
|
|
48
|
+
);
|
|
49
|
+
const ptBalance = await pool.utils.getBalance(PTJan26Usde, pool.address);
|
|
50
|
+
expect(ptBalance.gt(0)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
testingHelper({
|
|
56
|
+
network: Network.PLASMA,
|
|
57
|
+
onFork: false,
|
|
58
|
+
testingRun: testPendle
|
|
59
|
+
});
|