在前一个教程中,我们介绍了套利机器人背后的三个主要概念:套利、基于合约的交易和乐观转账。在本文中,我们将逐步介绍如何利用nodejs和solidity构建套利机器人程序,以监视uniswap和sushiswap上的潜在套利机会并执行有利可图的套利交易。
用自己熟悉的语言学习 以太坊DApp开发 :Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart
下面是我们的套利机器人的总体流程:
- 机器人程序/Bot跟踪Uniswap和Sushiswap上的交易对
- 当发现有利可图的套利机会时,套利机器人将交易发送给我们已部署的合约
- 在一个交易中,合约将执行如下操作:
- 使用闪电兑换从价格较低的资产池借入资产
- 立即在价格较高的资产池售出资产
- 偿还闪电兑换贷款并收获差额利润
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也存入该文件:
PRIVATE_KEY=
FLASH_LOANER=
INFURA_KEY=
确保PRIVATE_KEY与部署FLASH_LOANER合约时使用的账号相同。另外,与PRIVATE_KEY对应的以太坊账号需要有足够的资金来支付GAS成本。
如果你不确定我们为什么这样做,请阅读这篇文章,它解释了如何避免将私钥上传到Github。如文章所述,我们需要将敏感信息放入此.env文件中,然后将其添加到.gitignore文件中,如下所示:
.env
yarn.lock
package-lock.json
node_modules
这样,当我们将信息推送到Github时,将不包括该文件在内。这一点超级、超级重要!
3、DEX合约实例化
接下来,我们在第11行和第12行上实例化Uniswap和Sushiswap合约:
// 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的价格。然后,我们将比较这些数字以获得“价差”或可能的利润空间。
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中:
const shouldSendTx = shouldStartEth
? (gasCost / ETH_TRADE) < spread
: (gasCost / (DAI_TRADE / priceUniswap)) < spread;
对于像ETH和Dai这样常见的交易对,并没有很多的获利机会。这些交易对的交易量很大,并且Uniswap和Sushiswap是比较受欢迎的交易所。从经济角度来讲,套利机会是市场效率低下的结果。如果有很多人在使用这些货币对,那么我们不太可能找到很多机会。因此需要找到更新的代币或交易所!
7、开发套利智能合约
点击这里查看套利合约的源代码。
基本上,合约是我们的套利中介。当程序检测到有利可图的机会时,它将向该合约发送资金和交易指令。我们的套利合约相对还是比较简单的。大多数代码来自Uniswap的示例代码。
合约的构造函数,可以传入一些硬编码的数据,例如Uniswap和Sushiswap的合约地址。合约还有一个函数uniswapV2Call,我们在其中以乐观方式从一个交易所借入代币,在另一个交易所执行交换,然后立即偿还第一笔借款:
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、套利机器人教程小节
尽管此代码还不适用于生产环境,但我们希望它能说明闪电交换的基本概念,同时我们也希望它能够展示出这个仅在区块链上可能存在的简单工具的强大功能!
原文链接:闪电贷套利机器人开发教程二 — 汇智网