Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tradeDefiLlama): Add trading via DefiLlama API #62

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ONEINCH_API_KEY=""
ZEROEX_API_KEY=""
ZEROEX_API_KEY=""
DEFILLAMA_API_KEY=""
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ Then you need to copy .env.example file to .env and set your API key there.
ONEINCH_API_KEY=YOUR_API_KEY_FROM_1INCH
```

For trading with the DefiLlama API you neeed to set:

```
DEFILLAMA_API_KEY=YOUR_API_KEY_FROM_DEFILLAMA
```

Initialize the sdk with an [ethers wallet](https://docs.ethers.io/v5/api/signer/#Wallet) and the network.

```ts
Expand Down Expand Up @@ -216,6 +222,22 @@ const tx = await pool.trade(
)
```

#### 11. Trade pool assets with the DefiLlama API

Trade 1 USDC into DAI either on 1Inch or 0x, it will be executed on the protocol with the best price

```ts
const amountIn = "1000000"
const slippage = 0.5
const tx = await pool.tradeDefiLlama(
null,
"USDC_TOKEN_ADDRESS",
"DAI_TOKEN_ADDRESS",
amountIn,
slippage
)
```

### Liquidity

---
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dhedge/v2-sdk",
"version": "1.10.4",
"version": "1.10.5",
"license": "MIT",
"description": "🛠 An SDK for building applications on top of dHEDGE V2",
"main": "dist/index.js",
Expand Down
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,8 @@ export const flatMoneyContractAddresses: Readonly<Partial<
RETH: "0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c"
}
};

export const dappDefiLlamaMap: Readonly<Partial<Record<Dapp, string>>> = {
[Dapp.ONEINCH]: "1inch",
[Dapp.ZEROEX]: "Matcha/0x"
};
38 changes: 38 additions & 0 deletions src/entities/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
getCompoundV3LendTxData,
getCompoundV3WithdrawTxData
} from "../services/compound/lending";
import { getDefiLlamaTxData } from "../services/defiLlama";

export class Pool {
public readonly poolLogic: Contract;
Expand Down Expand Up @@ -437,6 +438,43 @@ export class Pool {
return tx;
}

/**
* Uses the DefiLlama API to trade an asset into another asset
* @param {Dapp | null} dapp Protocol to use for trading, if null, the protocol with the best price will be used
* @param {string} assetFrom Asset to trade from
* @param {string} assetTo Asset to trade into
* @param {BigNumber | string} amountIn Amount
* @param {number} slippage Slippage tolerance in %
* @param {any} options Transaction options
* @param {boolean} estimateGas Simulate/estimate gas
* @returns {Promise<any>} Transaction
*/
async tradeDefiLlama(
dapp: Dapp | null,
assetFrom: string,
assetTo: string,
amountIn: BigNumber | string,
slippage = 0.5,
options: any = null,
estimateGas = false
): Promise<any> {
const { txData, protocolAddress } = await getDefiLlamaTxData(
this,
dapp,
assetFrom,
assetTo,
amountIn,
slippage
);

const tx = await getPoolTxOrGasEstimate(
this,
[protocolAddress, txData, options],
estimateGas
);
return tx;
}

/**
* Add liquidity to a liquidity pool
* @param {Dapp} dapp Platform like Sushiswap or Uniswap
Expand Down
108 changes: 108 additions & 0 deletions src/services/defiLlama/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from "axios";
import { ApiError, Dapp, ethers } from "../..";

import { Pool } from "../../entities";
import BigNumber from "bignumber.js";
import { dappDefiLlamaMap } from "../../config";

export type DefiLlamaResult = {
amountReturned: BigNumber;
protocolAddress: string;
txData: string;
};

export async function getDefiLlamaSwapResult(
pool: Pool,
protocol: "1inch" | "Matcha/0x",
assetFrom: string,
assetTo: string,
amountIn: ethers.BigNumber | string,
slippage: number
): Promise<DefiLlamaResult> {
if (!process.env.DEFILLAMA_API_KEY)
throw new Error("DEFILLAMA_API_KEY not configured in .env file");

const apiUrl = "https://swap-api.defillama.com/dexAggregatorQuote";
const params = {
from: assetFrom,
to: assetTo,
amount: amountIn.toString(),
chain: pool.network,
api_key: process.env.DEFILLAMA_API_KEY,
protocol
};
const [fromDecimals, toDecimals] = await Promise.all(
[assetFrom, assetTo].map(async asset => pool.utils.getDecimals(asset))
);
const body = {
userAddress: pool.address,
slippage: slippage,
fromToken: {
decimals: fromDecimals
},
toToken: {
decimals: toDecimals
}
};
try {
const response = await axios.post(apiUrl, body, {
params
});

return {
amountReturned: new BigNumber(response.data.amountReturned),
protocolAddress:
protocol === "1inch"
? response.data.rawQuote.tx.to
: response.data.rawQuote.to,
txData:
protocol === "1inch"
? response.data.rawQuote.tx.data
: response.data.rawQuote.data
};
} catch (e) {
throw new ApiError("Swap api request of DefiLlama failed");
}
}

export async function getDefiLlamaTxData(
pool: Pool,
dapp: Dapp | null,
assetFrom: string,
assetTo: string,
amountIn: ethers.BigNumber | string,
slippage: number
): Promise<DefiLlamaResult> {
if (!dapp) {
const result = await Promise.all(
Object.values(dappDefiLlamaMap).map(async protocol =>
getDefiLlamaSwapResult(
pool,
protocol as any,
assetFrom,
assetTo,
amountIn,
slippage
)
)
);
const maxResult = result.reduce((max, current) => {
return current.amountReturned.isGreaterThan(max.amountReturned)
? current
: max;
});
return maxResult;
} else {
if (!(dapp === Dapp.ONEINCH || dapp === Dapp.ZEROEX))
throw new Error("dapp not supported");
return await getDefiLlamaSwapResult(
pool,
dappDefiLlamaMap[dapp] as any,
assetFrom,
assetTo,
amountIn,
slippage
);
}
}
114 changes: 114 additions & 0 deletions src/test/defiLlama.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import { Dhedge, Pool } from "..";

import { Dapp, Network } from "../types";
import { CONTRACT_ADDRESS, MAX_AMOUNT, TEST_POOL } from "./constants";
import {
TestingRunParams,
setUSDCAmount,
testingHelper
} from "./utils/testingHelper";
import BigNumber from "bignumber.js";
import { balanceDelta } from "./utils/token";

import { getTxOptions } from "./txOptions";

const testDefiLlama = ({ wallet, network, provider }: TestingRunParams) => {
const USDC = CONTRACT_ADDRESS[network].USDC;
const WETH = CONTRACT_ADDRESS[network].WETH;

let dhedge: Dhedge;
let pool: Pool;
jest.setTimeout(100000);

describe(`pool on ${network}`, () => {
beforeAll(async () => {
dhedge = new Dhedge(wallet, network);
pool = await dhedge.loadPool(TEST_POOL[network]);
// top up gas
await provider.send("hardhat_setBalance", [
wallet.address,
"0x10000000000000000"
]);
await provider.send("evm_mine", []);
// top up USDC
await setUSDCAmount({
amount: new BigNumber(10).times(1e6).toFixed(0),
userAddress: pool.address,
network,
provider
});
await pool.approve(Dapp.ONEINCH, USDC, MAX_AMOUNT);
await pool.approve(Dapp.ZEROEX, USDC, MAX_AMOUNT);
});

it("trades 2 USDC into WETH on 0x", async () => {
await pool.tradeDefiLlama(
Dapp.ZEROEX,
USDC,
WETH,
"2000000",
0.5,
await getTxOptions(network)
);
const wethBalanceDelta = await balanceDelta(
pool.address,
WETH,
pool.signer
);
expect(wethBalanceDelta.gt(0));
});

it("trades 2 USDC into WETH on 1inch", async () => {
await pool.tradeDefiLlama(
Dapp.ONEINCH,
USDC,
WETH,
"2000000",
0.5,
await getTxOptions(network)
);
const wethBalanceDelta = await balanceDelta(
pool.address,
WETH,
pool.signer
);
expect(wethBalanceDelta.gt(0));
});

it("trades 2 USDC into WETH protocol with best price", async () => {
await pool.tradeDefiLlama(
null,
USDC,
WETH,
"2000000",
0.5,
await getTxOptions(network)
);
const wethBalanceDelta = await balanceDelta(
pool.address,
WETH,
pool.signer
);
expect(wethBalanceDelta.gt(0));
});
});
};

testingHelper({
network: Network.OPTIMISM,
testingRun: testDefiLlama
});

// testingHelper({
// network: Network.POLYGON,
// onFork: false,
// testingRun: testOneInch
// });

// testingHelper({
// network: Network.BASE,
// onFork: false,
// testingRun: testOneInch
// });