在前一個教程中,我們介紹了套利機器人背後的三個主要概念:套利、基於合約的交易和樂觀轉賬。在本文中,我們將逐步介紹如何利用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、套利機器人教程小節
儘管此代碼還不適用於生產環境,但我們希望它能說明閃電交換的基本概念,同時我們也希望它能夠展示出這個僅在區塊鏈上可能存在的簡單工具的強大功能!
原文鏈接:閃電貸套利機器人開發教程二 — 匯智網