pythonでUniswapV2Routerコントラクトを呼び出してBaseネットワークでswapしてみた話

※自身の備忘録的な意味合いが強いので、使用は自己責任の参考程度でお願いします。

仮想通貨まわりの作業を自動化したいと思い、少しずつ勉強していこうと思っています。その足掛かりとしてプログラムを組んでスワップする、ということを今回やってみました。

舞台はBaseネットワークです。

WETH→AERO(Swap)

#base_swap1.py

import json
from web3 import Web3
from datetime import datetime

baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'my_private_key'
WETH = '0x4200000000000000000000000000000000000006'
USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
AERO = '0x940181a94A35A4569E4529A3CDfB74e38FD98631'
UniV2Router = '0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24'
gas = 200000
gasPrice = 3

web3 = Web3(Web3.HTTPProvider(baseRpc))

with open('UniSwap_Router_ABI.json') as f:
   uniRouterABI = json.load(f)
uniRouterContract = web3.eth.contract(address=UniV2Router, abi=uniRouterABI)

now = int(datetime.now().timestamp()) 
nonce = web3.eth.get_transaction_count(walletAddress) 

funcSwap = uniRouterContract.functions.swapExactETHForTokens(
       0, 
       [WETH, AERO], 
       walletAddress, 
       now+200 
       )

tx = funcSwap.build_transaction({
   'value': web3.to_wei(0.01, 'ether'),
   'gas': gas,
   'gasPrice': web3.to_wei(gasPrice, 'gwei'),
   'nonce': nonce
   })

signedTx = web3.eth.account.sign_transaction(tx, walletKey)
txHash = web3.eth.send_raw_transaction(signedTx.raw_transaction)

print('Send transaction')
print(web3.to_hex(txHash))
result = web3.eth.wait_for_transaction_receipt(txHash)
status = result['status']
if status == 1:
   print('Transaction Succeeded')
else:
   print('Transaction Failed')

my_wallet_addressはmetamaskなどのアドレス
my_private_keyは秘密鍵
上記ファイルと同じディレクトリにUniSwap_Router_ABI.jsonというファイルが必要(中身はUniswap V2 RouterコントラクトのABI)。

Base mainnetのUniswap V2 Routerのコントラクトアドレスは
0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24
なので、BaseScanでこのアドレスを検索し、ContractタブのCodeの下の方でABIを見つけることができる(コピーしてUniSwap_Router_ABI.jsonにペースト)。

https://basescan.org/address/0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24#code

また、ContractタブのWrite Contractで「swapExactETHForTokens」ファンクションについても確認できる。

amountOutMin(許容できる引出しトークン最小量)
path(どのトークンとどのトークンをスワップするか)
to(トークンの送り先:自分のウォレット)
deadline(トランザクションが失効するまでの秒数)
が引数として必要であることが分かる。

実際の実行結果が以下

https://basescan.org/tx/0xcd2472786eb81545c2372a471847f5eac97494a508907782acb768f15a8831bb

0.01WETHがこれに応じたAEROにSwapされたことが分かる

このSwapで0.79ドルの手数料が掛かっている

AERO→WETH(Swap)

#base_swap2.py

import json
from web3 import Web3
from datetime import datetime

baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'my_private_key'
WETH = '0x4200000000000000000000000000000000000006'
USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
AERO = '0x940181a94A35A4569E4529A3CDfB74e38FD98631'
UniV2Router = '0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24'
gas = 200000
gasPrice = 3

web3 = Web3(Web3.HTTPProvider(baseRpc))

with open('UniSwap_Router_ABI.json') as f:
   uniRouterABI = json.load(f)
uniRouterContract = web3.eth.contract(address=UniV2Router, abi=uniRouterABI)

now = int(datetime.now().timestamp()) 
nonce = web3.eth.get_transaction_count(walletAddress) 

funcSwap = uniRouterContract.functions.swapExactTokensForETH(
       web3.to_wei(20, 'ether'),
       0, 
       [AERO, WETH], 
       walletAddress, 
       now+200 
       )

tx = funcSwap.build_transaction({
   'value': 0, 
   'gas': gas,
   'gasPrice': web3.to_wei(gasPrice, 'gwei'),
   'nonce': nonce
   })

signedTx = web3.eth.account.sign_transaction(tx, walletKey)
txHash = web3.eth.send_raw_transaction(signedTx.rawTransaction)

print('Send transaction')
print(web3.to_hex(txHash))
result = web3.eth.wait_for_transaction_receipt(txHash)
status = result['status']
if status == 1:
   print('Transaction Succeeded')
else:
   print('Transaction Failed')

実行結果は以下

https://basescan.org/tx/0xac9b148e666913ba2af9e8f41ef9de40251e0c50988437afccc7c41898ddd18b

20AEROがこれに対応する量のWETHにスワップされたことが分かる。
また、手数料として0.88ドル掛かったことが分かる。

AERO→WETHが失敗(failed)する場合

AEROトークンへの操作がUniswapに許可されていない可能性がある(私はこれでしばらくハマりました)ので下記コードでApproveする。

#base_approve.py

import json
from web3 import Web3
from datetime import datetime

baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'my_private_key'
WETH = '0x4200000000000000000000000000000000000006'
USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
AERO = '0x940181a94A35A4569E4529A3CDfB74e38FD98631'
UniV2Router = '0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24'
gas = 200000
gasPrice = 3

web3 = Web3(Web3.HTTPProvider(baseRpc))

with open('UniSwap_Router_ABI.json') as f:
   uniRouterABI = json.load(f)
uniRouterContract = web3.eth.contract(address=UniV2Router, abi=uniRouterABI)

with open('Aero_Contract_ABI.json') as f:
   aeroABI = json.load(f)
aeroContract= web3.eth.contract(address=AERO, abi=aeroABI)

now = int(datetime.now().timestamp()) 
nonce = web3.eth.get_transaction_count(walletAddress) 
inf = 2**256 - 1
func = aeroContract.functions.approve(UniV2Router, inf )
tx = func.build_transaction({
   'value': 0,
   'gas': gas,
   'gasPrice': web3.to_wei(gasPrice, 'gwei'),
   'nonce': nonce
   })
signedTx = web3.eth.account.sign_transaction(tx, walletKey)
txHash = web3.eth.send_raw_transaction(signedTx.rawTransaction)
print('Send transaction')
print(web3.to_hex(txHash))
result = web3.eth.wait_for_transaction_receipt(txHash)
status = result['status']
if status == 1:
   print('Transaction Succeeded')
else:
   print('Transaction Failed')

上記ファイルと同じ場所にAero_Contract_ABI.jsonを新規作成し、AEROトークンコントラクトのABIを貼り付ける必要がある(BaseScanでAEROトークンのコントラクトアドレス
0x940181a94A35A4569E4529A3CDfB74e38FD98631
を検索して、ContractタブのCodeのABIを貼り付け)。

実行結果は以下

https://basescan.org/tx/0x9b5d556b429308406d42d635c6eb066947bf7c2a5825c4d6ca5d04a8f1bc6720

承認作業にも0.21ドル掛かっていることがわかる。

公開されているDEXでSwapしてみる(WETH→AERO)

この結果を見てみる

https://basescan.org/tx/0xa83122fe8101e0374d975772108923003f3f75613f6912c3f2beb0fa60655777

なんと、25.99ドル分のWETHが25.73ドル分のAEROにスワップされ、手数料は0.001801ドルしか掛かっていないことが分かる。

今回のSwapにおける課題

WETH→AEROでは、25.99ドル分のWETHが24.73ドル分のAEROになり手数料は0.79ドル
AERO→WETHでは、19.37ドル分のAEROが18.79ドル分のWETHになり手数料は0.88ドル
AEROの承認作業には手数料0.21ドル

一方で、公開DEXでのWETH→AEROでは25.99ドル分のWETHが25.73ドルのAEROにスワップされ、手数料は0.001801ドル

pythonで動かしたプログラムに対して、圧倒的に公開DEXの方が優秀なことがわかる。

改善策としては、
「swapExactETHForTokens」や「swapExactTokensForETH」に設定しているamountOutMinを0としているが、これは引出しトークンが0でもOKとする(何が何でもSwapする)という設定。
ここをきちんと最善の取引条件になるようなロジックに変更する。

また、今回はガス代の設定を適当に行った
gas = 200000
gasPrice = 3
が、ここをどこまで減らしてもトランザクションが通るか、という部分について攻める必要がある。

他にも色々あるんだろうとは思いますが、詳しい方、アドバイス下さい笑

この記事が気に入ったらサポートをしてみませんか?