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

从代码角度上看懂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小时热点

融资交易平台不得从事代币兑换业务

随着虚拟货币的兴起,市场上刮起了交易虚拟货币的热潮。与此同时 ... 置顶

6592

网信办整治“币圈”乱象,关闭上万个违规账号和105家网站

网信办整治“币圈”乱象,关闭上万个违规账号和105家网站 ... 置顶

27964

开展国家区块链创新应用试点行动

近日,中央网信办会同中央宣传部、国务院办公厅[电子政务办公室 ...

21182

汤姆猫元宇宙

数字人民币校园生态应用场景

移动支付网讯:5月10日,农行宁波分行与浙大宁波理工学院举办 ...

16008

K网

是什么为所有代币提供动力

加密货币领域正在经历前所未有的增长和创新。

12631

区块链博客

美联储限制银行派发股息,禁止三季度回购股票

近期,欧洲聚集性感染频发,美欧贸易争端再起,金融市场“惴惴不 ...

10991

区块链交易平台有哪些

中国十大传销币:派币(π币、pi币、pi network、兀币)

中国十大传销币:派币(π币、pi币、pi network、兀 ...

503303

海藏

恒生指数收跌0.53% 券商、保险等金融股普跌

10月27日,香港恒生指数低位震荡,跌131.59点,跌幅0 ...

6183

Lets Talk Bitcoin

WTF 空投 吃了我的gas费给我拿回来

WTF ?抱歉,我们不是在骂人。 这是一个正在发 ...

12042

Lazy Lions

核电是理论上最适合比特币挖矿的能源

当下比特币挖矿引发的能源消耗问题引发全球关注

15860

RMRK

热点专题

以游戏带动引擎发展实现功能迭代与技术演进

游戏引擎作为游戏创作工具与游戏运行的底层控制器,着力于在虚拟 ... 置顶

33356

2022年打假总结:存活近3年 疯狂的“传销币”GEC是怎么苟延残喘至今?

币圈从来不缺乏资金盘或是传销币,但多数都不长久,一夜之间项目 ...

2779460

加密兔

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

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

2292823

文昌链

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

“大师”的伎俩: 最近几年,在网络、机场的书店, ...

1737356

希壤

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

全员警惕!!!国内460种传销币套路和骗子币名单大曝光(附全 ...

1728187

Coin Metrics

柴犬币SHIB来了

受狗狗币启发,柴犬币(SHIB)近两天被创造出来。

1213457

Luart

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

2021年1月19日20:00MDX正式上线交易挖矿和流动性 ...

1092706

RMRK

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

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

1080791

bitFlyer交易所

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

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

876352

红果数藏

在Mdex上挖矿教程

Mdex是火币生态链上的去中心化交易所,Mdex挖矿于1月1 ...

774374

大都会资本

“iBox” 数藏平台是违法违规的金融活动

NFT可以看作是元宇宙的其中一种应用,我们可以把数字藏品看成 ...

716913

比推Bitpush