闪电贷套利机器人开发教程(二)

前一个教程中,我们介绍了 套利机器人背后的三个主要概念:套利、基于合约的交易和乐观转账。在本文中,我们将逐步介绍如何 利用nodejs和solidity构建套利机器人程序,以监视uniswap和sushiswap上的潜在套利机会并执行有利可图的套利交易。

用自己熟悉的语言学习 以太坊DApp开发Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

下面是我们的套利机器人的总体流程:

闪电贷套利机器人

  1. 机器人程序/Bot跟踪Uniswap和Sushiswap上的交易对
  2. 当发现有利可图的套利机会时,套利机器人将交易发送给我们已部署的合约
  3. 在一个交易中,合约将执行如下操作:
    • 使用闪电兑换从价格较低的资产池借入资产
    • 立即在价格较高的资产池售出资产
    • 偿还闪电兑换贷款并收获差额利润

1、DEX套利机器人使用的技术栈

教程中使用的技术栈如下:

  • Node.js:基础运行环境
  • Ethers.js:以太坊JS开发库
  • Infura:提供以太坊接入访问点
  • Solidity:套利智能合约

后端采用Node开发,使用Infura节点来跟踪Uniswap和Sushiswap合约中ETH和Dai的价格。 我们利用Infura端点来获取主网上产出的每个新区块上的价格。

我们之所以使用Ethers.js,是因为它与Typescript(项目的原始语言)兼容。对于以太坊开发人员 来说这是一个古老的问题,但有关ethers.js和web3.js之间的区别,请参阅这篇文章

2、使用.env保存套利机器人的敏感信息

这是非常重要的!由于需要存储私钥来签署主网交易,我们将所有敏感信息都放在.env文件中, 此外,套利合约的地址和Infura主网端点的key也存入该文件:

1
2
3
PRIVATE_KEY=
FLASH_LOANER=
INFURA_KEY=

确保PRIVATE_KEY与部署FLASH_LOANER合约时使用的账号相同。另外,与PRIVATE_KEY对应的以太坊账号 需要有足够的资金来支付GAS成本。

如果你不确定我们为什么这样做,请阅读这篇文章, 它解释了如何避免将私钥上传到Github。如文章所述,我们需要将敏感信息放入此.env文件中,然后 将其添加到.gitignore文件中,如下所示:

1
2
3
4
.env
yarn.lock
package-lock.json
node_modules

这样,当我们将信息推送到Github时,将不包括该文件在内。这一点超级、超级重要!

3、DEX合约实例化

接下来,我们在第11行和第12行上实例化Uniswap和Sushiswap合约:

1
2
3
// uni/sushiswap ABIs
const UniswapV2Pair = require('./abis/IUniswapV2Pair.json');
const UniswapV2Factory = require('./abis/IUniswapV2Factory.json');

Sushiswap本质上是Uniswap的一个分支,因此它们具有完全相同的合同ABI和可供我们使用的完全相同 的功能…。这也是他们适合套利的另一个原因!

4、定期检查套利机会

第50行是套利机器人的关键所在。每隔一个区块时间,我们将要求Infura检查Uniswap和Sushiswap中 ETH和Dai的价格。然后,我们将比较这些数字以获得“价差”或可能的利润空间。

1
2
3
4
5
6
7
8
provider.on('block', async (blockNumber) => {
try {
console.log(blockNumber);
const sushiReserves = await sushiEthDai.getReserves();
const uniswapReserves = await uniswapEthDai.getReserves();
[...]
}
}

5、关于提前交易/Front Running

提前交易在中心化金融交易中很常见,通常利用数据速度获取微小的优势。 我们不必为此担心太多,因为Uniswap和Sushiswap是去中心化交易所。它们的价格保持在链上, 并且逐块变化。

即使我们想以某种方式在网络主体之前获取信息,唯一能采取行动的方法是将交易包括在 下一个区块中,这对所有人都是可见的。我们需要支付高昂的GAS费来阻止抢先交易, 但这大概是最大的收益。

6、避免无法获利的交易 - GAS估算

这样的DeFi交易可能非常昂贵。虽然套利活动可能获利,但利润空间更可能被GAS成本吞噬。 我们的套利机器人程序的一项重要检查就是确保GAS成本不会吃掉利润。我们执行此操作, 并将其包含在shouldSendTx中:

1
2
3
const shouldSendTx = shouldStartEth
? (gasCost / ETH_TRADE) < spread
: (gasCost / (DAI_TRADE / priceUniswap)) < spread;

对于像ETH和Dai这样常见的交易对,并没有很多的获利机会。这些交易对的交易量很大, 并且Uniswap和Sushiswap是比较受欢迎的交易所。从经济角度来讲,套利机会是市场效率 低下的结果。如果有很多人在使用这些货币对,那么我们不太可能找到很多机会。因此 需要找到更新的代币或交易所!

7、开发套利智能合约

点击这里查看套利合约的源代码。

基本上,合约是我们的套利中介。当程序检测到有利可图的机会时,它将向该合约发送资金和交易指令。

我们的套利合约相对还是比较简单的。大多数代码来自Uniswap的示例代码

合约的构造函数,可以传入一些硬编码的数据,例如Uniswap和Sushiswap的合约地址。合约还有一个 函数uniswapV2Call,我们在其中以乐观方式从一个交易所借入代币,在另一个交易所执行交换, 然后立即偿还第一笔借款:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
pragma solidity =0.6.6;

import './UniswapV2Library.sol';
import './interfaces/IUniswapV2Router02.sol';
import './interfaces/IUniswapV2Pair.sol';
import './interfaces/IERC20.sol';

contract FlashLoaner {
address immutable factory;
uint constant deadline = 10 days;
IUniswapV2Router02 immutable sushiRouter;

constructor(address _factory, address _uniRouter, address _sushiRouter) public {
factory = _factory;
sushiRouter = IUniswapV2Router02(_sushiRouter);
}

function uniswapV2Call(address _sender, uint _amount0, uint _amount1, bytes calldata _data) external {
address[] memory path = new address[](2);
uint amountToken = _amount0 == 0 ? _amount1 : _amount0;

address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();

require(msg.sender == UniswapV2Library.pairFor(factory, token0, token1), "Unauthorized");
require(_amount0 == 0 || _amount1 == 0);

path[0] = _amount0 == 0 ? token1 : token0;
path[1] = _amount0 == 0 ? token0 : token1;

IERC20 token = IERC20(_amount0 == 0 ? token1 : token0);

token.approve(address(sushiRouter), amountToken);

// no need for require() check, if amount required is not sent sushiRouter will revert
uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountToken, path)[0];
uint amountReceived = sushiRouter.swapExactTokensForTokens(amountToken, amountRequired, path, msg.sender, deadline)[1];

// YEAHH PROFIT
token.transfer(_sender, amountReceived - amountRequired);

}
}

如果有任何利润,合约会将其发送到发起交易的地址(_sender)。

8、套利机器人教程小节

尽管此代码还不适用于生产环境,但我们希望它能说明闪电交换的基本概念,同时我们也希望它 能够展示出这个仅在区块链上可能存在的简单工具的强大功能!


原文链接:Build a Flash Loan Arbitrage Bot on Infura Part II

汇智网翻译整理,转载请标明出处