[メモ] Web3.pyでgetAmountOuts, quoteExactOutputを行う
おはこんばんにちは。
DMMビットコインから482億円分流出や、TGE後のゴミ対応プロジェクトが出たり、相場が上がりそうなのに急に下げたりと。
なんか疲弊する市場ですね。
ってことで、今回は皆さん一度は憧れるDEX間アービトラージbotの監視ツールを作成したいと思います(完全に意味不明)
まず前提として、こんな監視ツールじゃ儲からないです
手数料、スリッページ、フロントラン等のriskをフル無視しています。
まあ、逆に捉えてそれらを考慮すればワンチャン。。って感じです。
ただ、DEXbotは修羅の道。
いくら早い言語にしてもロジが優秀でも、結局は通信速度とTX承認スピードが求められます。
これを乗り越えられるなら儲かるかも?(白目)
環境構築
python3が既にインストール済みなことを確認してください。
もしくは、GoogleColabやJupiterNotebook等を使用すれば予め環境が整っている上でビルドができます。
ディレクトリを作成し、pyenv(仮想環境)を立ち上げます。
!mkdir DexWatcher
!cd DexWatcher
!python3 -m venv myvenv
!python3 source myvenv/bin/activate
必要なライブラリをインストール
!pip install web3
以上です。
単体のコード
UniswapV2単体のgetAmountOuts
v2{.}py
import json
from web3 import Web3
with open("config.json") as f:
config = json.load(f)
web3 = Web3(Web3.HTTPProvider(config["INFURA_RPC_URL"]))
with open("UniV2Router02abi.json") as f:
UNISWAPV2_ROUTER_ABI = json.load(f)
router_contract = web3.eth.contract(address=config["UNI_V2_ROUTER_ADDRESS"], abi=UNISWAPV2_ROUTER_ABI)
buy_path = [config["TOKEN_A_ADDRESS"], config["TOKEN_B_ADDRESS"]]
amount_to_buy_for = 1 * 10**18
token_a_amount, token_b_amount = router_contract.functions.getAmountsOut(amount_to_buy_for, buy_path).call()
print(f"for {token_a_amount/10**18} Token A you would get {token_b_amount/10**18} Token B")
token_b_to_buy = 500 * 10**18
token_a_amount, token_b_amount = router_contract.functions.getAmountsIn(token_b_to_buy, buy_path).call()
print(f"for {token_b_to_buy/10**18} Token B you have to pay {token_a_amount/10**18} Token A")
config{.}json
{
"INFURA_RPC_URL": "https://mainnet.infura.io/v3/XXXXXXXXXXXXXXX",
"UNI_V2_ROUTER_ADDRESS": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
"TOKEN_A_ADDRESS": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", //wETH
"TOKEN_B_ADDRESS": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" //UNI
}
ABI (UniV2Router02abi) に関しては下記のetherscanからjson formatでexportしましょう
今回はWETHをTokenA、UNIをTokenBに指定しました。
数量は1ETH, 500UNIでそれぞれ2ルートで行います。
ここで少し補足ですが、ERC20トークンの種別によって*DECIMALS*と呼ばれる小数点桁数を指定している数値が異なっています。
基本的には18桁ですが、USDTやUSDCなどは6桁とのこと。
なので、input, outputによって使用するトークンが何DECIMALSなのか調べた上で随時変更しましょう
10**18 -> 10**8 -> 10**6
それはさておき、実行すると下記のようなログが出ました。
UniswapV3単体のquoteExactOutput
v3{.}py
import json
from web3 import Web3
import eth_abi.packed
with open("config.json") as f:
config = json.load(f)
web3 = Web3(Web3.HTTPProvider(config["INFURA_RPC_URL"]))
with open("UniV3Quoter2abi.json") as f:
UNISWAP_V3_QUOTER2_ABI = json.load(f)
quoter2_contract = web3.eth.contract(address=config["UNISWAP_V3_QUOTER2_ADDRESS"], abi=UNISWAP_V3_QUOTER2_ABI)
path = eth_abi.packed.encode_packed(['address','uint24','address'], [config["TOKEN_A_ADDRESS"], 3000, config["TOKEN_B_ADDRESS"]])
amount_to_buy_for = 1 * 10**18
amount_out, sqrtPriceX96After, initializedTicksCrossed, gasEstimate = quoter2_contract.functions.quoteExactOutput(path, amount_to_buy_for).call()
print(f"for {amount_to_buy_for/10**18} Token A you would get {amount_out/10**18} Token B")
path = eth_abi.packed.encode_packed(['address','uint24','address'], [config["TOKEN_B_ADDRESS"], 3000, config["TOKEN_A_ADDRESS"]])
token_b_to_buy = 500 * 10**18
amount_out, sqrtPriceX96After, initializedTicksCrossed, gasEstimate = quoter2_contract.functions.quoteExactInput(path, token_b_to_buy).call()
print(f"for {token_b_to_buy/10**18} Token B you have to pay {amount_out/10**18} Token A")
config{.}json
{
"INFURA_RPC_URL": "https://mainnet.infura.io/v3/9921258f942545febeb8642d511a0156",
"UNISWAP_V3_QUOTER2_ADDRESS": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
"TOKEN_A_ADDRESS": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"TOKEN_B_ADDRESS": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
}
abi (UniV3Quoter2abi)
https://etherscan.io/address/0x61fFE014bA17989E743c5F6cB21bF9697530B21e#code
Output (V2と同じ条件)
二者間アビトラの監視コード(マージ)
UniswapV2 -> UniswapV3
UniswapV3 -> UniswapV2
双方を10秒に一回並列シミュレーションする二者間アビトラのコードです
import json
from web3 import Web3
import eth_abi.packed
import time
from concurrent.futures import ThreadPoolExecutor
with open("config.json") as f:
config = json.load(f)
web3 = Web3(Web3.HTTPProvider(config["INFURA_RPC_URL"]))
with open("UniV2Router02abi.json") as f:
UNISWAPV2_ROUTER_ABI = json.load(f)
with open("UniV3Quoter2abi.json") as f:
UNISWAP_V3_QUOTER2_ABI = json.load(f)
router_contract = web3.eth.contract(address=config["UNI_V2_ROUTER_ADDRESS"], abi=UNISWAPV2_ROUTER_ABI)
quoter2_contract = web3.eth.contract(address=config["UNISWAP_V3_QUOTER2_ADDRESS"], abi=UNISWAP_V3_QUOTER2_ABI)
def simulate_arbitrage(direction):
initial_eth = 1
eth_amount = initial_eth * 10**18
if direction == "v2_to_v3":
# UniswapV2: TokenA -> TokenB
buy_path = [config["TOKEN_A_ADDRESS"], config["TOKEN_B_ADDRESS"]]
token_a_amount, token_b_amount = router_contract.functions.getAmountsOut(eth_amount, buy_path).call()
# UniswapV3: TokenB -> TokenA
path = eth_abi.packed.encode_packed(['address','uint24','address'], [config["TOKEN_B_ADDRESS"], 3000, config["TOKEN_A_ADDRESS"]])
amount_out, sqrtPriceX96After, initializedTicksCrossed, gasEstimate = quoter2_contract.functions.quoteExactInput(path, token_b_amount).call()
profit_loss = (amount_out - eth_amount) / 10**18
return f"Profit/Loss (UniV2 -> UniV3): {profit_loss} ETH"
else:
# UniswapV3: TokenA -> TokenB
path = eth_abi.packed.encode_packed(['address','uint24','address'], [config["TOKEN_A_ADDRESS"], 3000, config["TOKEN_B_ADDRESS"]])
token_b_amount, sqrtPriceX96After, initializedTicksCrossed, gasEstimate = quoter2_contract.functions.quoteExactInput(path, eth_amount).call()
# UniswapV2: TokenB -> TokenA
sell_path = [config["TOKEN_B_ADDRESS"], config["TOKEN_A_ADDRESS"]]
token_a_amount_out = router_contract.functions.getAmountsOut(token_b_amount, sell_path).call()[-1]
profit_loss = (token_a_amount_out - eth_amount) / 10**18
return f"Profit/Loss (UniV3 -> UniV2): {profit_loss} ETH"
with ThreadPoolExecutor(max_workers=2) as executor:
while True:
print(f"Arbitrage simulation at {time.strftime('%Y-%m-%d %H:%M:%S')}")
futures = [executor.submit(simulate_arbitrage, direction) for direction in ["v2_to_v3", "v3_to_v2"]]
results = [f.result() for f in futures]
for result in results:
print(result)
print("---")
time.sleep(10)
Output
余裕でマイナスです!!
10秒ごとに繰り返して、監視の基本部分はできているので、あとはパス指定さえ好みに変えていけばOKです。
三者間アビトラのexampleまで書く気力がないので、あとはお任せします!(投げやりで草)
言い訳
正直、新規ペアを取得するためにmempoolから監視してcreate pairの関数を読み取り、decodeしてパス取るのも良いのですが、ハニーポットのリスクがまだ少なからずあるので、riskが高いです。
そういや、ハニポで捕まった中華人大学生おったな。
そう思うと、足元には常に地雷があって競争が激しい世界で利益を継続的に取るのは不可能説が個人的にはあります。
そもそも人間辞めないとマジでムリだと思います。(丸一日継続的に研究できるタイプとか。)
とか言う自分も研究者気質が少しある方だと自負してるのですが、メインのCEXbotに1日10時間費やせても、DEXに関しては稼げる見込みがなさ過ぎて途方に暮れてます。。w
だから僕はDEXbotを辞めた
結論
現場からは以上です。