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

从代码角度上看懂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 交易。


欧易OKX

注册并登录App即可领取高达60,000元的数字货币盲盒,享受高额返佣。

24小时热点

创始人抛售后Pepe币价格暴跌20%

佩佩币Pepe币价格,在大量交易所流入之情况下,暴跌超过二成 ... 置顶

5864

新的加密货币预售Sonik币能上涨50倍?

星电币Sonik Coin是最新的迷因币加密货币,它的目标是 ... 置顶

37457

链圈打诈:十大骗子公司名单

一、今日丽萃网:今日丽萃网的骗局很多,首先,他们宣称自己是一 ...

104305

库神钱包

资金盘国家不管吗

肯定管啊,只不过搜集证据是需要时间的,到时把资金盘连根拔起! ...

121314

Coinbase Pro交易所

币圈打诈骗之:aot慈善币

aot慈善币,崩盘,这个币零撸很多人赚了很多,但是后面崩盘也 ...

3623

非小号

十堰七夕情资金盘为啥没人管?

答:对于十堰七夕情资金盘而言,其实并不是没人管,而是因为其管 ...

99949

网易星球

2023中国武警进入缅北?中国大使:无稽之谈

2023中国武警进入缅北?中国大使:无稽之谈 中国大使:无 ...

229579

布洛克

链圈百科:欧易web3钱包是什么?

欧易web3钱包(Erc20 web3 Wallet)是一种 ...

17288

Gemini 交易所

为什么那么多资金盘国家不打击

如今,资金盘国家因其金融销售手段的优势而受到越来越多的关注, ...

47275

bitFlyer交易所

诈骗披露:成功学周文强

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

300516

Lazy Lions

链圈打诈之缅北十大酷刑之:软件升级

缅北十大酷刑之:软件升级 关进小黑屋,手脚 ...

106012

ZT GLOBAL 交易所

分布式账本技术

分布式账本技术是建立在区块链底层的分布式数据库架构上的一种账 ...

499303

和讯区块链

热点专题

Pi Network最新消息-2023年路线图揭秘及未来规划

笔者观察到在Pi Network的版主因核心团队缺乏信息而举 ... 置顶

9272

追踪神秘钱包比特币巨鲸短短3个月累积308亿美元

最近出现的一个神秘巨鲸钱包,却让所有人瞠目结舌。据Bitin ... 置顶

11540

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

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

2691808

希壤

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

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

2379846

文昌链

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

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

2024222

Coin Metrics

柴犬币SHIB来了

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

1251304

Luart

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

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

1135334

RMRK

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

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

1126889

bitFlyer交易所

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

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

862779

Gemini 交易所

在Mdex上挖矿教程

一文读懂如何在Mdex上挖矿教程。Mdex是火币生态链上的去 ...

809159

大都会资本

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

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

807390

比推Bitpush

国家区块链相关政策汇总

据零壹智库不完全统计,截至2019年12月,国家层面共计出台 ...

736854

利得链