@dhedge/v2-sdk 1.11.0 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhedge/v2-sdk",
3
- "version": "1.11.0",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "description": "🛠 An SDK for building applications on top of dHEDGE V2",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,27 @@
1
+ [
2
+ {
3
+ "inputs": [
4
+ { "internalType": "uint256", "name": "assets_", "type": "uint256" },
5
+ { "internalType": "address", "name": "receiver_", "type": "address" }
6
+ ],
7
+ "name": "deposit",
8
+ "outputs": [
9
+ { "internalType": "uint256", "name": "shares_", "type": "uint256" }
10
+ ],
11
+ "stateMutability": "nonpayable",
12
+ "type": "function"
13
+ },
14
+ {
15
+ "inputs": [
16
+ { "internalType": "uint256", "name": "shares_", "type": "uint256" },
17
+ { "internalType": "address", "name": "receiver_", "type": "address" },
18
+ { "internalType": "address", "name": "owner_", "type": "address" }
19
+ ],
20
+ "name": "redeem",
21
+ "outputs": [
22
+ { "internalType": "uint256", "name": "assets_", "type": "uint256" }
23
+ ],
24
+ "stateMutability": "nonpayable",
25
+ "type": "function"
26
+ }
27
+ ]
@@ -0,0 +1,15 @@
1
+ [
2
+ {
3
+ "inputs": [],
4
+ "name": "SY",
5
+ "outputs": [
6
+ {
7
+ "internalType": "address",
8
+ "name": "",
9
+ "type": "address"
10
+ }
11
+ ],
12
+ "stateMutability": "view",
13
+ "type": "function"
14
+ }
15
+ ]
@@ -0,0 +1 @@
1
+ [{"inputs":[],"name":"exchangeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
@@ -372,9 +372,10 @@ export class Pool {
372
372
  estimateGas = false
373
373
  ): Promise<any> {
374
374
  let swapTxData: string;
375
+ let minAmountOut: string | null = null;
375
376
  switch (dapp) {
376
377
  case Dapp.ONEINCH:
377
- ({ swapTxData } = await getOneInchSwapTxData(
378
+ ({ swapTxData, dstAmount: minAmountOut } = await getOneInchSwapTxData(
378
379
  this,
379
380
  assetFrom,
380
381
  assetTo,
@@ -415,28 +416,28 @@ export class Pool {
415
416
  );
416
417
  break;
417
418
  case Dapp.ODOS:
418
- swapTxData = await getOdosSwapTxData(
419
+ ({ swapTxData, minAmountOut } = await getOdosSwapTxData(
419
420
  this,
420
421
  assetFrom,
421
422
  assetTo,
422
423
  amountIn,
423
424
  slippage
424
- );
425
+ ));
425
426
  break;
426
427
  case Dapp.PENDLE:
427
- swapTxData = await getPendleSwapTxData(
428
+ ({ swapTxData, minAmountOut } = await getPendleSwapTxData(
428
429
  this,
429
430
  assetFrom,
430
431
  assetTo,
431
432
  amountIn,
432
433
  slippage
433
- );
434
+ ));
434
435
  break;
435
436
  default:
436
437
  const iUniswapV2Router = new ethers.utils.Interface(
437
438
  IUniswapV2Router.abi
438
439
  );
439
- const minAmountOut = await this.utils.getMinAmountOut(
440
+ const calculatedMinAmountOut = await this.utils.getMinAmountOut(
440
441
  dapp,
441
442
  assetFrom,
442
443
  assetTo,
@@ -445,7 +446,7 @@ export class Pool {
445
446
  );
446
447
  swapTxData = iUniswapV2Router.encodeFunctionData(Transaction.SWAP, [
447
448
  amountIn,
448
- minAmountOut,
449
+ calculatedMinAmountOut,
449
450
  [assetFrom, assetTo],
450
451
  this.address,
451
452
  await getDeadline(this)
@@ -453,7 +454,7 @@ export class Pool {
453
454
  }
454
455
  const tx = await getPoolTxOrGasEstimate(
455
456
  this,
456
- [routerAddress[this.network][dapp], swapTxData, options],
457
+ [routerAddress[this.network][dapp], swapTxData, options, minAmountOut],
457
458
  estimateGas
458
459
  );
459
460
  return tx;
@@ -717,8 +718,8 @@ export class Pool {
717
718
  }
718
719
 
719
720
  /**
720
- * Lend asset to a Compound V3 style lending pool
721
- * @param {string} market Address of market e.g cUSDCv3 address
721
+ * Lend asset to a Compound V3 or Fluid lending pool
722
+ * @param {string} market Address of cToken or fToken
722
723
  * @param {string} asset Asset
723
724
  * @param {BigNumber | string} amount Amount of asset to lend
724
725
  * @param {any} options Transaction options
@@ -732,7 +733,12 @@ export class Pool {
732
733
  options: any = null,
733
734
  estimateGas = false
734
735
  ): Promise<any> {
735
- const supplyTxData = getCompoundV3LendTxData(asset, amount);
736
+ const supplyTxData = await getCompoundV3LendTxData(
737
+ this,
738
+ market,
739
+ asset,
740
+ amount
741
+ );
736
742
 
737
743
  const tx = await getPoolTxOrGasEstimate(
738
744
  this,
@@ -773,8 +779,8 @@ export class Pool {
773
779
  }
774
780
 
775
781
  /**
776
- * Witdraw asset from a COmpound V3 style lending pool
777
- * @param {string} market Address of market e.g cUSDCv3 address
782
+ * Witdraw asset from a Compound V3 or Fluid lending pool
783
+ * @param {string} market Address of cToken or fToken
778
784
  * @param {string} asset Asset
779
785
  * @param {BigNumber | string} amount Amount of asset to withdraw
780
786
  * @param {any} options Transaction options
@@ -788,7 +794,12 @@ export class Pool {
788
794
  options: any = null,
789
795
  estimateGas = false
790
796
  ): Promise<any> {
791
- const withdrawTxData = getCompoundV3WithdrawTxData(asset, amount);
797
+ const withdrawTxData = await getCompoundV3WithdrawTxData(
798
+ this,
799
+ market,
800
+ asset,
801
+ amount
802
+ );
792
803
 
793
804
  const tx = await getPoolTxOrGasEstimate(
794
805
  this,
@@ -1,20 +1,59 @@
1
- import { ethers } from "../..";
1
+ import { ethers, Pool } from "../..";
2
2
  import ICompoundV3Comet from "../../abi/compound/ICompoundV3Comet.json";
3
+ import IFToken from "../../abi/fluid/IFToken.json";
3
4
 
4
- export function getCompoundV3LendTxData(
5
+ export async function getCompoundV3LendTxData(
6
+ pool: Pool,
7
+ market: string,
5
8
  asset: string,
6
9
  amount: ethers.BigNumber | string
7
- ): string {
8
- return new ethers.utils.Interface(
9
- ICompoundV3Comet
10
- ).encodeFunctionData("supply", [asset, amount]);
10
+ ): Promise<string> {
11
+ if (await isCompoundV3Market(pool, market)) {
12
+ return new ethers.utils.Interface(
13
+ ICompoundV3Comet
14
+ ).encodeFunctionData("supply", [asset, amount]);
15
+ } else {
16
+ //Fluid lending
17
+ return new ethers.utils.Interface(IFToken).encodeFunctionData("deposit", [
18
+ amount,
19
+ pool.address
20
+ ]);
21
+ }
11
22
  }
12
23
 
13
- export function getCompoundV3WithdrawTxData(
24
+ export async function getCompoundV3WithdrawTxData(
25
+ pool: Pool,
26
+ market: string,
14
27
  asset: string,
15
28
  amount: ethers.BigNumber | string
16
- ): string {
17
- return new ethers.utils.Interface(
18
- ICompoundV3Comet
19
- ).encodeFunctionData("withdraw", [asset, amount]);
29
+ ): Promise<string> {
30
+ if (await isCompoundV3Market(pool, market)) {
31
+ return new ethers.utils.Interface(
32
+ ICompoundV3Comet
33
+ ).encodeFunctionData("withdraw", [asset, amount]);
34
+ } else {
35
+ //Fluid withdrawal
36
+ return new ethers.utils.Interface(IFToken).encodeFunctionData("redeem", [
37
+ amount,
38
+ pool.address,
39
+ pool.address
40
+ ]);
41
+ }
42
+ }
43
+
44
+ export async function isCompoundV3Market(
45
+ pool: Pool,
46
+ market: string
47
+ ): Promise<boolean> {
48
+ const marketContract = new ethers.Contract(
49
+ market,
50
+ ICompoundV3Comet,
51
+ pool.signer
52
+ );
53
+ try {
54
+ await marketContract.baseToken();
55
+ return true;
56
+ } catch (error) {
57
+ return false;
58
+ }
20
59
  }
@@ -12,7 +12,7 @@ export async function getOdosSwapTxData(
12
12
  assetTo: string,
13
13
  amountIn: ethers.BigNumber | string,
14
14
  slippage: number
15
- ): Promise<string> {
15
+ ): Promise<{ swapTxData: string; minAmountOut: string }> {
16
16
  let referralCode = 0; // Defaults to 0 for unregistered activity.
17
17
  if (
18
18
  process.env.ODOS_REFERAL_CODE &&
@@ -53,7 +53,10 @@ export async function getOdosSwapTxData(
53
53
  `${odosBaseUrl}/assemble`,
54
54
  assembleParams
55
55
  );
56
- return assembleResult.data.transaction.data;
56
+ return {
57
+ swapTxData: assembleResult.data.transaction.data,
58
+ minAmountOut: assembleResult.data.outputTokens[0].amount
59
+ };
57
60
  } catch (e) {
58
61
  console.error("Error in Odos API request:", e);
59
62
  throw new ApiError("Swap api request of Odos failed");
@@ -4,6 +4,9 @@ import { ApiError, ethers } from "../..";
4
4
  import { networkChainIdMap } from "../../config";
5
5
  import { Pool } from "../../entities";
6
6
  import ActionMiscV3Abi from "../../abi/pendle/ActionMiscV3.json";
7
+ import PTAbi from "../../abi/pendle/PT.json";
8
+ import SYAbi from "../../abi/pendle/SY.json";
9
+ import BigNumber from "bignumber.js";
7
10
 
8
11
  const pendleBaseUrl = "https://api-v2.pendle.finance/core/v1";
9
12
 
@@ -13,17 +16,27 @@ export async function getPendleSwapTxData(
13
16
  tokenOut: string,
14
17
  amountIn: ethers.BigNumber | string,
15
18
  slippage: number
16
- ): Promise<string> {
19
+ ): Promise<{ swapTxData: string; minAmountOut: string | null }> {
17
20
  const expiredMarket = await checkExitPostExpPT(pool, tokenIn, tokenOut);
18
21
  if (expiredMarket) {
19
- return getExitExpPTTxData(pool, tokenOut, amountIn, expiredMarket);
22
+ const result = await getExitExpPTTxData(
23
+ pool,
24
+ tokenIn,
25
+ tokenOut,
26
+ amountIn,
27
+ expiredMarket
28
+ );
29
+ return {
30
+ swapTxData: result.txData,
31
+ minAmountOut: result.minAmountOut
32
+ };
20
33
  }
21
34
  const params = {
22
35
  receiver: pool.address,
23
36
  tokenIn,
24
37
  tokenOut,
25
38
  amountIn: amountIn.toString(),
26
- slippage
39
+ slippage: slippage / 100
27
40
  };
28
41
  const market = await getMarket(pool, tokenIn, tokenOut);
29
42
  try {
@@ -33,8 +46,10 @@ export async function getPendleSwapTxData(
33
46
  }/markets/${market}/swap`,
34
47
  { params }
35
48
  );
36
-
37
- return swapResult.data.tx.data;
49
+ return {
50
+ swapTxData: swapResult.data.tx.data,
51
+ minAmountOut: swapResult.data.data.amountOut
52
+ };
38
53
  } catch (e) {
39
54
  console.error("Error in Pendle API request:", e);
40
55
  throw new ApiError("Pendle api request failed");
@@ -111,12 +126,13 @@ const checkExitPostExpPT = async (
111
126
  }
112
127
  };
113
128
 
114
- const getExitExpPTTxData = (
129
+ const getExitExpPTTxData = async (
115
130
  pool: Pool,
131
+ tokenIn: string,
116
132
  tokenOut: string,
117
133
  amountIn: ethers.BigNumber | string,
118
134
  market: string
119
- ) => {
135
+ ): Promise<{ txData: string; minAmountOut: string | null }> => {
120
136
  const actionMiscV3 = new ethers.utils.Interface(ActionMiscV3Abi);
121
137
  const txData = actionMiscV3.encodeFunctionData("exitPostExpToToken", [
122
138
  pool.address, // receiver
@@ -137,5 +153,24 @@ const getExitExpPTTxData = (
137
153
  ]
138
154
  ]
139
155
  ]);
140
- return txData;
156
+
157
+ // Get the PT contract instance
158
+ const PTcontract = new ethers.Contract(tokenIn, PTAbi, pool.signer);
159
+ // Get the SY contract instance
160
+ const SYcontract = new ethers.Contract(
161
+ await PTcontract.SY(),
162
+ SYAbi,
163
+ pool.signer
164
+ );
165
+
166
+ const exchangeRate = await SYcontract.exchangeRate();
167
+ const minAmountOut = new BigNumber(amountIn.toString())
168
+ .times(1e18)
169
+ .div(exchangeRate.toString())
170
+ .decimalPlaces(0, BigNumber.ROUND_DOWN)
171
+ .toFixed(0);
172
+ return {
173
+ txData,
174
+ minAmountOut
175
+ };
141
176
  };
@@ -63,6 +63,7 @@ export const CONTRACT_ADDRESS = {
63
63
  VELODROME_CL_USDC_WETH_GAUGE: "",
64
64
  VELO: "",
65
65
  COMPOUNDV3_WETH: "",
66
+ FLUID_WETH: "",
66
67
  TOROS: "",
67
68
  UNIT: ""
68
69
  },
@@ -87,6 +88,7 @@ export const CONTRACT_ADDRESS = {
87
88
  VELODROME_CL_USDC_WETH_GAUGE: "0xa75127121d28a9BF848F3B70e7Eea26570aa7700",
88
89
  VELO: "0x9560e827aF36c94D2Ac33a39bCE1Fe78631088Db",
89
90
  COMPOUNDV3_WETH: "",
91
+ FLUID_WETH: "",
90
92
  TOROS: "0x49bf093277bf4dde49c48c6aa55a3bda3eedef68" //USDmny
91
93
  },
92
94
  [Network.ARBITRUM]: {
@@ -113,6 +115,7 @@ export const CONTRACT_ADDRESS = {
113
115
  VELODROME_CL_USDC_WETH_GAUGE: "",
114
116
  VELO: "",
115
117
  COMPOUNDV3_WETH: "0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486",
118
+ FLUID_WETH: "0x45df0656f8adf017590009d2f1898eeca4f0a205",
116
119
  TOROS: ""
117
120
  },
118
121
  [Network.BASE]: {
@@ -133,6 +136,7 @@ export const CONTRACT_ADDRESS = {
133
136
  VELODROME_CL_USDC_WETH_GAUGE: "0xF33a96b5932D9E9B9A0eDA447AbD8C9d48d2e0c8",
134
137
  VELO: "0x940181a94A35A4569E4529A3CDfB74e38FD98631",
135
138
  COMPOUNDV3_WETH: "",
139
+ FLUID_WETH: "",
136
140
  TOROS: ""
137
141
  },
138
142
  [Network.ETHEREUM]: {
@@ -152,6 +156,7 @@ export const CONTRACT_ADDRESS = {
152
156
  VELODROME_CL_USDC_WETH_GAUGE: "",
153
157
  VELO: "",
154
158
  COMPOUNDV3_WETH: "",
159
+ FLUID_WETH: "",
155
160
  TOROS: "",
156
161
  UNIT: ""
157
162
  }
@@ -0,0 +1,90 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import BigNumber from "bignumber.js";
4
+ import { Dhedge, Pool } from "..";
5
+ import { AssetEnabled, Network } from "../types";
6
+ import { CONTRACT_ADDRESS, MAX_AMOUNT, TEST_POOL } from "./constants";
7
+ import {
8
+ TestingRunParams,
9
+ beforeAfterReset,
10
+ setWETHAmount,
11
+ testingHelper
12
+ } from "./utils/testingHelper";
13
+ import { allowanceDelta, balanceDelta } from "./utils/token";
14
+ import { getWalletData } from "./wallet";
15
+
16
+ const testFluid = ({ network, provider }: TestingRunParams) => {
17
+ const WETH = CONTRACT_ADDRESS[network].WETH;
18
+ const FLUID_WETH = CONTRACT_ADDRESS[network].FLUID_WETH;
19
+
20
+ let dhedge: Dhedge;
21
+ let pool: Pool;
22
+ jest.setTimeout(100000);
23
+
24
+ describe(`[${network}] compound V3 tests`, () => {
25
+ beforeAll(async () => {
26
+ const { wallet } = getWalletData(network);
27
+ // top up ETH (gas)
28
+ await provider.send("hardhat_setBalance", [
29
+ wallet.address,
30
+ "0x100000000000000"
31
+ ]);
32
+ dhedge = new Dhedge(wallet, network);
33
+ pool = await dhedge.loadPool(TEST_POOL[network]);
34
+ await setWETHAmount({
35
+ amount: new BigNumber(1e18).toFixed(0),
36
+ userAddress: pool.address,
37
+ network,
38
+ provider
39
+ });
40
+
41
+ const newAssets: AssetEnabled[] = [
42
+ { asset: WETH, isDeposit: true },
43
+ {
44
+ asset: FLUID_WETH,
45
+ isDeposit: false
46
+ }
47
+ ];
48
+ await pool.managerLogic.changeAssets(newAssets, []);
49
+ });
50
+ beforeAfterReset({ beforeAll, afterAll, provider });
51
+
52
+ it("approves unlimited WETH for fWETH market", async () => {
53
+ await pool.approveSpender(FLUID_WETH, WETH, MAX_AMOUNT);
54
+ const wethAllowanceDelta = await allowanceDelta(
55
+ pool.address,
56
+ WETH,
57
+ FLUID_WETH,
58
+ pool.signer
59
+ );
60
+ await expect(wethAllowanceDelta.gt(0));
61
+ });
62
+
63
+ it("lends WETH to Fluid WETH market", async () => {
64
+ const wethBalance = await pool.utils.getBalance(WETH, pool.address);
65
+ await pool.lendCompoundV3(FLUID_WETH, WETH, wethBalance);
66
+
67
+ const fWETHTokenDelta = await balanceDelta(
68
+ pool.address,
69
+ FLUID_WETH,
70
+ pool.signer
71
+ );
72
+ expect(fWETHTokenDelta.gt(0));
73
+ });
74
+
75
+ it("withdraw WETH from Fluid WETH market", async () => {
76
+ const fWETHBalance = await pool.utils.getBalance(
77
+ FLUID_WETH,
78
+ pool.address
79
+ );
80
+ await pool.withdrawCompoundV3(FLUID_WETH, WETH, fWETHBalance);
81
+ const wethBalance = await balanceDelta(pool.address, WETH, pool.signer);
82
+ expect(wethBalance.gt(0));
83
+ });
84
+ });
85
+ };
86
+
87
+ testingHelper({
88
+ network: Network.ARBITRUM,
89
+ testingRun: testFluid
90
+ });
@@ -63,7 +63,8 @@ const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
63
63
  await getTxOptions(network),
64
64
  true
65
65
  );
66
- expect(gasEstimate.gt(0));
66
+ expect(gasEstimate.gas.gt(0));
67
+ expect(gasEstimate.minAmountOut).not.toBeNull();
67
68
  });
68
69
 
69
70
  it("trades 2 USDC into WETH on Odos", async () => {
@@ -86,16 +87,16 @@ const testOdos = ({ wallet, network, provider }: TestingRunParams) => {
86
87
  });
87
88
  };
88
89
 
89
- // testingHelper({
90
- // network: Network.OPTIMISM,
91
- // testingRun: testOdos
92
- // });
93
-
94
90
  testingHelper({
95
- network: Network.ARBITRUM,
91
+ network: Network.OPTIMISM,
96
92
  testingRun: testOdos
97
93
  });
98
94
 
95
+ // testingHelper({
96
+ // network: Network.ARBITRUM,
97
+ // testingRun: testOdos
98
+ // });
99
+
99
100
  // testingHelper({
100
101
  // network: Network.POLYGON,
101
102
  // onFork: false,
@@ -71,24 +71,22 @@ const testOneInch = ({ wallet, network, provider }: TestingRunParams) => {
71
71
  await getTxOptions(network),
72
72
  true
73
73
  );
74
- expect(gasEstimate.gt(0));
74
+ expect(gasEstimate.gas.gt(0));
75
+ expect(gasEstimate.minAmountOut).not.toBeNull();
75
76
  });
76
77
 
77
78
  it("gets error on gas estimation for 200 USDC into WETH on 1Inch", async () => {
78
79
  await wait(1);
79
- let gasEstimate = null;
80
- try {
81
- gasEstimate = await pool.trade(
82
- Dapp.ONEINCH,
83
- USDC,
84
- WETH,
85
- "200000000",
86
- 1,
87
- await getTxOptions(network),
88
- true
89
- );
90
- } catch (err) {}
91
- expect(gasEstimate).toBeNull();
80
+ const gasEstimate = await pool.trade(
81
+ Dapp.ONEINCH,
82
+ USDC,
83
+ WETH,
84
+ "200000000",
85
+ 1,
86
+ await getTxOptions(network),
87
+ true
88
+ );
89
+ expect(gasEstimate.gasEstimationError).not.toBeNull();
92
90
  });
93
91
 
94
92
  it("trades 2 USDC into WETH on 1Inch", async () => {
@@ -111,10 +109,10 @@ const testOneInch = ({ wallet, network, provider }: TestingRunParams) => {
111
109
  });
112
110
  };
113
111
 
114
- // testingHelper({
115
- // network: Network.OPTIMISM,
116
- // testingRun: testOneInch
117
- // });
112
+ testingHelper({
113
+ network: Network.OPTIMISM,
114
+ testingRun: testOneInch
115
+ });
118
116
 
119
117
  // testingHelper({
120
118
  // network: Network.POLYGON,
@@ -128,7 +126,7 @@ const testOneInch = ({ wallet, network, provider }: TestingRunParams) => {
128
126
  // testingRun: testOneInch
129
127
  // });
130
128
 
131
- testingHelper({
132
- network: Network.ETHEREUM,
133
- testingRun: testOneInch
134
- });
129
+ // testingHelper({
130
+ // network: Network.ETHEREUM,
131
+ // testingRun: testOneInch
132
+ // });