广告

什么是去中心化交易所聚合器?

从代码角度上看懂DEX的聚合交易?


1inch发 币了,不知道各位老铁有没有领到。有的人暗中窃喜,有人还不了解 1inch,这篇文件就介绍了 1inch 的核心功能。

文章的主要步骤如下:

  • 获得最大的收益兑换方案

  • 授权1inch合约操作你的代币

  • 利用第一步获得的兑换方案进行交易


什么是去中心化交易所聚合器?

去中心化交易所聚合器,即 DEX,以下都用 DEX 表示。DEX 聚合器是一个平台,它将搜索一组 DEX,以寻找在给定时间和数量下执行交易的最佳价格。

1inch DEX 聚合器

1inch 的一大特色就是聚合交易,它会在很多个 DEX 找到收益最大的成交方式。比如 100000dai 想买 x 个 eth,在 uniswap 成交 77%, 在 Bancor 成交 23% ,是最合算的,买到的 eth 最多。

1inch 是由 Anton Bukov 和 Sergej Kunz 开发的 DEX 聚合器,通过一次交易将订单在多个 DEX 之间拆分,给用户提供最好的兑换汇率。1inch 的智能合约是开源的。

在 1inch 执行交易,过程其实很简单:

  • 根据输入的 token 或 ETH 数量,获得预期可兑换的 token 数量

  • 授权(Approve)交易所使用你的 token

  • 使用第一步的获取的 token 数量进行交易

我们首先仔细了解一下 1inch 的智能合约,让我们感兴趣的是这两个方法:

  • getExpectedReturn()

  • swap()

getExpectedReturn - 估算最佳兑换方案

getExpectedReturn 可以随意调用,不需要消耗任何 gas。

这个函数需要传入兑换参数,返回兑换的期望结果,以及交易在各个 dex 之间的兑换比例。

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

这个方法接收 5 个参数:

  • fromToken:当前拥有的 token 的地址

  • toToken:要交换的 token 的地址

  • amount:想要交换的 token 数量

  • parts:卖出数量拆分成多少份进行最优分布的估算。查看distribution 可以了解更多细节,默认是 100

  • disableFlags:标记位,用于调整 1inch 的算法,例如可设置禁用某个特定的 DEX

这个方法有 2 个返回值:

  • returnAmount:执行交易后将收到的 token 数量。

  • distribution:一个 uint256 类型的数组,代表交易在不同 DEX 中的分布情况。例如,parts 设置为 100,成交额度的 25%在 Kyber 的,成交额度的 75%在 Uniswap,那么 distribution 看起来是这样的:[75, 25, 0, 0, …]。

目前 1inch 支持的交易所和排序(与 distribution 对应)如下:

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

注意:如果你想交易 Eth 而不是 ERC20 token,fromToken 需要设置为特殊的值 0x0或 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE。

getExpectedReturn函数的返回值非常重要,因为接下来需要利用它来执行实际的链上兑换操作。

swap - 执行多 DEX 兑换交易

要执行链上 token 兑换交易,就需要使用合约提供的另一个函数swap。调用swap时,需要传入我们之前从getExpectedReturn返回的数据,这个操作需要花费 gas。如果要卖出的是 ERC20 token,那么还需要先授权 1inch 合约可以操作你持有的待卖出 token。swap函数的定义如下:

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

swap 函数接收 6 个参数:

  • fromToken:待卖出 token 的地址

  • toToken:待买入 token 的地址

  • amount:待卖出 token 的数量

  • minReturn:期望得到的待买入 token 的最少数量

  • distribution:兑换交易拆分分布数组

  • parts:执行估算时的拆分数量,默认值是 100

  • disableFlags:标记位,例如可设置禁用某个特定的 DEX

开发环境搭建

我们将使用 ganache-cli 分叉(fork)当前的区块链状态[4],并提前在 1 个地址上充值了很多 DAI。在示例中,地址是 0x78bc49be7bae5e0eec08780c86f0e8278b8b035b。我们还将 gas limit 设置的非常高,因此在测试过程中不至于出现 out of gas 的问题,也不需要在每次交易前估算 gas。启动命令是:

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

实战 - 估算最佳兑换方案

分析完了 1inch 的关键方法,我们将进行第一笔兑换交易,代码已在 github 开源:https://github.com/liushooter/ethereumdevio-dex-tutorial/blob/master/part1/index.js。

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); // (1)
const oneSplitDexes = ["Uniswap", "Kyber", "Bancor", // (2)
  "Oasis", "CurveCompound", "CurveUsdt", "CurveY", "Binance", "Synthetix", "UniswapCompound", "UniswapChai", "UniswapAave"
]
onesplitContract.methods.getExpectedReturn(fromToken, toToken, // (3)
  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] + "%"); } });

(1)加载 ABI 以便实例化 1inch 合约实例

(2)该数组指定要使用的 DEX

(3)调用getExpectedReturn函数获取兑换方案

代码执行后返回结果类似下面这样:

此时卖出 1 个 eth,1inch 可以买到 148.47DAI,而 Coinbase 是 148.12。1inch 给出的最佳兑换方案是通过 Uniswap 兑换 96%,再通过 Bancor 兑换 4%,这样可以得到 148.47 DAI,这样比单独通过 Uniswap 或 Bancor 进行兑换都划算。

注意,这个价格不能作为智能合约的 Oracle 价格,由于各种各样的错误,DEX 可以提供非常低的价格,因此可能会严重操纵这个价格。

实战 - 执行多 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);
}

我们在之前已经获得了兑换比率,现在把代码变的更可读,定义 1 个getQuote函数,返回一个包含所有参数的对象。

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); }

一旦我们得到了兑换 token 的比率,接下来需要授权 1inch 可以操作我们持有的 token,ERC20 token 标准不允许在一次交易中向合约发送 token 并触发下一个操作。我们写了一个简单的函数,调用approval函数,并使用 waitTransaction 等待交易确认。

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函数执行交易后,等待交易确认,并在交易确认后,显示转出账户的 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。

在这个过程中,你可能会遇到的这样一个错误提示:“VM Exception while processing transaction: revert OneSplit: actual return amount is less than minReturn”。这表示链上的报价已经更新。如果想避免这种情况发生,你可以在代码中引入一个滑点,根据交易金额,将 minReturn 参数减小 1%或 3%。

总结

1inch 提供了出色的链上 DEX 聚合实现,可以在一个交易内利用多个 DEX 实现最优的兑换策略。1inch 的 API 使用也很简单,只需要用 getExpectedReturn 估算兑换方案, 然后使用 swap 执行兑换方案,就可以得到最好的兑换结果。你不必总是用 eth 交易,也可以交换 2 个 ERC20 token,甚至可以用 weth 交易。


24小时热点

比特币链上指标全解析

比特币链上指标可以用于判断加密货币所处市场周期,本文从MVR ...

123182

Coin Metrics

数字人民币跟其他的数字货币有什么区别?

简单来说,数字人民币和支付宝、微信支付的使用体验没什么差别

103415

Layer2

fil币是主流币吗?fil币的前景分析!

在数字货币的浩瀚海洋中,各种加密货币层出不穷,各有其独特之处 ...

1634

区块链圈小菜鸡

大模型不是一跃而起的

大模型不是一跃而起的 大模型发展的前期被称为预训 ...

5210

币信

GameFi链游平台

GameFi是一家专业的游戏运营平台,为游戏评测届的开发者和 ...

240640

CryptoPunks

曝光非法集资:时空云

FIL头部矿商再暴雷!第一大矿池时空云被警方调查! 去 ...

62784

Entropot

夏天韭菜几天割一次

夏天韭菜几天割一次? 韭菜是农作物的一种,属于蔬菜作物,与 ...

4785

K网

PUNDIX币(Pundi X [new])走势?

Pundi X(new)是一种新型的加密数字货币,它的发行是 ...

3879

coinbestex

睿蜂群(北京)旗下的“睿链库”是什么?

睿蜂群(北京)旗下的"睿链库"是一个区块链资源数据库,旨在帮 ...

7430

ImToken 多链钱包

Coinw币赢交易所APP涉嫌诈骗?

Coinw币赢交易所APP涉嫌诈骗问题最近受到大量投资者关注 ...

411166

中币交易所
广告

热点专题

专门忽悠企业家的中国十大骗子大师

区块链网创立于2015年初,为国内第一批有资质的区块链媒体, ...

2752704

希壤

2023年打假总结:Pi Network项目的传销性质Pi币

曾经,号称“走路就能赚钱”拥有大量用户和广泛影响的国内APP ...

2425274

文昌链

国内460种传销币套路和骗子币名单大曝光

打着区块链旗号,以聚集性传销、网络传销为手段,以每枚3元的价 ...

2093978

Coin Metrics

原力元宇宙佛萨奇骗局

原力元宇宙佛萨奇骗局是一种不可思议的幻想计划,它由一群英勇可 ...

1866395

聚币网

诈骗披露:成功学周文强

又一个“成功学”大师周文强翻车 你想一夜暴富吗?你想年薪百 ...

1717304

Lazy Lions

中国十大骗局之pi network(π币、pi币、派币、兀币)

中国十大骗局之pi network(π币、pi币、派币、兀币 ...

1435289

Gemini 交易所

柴犬币SHIB来了

因其可爱形象和马斯克代言,近段时间最耀眼的加密货币非狗狗币莫 ...

1268509

Luart

链圈打诈:3000名解放军去缅甸真的假的?

3000名解放军去缅甸这个是假新闻,出兵这是个大问题,如果我 ...

1173268

a16z

非常硬核的LP流动性挖矿的核算

MDX不同挖矿方法的真实收益率 如何挖取高APY?2021年 ...

1157405

RMRK

什么是去中心化交易所(DEX)?

去中心化交易所是一个基于区块链的交易所,它不将用户资金和个人 ...

1145010

bitFlyer交易所