DEX聚合器1inch對接教程【含源碼】

1inch是一個鏈上去中心化交易所(DEX)聚合器,它可以在單一交易內按最優策略將兌換拆分到多個DEX上執行,從而獲得最佳的兌換比率。1inch智能合約已經在Gihub開源,在這個教程中我們將學習如何通過1inch智能合約利用Uniswap/Banchor等 多個交易實現ERC20代幣的最佳兌換方案。

在這裏插入圖片描述

用自己熟悉的語言學習 以太坊DApp開發Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

1、用1inch實現代幣兌換的主要步驟

要利用1inch智能合約進行代幣兌換很簡單,主要步驟如下:

  • 估算如何利用多DEX獲得最大收益的兌換方案
  • 授權1inch合約操作你的代幣
  • 利用第一步獲得的兌換方案進行交易

首先讓我們看一下1inch的智能合約,其中最重要的兩個方法是:

  • getExpectedReturn() - 估算最優兌換方案
  • swap() - 執行多DEX兌換方案

2、getExpectedReturn - 估算最佳兌換方案

getExpectedReturn函數不會影響鏈上狀態,不需要手續費,因此你可以根據自己的需要隨意調用,只要節點在線就可以。

這個函數需要傳入兌換參數,然後返回兌換的期望結果數據, 其中包含了1inch將總的兌換數量如何分配到多個DEX上的分佈描述。

function getExpectedReturn(
        IERC20 fromToken,
        IERC20 toToken,
        uint256 amount,
        uint256 parts,
        uint256 disableFlags
) public view
returns(
         uint256 returnAmount,
         uint256[] memory distribution
);

getExpectedReturn()函數的5個參數說明如下:

  • fromToken:要賣出代幣的地址
  • toToken:要買入代幣的地址
  • amount:要賣出代幣的數量
  • parts:賣出數量拆分成多少份進行最優分佈的估算。默認值爲100
  • disableFlags:標誌位,用於調整1inch的算法,例如可設置是否禁用某個特定的DEX

getExpectedReturn()函數返回2個值:

  • returnAmount:執行兌換交易後將得到的買入代幣的數量
  • distribution:兌換交易在各DEX上的分佈數組,各成員和爲parts參數指定的值。例如 假設parts設置爲100並且設置只使用Kyber和Uniswap,那麼distribution可能看起來就是 這樣:[75, 25, 0, 0, …]。

目前1inch支持的交易所和排序(與distribution對應)如下:

[
    "Uniswap",
    "Kyber",
    "Bancor",
    "Oasis",
    "CurveCompound",
    "CurveUsdt",
    "CurveY",
    "Binance",
    "Synthetix",
    "UniswapCompound",
    "UniswapChai",
    "UniswapAave"
]

需要指出的是,如果你希望賣出ETH而不是ERC20代幣,那麼應當將fromToken參數設置爲特殊的值:0x0 或 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE。

getExpectedReturn函數的返回值非常重要,因爲接下來需要利用它來執行實際的鏈上兌換操作。

3、swap - 執行多DEX兌換交易

要執行鏈上代幣兌換交易,就需要使用1inch合約提供的另一個函數swap。調用swap時,需要傳入我們之前從getExpectedReturn返回的數據,並且承擔必要的gas開銷。如果要賣出的是ERC20代幣,那麼還需要先授權1inch合約可以操作你持有的待賣出代幣。swap函數的定義如下:

function swap(
        IERC20 fromToken,
        IERC20 toToken,
        uint256 amount,
        uint256 minReturn,
        uint256[] memory distribution,
        uint256 disableFlags
 ) public payable;

swap函數的6個參數說明如下:

  • fromToken:待賣出代幣的地址
  • toToken:待買入代幣的地址
  • amount:待賣出代幣的數量
  • minReturn:期望得到的待買入代幣的最少數量,當實際交易結果低於該值時將回滾交易
  • distribution:兌換交易拆分分佈數組,取自getExpectedReturn返回值
  • parts:執行估算時的拆分數量,同getExpectedReturn的參數
  • disableFlags:估算算法參數,同getExpectedReturn的參數

4、1inch實戰開發環境搭建

爲了進行下面的實戰,我們需要一個方便的開發環境 —— 直接在主網進行操作需要花費真金白銀,成本太高了。因此我們使用ganache-cli來分叉主鏈並解鎖某個已經持有DAI代幣的賬號,例如 0x78bc49be7bae5e0eec08780c86f0e8278b8b035b。出於簡化,我們也可以在開發過程中把gas上限設置的高一些,而不是在每個交易前估算gas成本。

下面的命令加載本地分叉鏈:

ganache-cli  -f https://mainnet.infura.io/v3/[YOUR INFURA KEY] \
             -d -i 66 \
             --unlock 0x78bc49be7bae5e0eec08780c86f0e8278b8b035b \
             -l 8000000

5、實戰1inch - 估算最佳兌換方案

首先我們用web3.js來通過1inch估算1 ETH兌換爲DAI的最佳方案。代碼如下:

var Web3 = require('web3');
const BigNumber = require('bignumber.js');

const oneSplitABI = require('./abis/onesplit.json');
const onesplitAddress = "0xC586BeF4a0992C495Cf22e1aeEE4E446CECDee0E"; // 1plit contract address on Main net

const fromToken = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; // ETHEREUM
const fromTokenDecimals = 18;

const toToken = '0x6b175474e89094c44da98b954eedeac495271d0f'; // DAI Token
const toTokenDecimals = 18;

const amountToExchange = 1

const web3 = new Web3('http://127.0.0.1:8545');

const onesplitContract = new web3.eth.Contract(oneSplitABI, onesplitAddress);

const oneSplitDexes = [
    "Uniswap",
    "Kyber",
    "Bancor",
    "Oasis",
    "CurveCompound",
    "CurveUsdt",
    "CurveY",
    "Binance",
    "Synthetix",
    "UniswapCompound",
    "UniswapChai",
    "UniswapAave"
]


onesplitContract.methods.getExpectedReturn(fromToken, toToken, new BigNumber(amountToExchange).shiftedBy(fromTokenDecimals).toString(), 100, 0).call({ from: '0x9759A6Ac90977b93B58547b4A71c78317f391A28' }, function (error, result) {
    if (error) {
        console.log(error)
        return;
    }
    console.log("Trade From: " + fromToken)
    console.log("Trade To: " + toToken);
    console.log("Trade Amount: " + amountToExchange);
    console.log(new BigNumber(result.returnAmount).shiftedBy(-fromTokenDecimals).toString());
    console.log("Using Dexes:");
    for (let index = 0; index < result.distribution.length; index++) {
        console.log(oneSplitDexes[index] + ": " + result.distribution[index] + "%");
    }
});

第4行:加載ABI以便實例化web3.js和1inch合約實例

第19行:該數組指定要使用的DEX

第35行:調用getExpectedReturn函數獲取兌換方案

如果你之前不太熟悉BigNumber.js這個庫,可以查閱這篇文章

上述代碼執行後返回的結果類似下面這樣:

在這裏插入圖片描述

也就是說,這時1inch給出的最佳兌換方案是通過Uniswap兌換96%,通過Bancor兌換4%,這樣可以得到148.47 DAI,這樣比單獨通過Uniswap或Bancor進行兌換都划算。

6、實戰1inch - 執行多DEX兌換方案

下面我們使用1inch聚合器將1000 DAI兌換爲ETH。首先定義一些變量,例如合約地址、ABI等等。

var Web3 = require('web3');
const BigNumber = require('bignumber.js');

const oneSplitABI = require('./abis/onesplit.json');
const onesplitAddress = "0xC586BeF4a0992C495Cf22e1aeEE4E446CECDee0E"; // 1plit contract address on Main net

const erc20ABI = require('./abis/erc20.json');
const daiAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI ERC20 contract address on Main net

const fromAddress = "0x4d10ae710Bd8D1C31bd7465c8CBC3add6F279E81";

const fromToken = daiAddress;
const fromTokenDecimals = 18;

const toToken = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; // ETH
const toTokenDecimals = 18;

const amountToExchange = new BigNumber(1000);

const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));

const onesplitContract = new web3.eth.Contract(oneSplitABI, onesplitAddress);
const daiToken = new web3.eth.Contract(erc20ABI, fromToken);

同時寫一個輔助函數來等待交易確認:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function waitTransaction(txHash) {
    let tx = null;
    while (tx == null) {
        tx = await web3.eth.getTransactionReceipt(txHash);
        await sleep(2000);
    }
    console.log("Transaction " + txHash + " was mined.");
    return (tx.status);
}

和前面類似,我們使用getExpectedReturn先估算兌換方案:

async function getQuote(fromToken, toToken, amount, callback) {
    let quote = null;
    try {
        quote = await onesplitContract.methods.getExpectedReturn(fromToken, toToken, amount, 100, 0).call();
    } catch (error) {
        console.log('Impossible to get the quote', error)
    }
    console.log("Trade From: " + fromToken)
    console.log("Trade To: " + toToken);
    console.log("Trade Amount: " + amountToExchange);
    console.log(new BigNumber(quote.returnAmount).shiftedBy(-fromTokenDecimals).toString());
    console.log("Using Dexes:");
    for (let index = 0; index < quote.distribution.length; index++) {
        console.log(oneSplitDexes[index] + ": " + quote.distribution[index] + "%");
    }
    callback(quote);
}

接下來需要授權1inch可以操作我們持有的代幣:

function approveToken(tokenInstance, receiver, amount, callback) {
    tokenInstance.methods.approve(receiver, amount).send({ from: fromAddress }, async function(error, txHash) {
        if (error) {
            console.log("ERC20 could not be approved", error);
            return;
        }
        console.log("ERC20 token approved to " + receiver);
        const status = await waitTransaction(txHash);
        if (!status) {
            console.log("Approval transaction failed.");
            return;
        }
        callback();
    })
}

注意,授權額度可以遠遠高於當前實際需要的數量,這樣後續就不再需要執行這個環節的操作了。

接下來就可以調用1inch聚合器的swap函數了。在下面的代碼中,我們在調用swap函數執行交易後,使用前面編寫的輔助函數waitTransaction來等待交易確認,並在交易確認後,顯示轉出賬戶的eth餘額和dai餘額:

let amountWithDecimals = new BigNumber(amountToExchange).shiftedBy(fromTokenDecimals).toFixed()

getQuote(fromToken, toToken, amountWithDecimals, function(quote) {
    approveToken(daiToken, onesplitAddress, amountWithDecimals, async function() {
        // We get the balance before the swap just for logging purpose
        let ethBalanceBefore = await web3.eth.getBalance(fromAddress);
        let daiBalanceBefore = await daiToken.methods.balanceOf(fromAddress).call();
        onesplitContract.methods.swap(fromToken, toToken, amountWithDecimals, quote.returnAmount, quote.distribution, 0).send({ from: fromAddress, gas: 8000000 }, async function(error, txHash) {
            if (error) {
                console.log("Could not complete the swap", error);
                return;
            }
            const status = await waitTransaction(txHash);
            // We check the final balances after the swap for logging purpose
            let ethBalanceAfter = await web3.eth.getBalance(fromAddress);
            let daiBalanceAfter = await daiToken.methods.balanceOf(fromAddress).call();
            console.log("Final balances:")
            console.log("Change in ETH balance", new BigNumber(ethBalanceAfter).minus(ethBalanceBefore).shiftedBy(-fromTokenDecimals).toFixed(2));
            console.log("Change in DAI balance", new BigNumber(daiBalanceAfter).minus(daiBalanceBefore).shiftedBy(-fromTokenDecimals).toFixed(2));
        });
    });
});

最後的執行結果看起來類似下面這樣:

在這裏插入圖片描述

正如你看到的,我們用1000 DAI換回來5.85 ETH。

7、1inch Dex聚合器使用小結

1inch提供了出色的鏈上DEX聚合實現,可以在一個交易內利用多個DEX實現最優的兌換策略。1inch的API使用也很簡單,只需要用getExpectedReturn估算兌換方案,然後使用swap執行兌換方案,就可以得到最好的兌換結果。無論是錢包、套利機器人還是其他DApp,都可以利用1inch來快速實現ETH/ERC20的兌換並且得到最佳的收益。


原文鏈接:基於1inch的ERC20最優兌換實現 — 匯智網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章