概要
Uniswapはその誕生以降、様々なクローンが様々なチェーンで作られています。そのほとんどがUniswapV2に対応しており、適宜アドレスを変えるだけで同じように扱えます。
今回はgas代が安く、Infuraなどの登録なしでRPCが使える、PolygonチェーンのQuickswapを例にトークンの価格取得とスワップの実装を行います。
DAI/ETHの価格取得
はじめにUniswapのfactoryとrouterのコントラクトインスタンスを取得します。factoryは、DAI-ETHのペアコントラクトのアドレスを取得するために必要です。routerはスワップする際に必要になります。
from web3 import Web3,HTTPProvider
import json, os, time
RPC_URL = "https://polygon-rpc.com" # Polygon Matic RPC address
FACTORY_ADDRESS = "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32" # QuickSwap UniswapV2Factory
ROUTER_ADDRESS = "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff" # QuickSwap UniswapV2Router02
DAI_ADDRESS = "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" # DAI on polygon
WETH_ADDRESS ="0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"# WETH on polygon
with open("abis/IUniswapV2Pair.json", "r") as f:
IUniswapV2Pair =json.loads(f.read())
with open("abis/IUniswapV2Factory.json", "r") as f:
IUniswapV2Factory =json.loads(f.read())
with open("abis/IUniswapV2Router02.json", "r") as f:
IUniswapV2Router02 =json.loads(f.read())
with open("abis/IERC20.json", "r") as f:
IERC20 =json.loads(f.read())
w3 = Web3(HTTPProvider(RPC_URL))
factory = w3.eth.contract(address = FACTORY_ADDRESS , abi = IUniswapV2Factory["abi"])
router = w3.eth.contract(address = ROUTER_ADDRESS , abi = IUniswapV2Router02["abi"])
FACTORY_ADDRESS
, ROUTER_ADDRESS
はこのページ、DAIやWETHのコントラクトアドレスはこのページから確認できます。ABIファイルは以下のリンクからダウンロードできます。
次にDAI-ETHペアコントラクトのアドレスをgetPair
メソッドで取得し、そのアドレスを使ってコントラクトインスタンスを作ります。
eth_dai_address = factory.functions.getPair(DAI_ADDRESS , WETH_ADDRESS).call() # eth_daiのコントラクトアドレスを取得
eth_dai = w3.eth.contract(address = eth_dai_address, abi = IUniswapV2Pair["abi"]) # eth_daiのコントラクトインスタンスを取得
print("token0 = ", eth_dai.functions.token0().call())
print("token1 = ", eth_dai.functions.token1().call())
出力結果
token0 = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619
token1 = 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063
ペア通貨はそれぞれtoken0
, token1
と呼ばれます。今回はWETHがtoken0
, DAIがtoken1
に対応していることがわかります。
次に価格を取得します。
reserves = eth_dai.functions.getReserves().call()
print("token0 reserver = ", reserves[0]*10**(-18))
print("token1 reserver = ", reserves[1]*10**(-18))
print(" block.timestamp = ", reserves[2])
print("token0 price = ", reserves[1]/reserves[0]) # DAI/ETH price in this case
出力結果
token0 reserver = 3025.605972350233
token1 reserver = 6015799.838675748
block.timestamp = 1653458719
token0 price = 1988.2958632590182
reserverとはプールされているトークン量です。ETHもDAIも最小単位に対し小数点が18桁目にあるので、$10^{-18}$を掛けています。reservers
の第3要素は値を取得したブロックのタイムスタンプになっています。reserves[1]/reserves[0]
がtoken0
1単位あたりのtoken1
の量、この場合はDAI/ETHの価格になっています。
DAI→WETHのスワップ
DAI→WETHのスワップを実装してみます。あらかじめウォレットにいくらかDAIを用意しておいて下さい。
approve
スワップはUniswapのRouterコントラクトが、ウォレットからDAIをtransferFrom
メソッドで引き抜き、ETHに交換します。そのためスワップに先立って、Routerコントラクトのアドレスに対しapprove
を行う必要があります。
my_address = os.getenv("ADDRESS")
my_private_key = os.getenv("PRIVATE_KEY")
dai = w3.eth.contract(address = DAI_ADDRESS, abi = IERC20["abi"])
dai_amount = Web3.toWei("0.1", "ether") # スワップしたい量
nonce = w3.eth.get_transaction_count(my_address)
tx_approve = dai.functions.approve(ROUTER_ADDRESS, dai_amount).buildTransaction(\
{'chainId': 137, \
'type': 2, \
'gas': 10**6, \
'maxFeePerGas': w3.toWei('100', 'gwei'),\
'nonce': nonce, })
signed_tx_approve = w3.eth.account.sign_transaction(tx_approve, private_key = my_private_key)
w3.eth.send_raw_transaction(signed_tx_approve.rawTransaction)
swap
いよいよスワップです。
スワップのメソッドはいくつかありますが、今回はswapExactTokensForTokens
を使います。
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
これはamountIn
で指定した量のトークン(path[0]
で指定されたもの)をトークン(path[path.length - 1]
で指定されたもの)になるべく多く交換し、アドレスto
に送金するメソッドです。
path
はスワップの経路を指定します。今回はDAIとWETHのペアプールが存在するので直接スワップすることができて、path[0]
にDAIのコントラクトアドレス、path[1]
にETHのコントラクトアドレスを指定すれば良いです。amountOutMin
は交換後の最低トークン量を指定します。この量を下回るならばrevertされます。deadline
には取引の期限を指定します。
amountIn = dai_amount
amountOutMin =int(reserves[0]/reserves[1]*amountIn*0.99) # 1%以内のスリッページで実行
path = [DAI_ADDRESS, WETH_ADDRESS] # daiからwethへ両替することを指定
to = my_address
deadline = int(time.time() + 60*10) # 10分以内に実行されなければrevert
nonce = w3.eth.get_transaction_count(my_address)
tx_swap = router.functions.swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline).buildTransaction(\
{'chainId': 137, \
'type': 2, \
'gas': 10**6, \
'maxFeePerGas': w3.toWei('100', 'gwei'),\
'nonce': nonce, })
signed_tx_swap = w3.eth.account.sign_transaction(tx_swap, private_key = my_private_key)
w3.eth.send_raw_transaction(signed_tx_swap.rawTransaction)
以上でスワップが完了します。
参考文献