pythonでswapするコードの正解を理解しながら自分の型に落とし込む作業

様々なDEXにおいて、pythonでプログラムからのswapに試みたが、結局何もかも上手くいかなかったので、まずは真似ることから入ってみようと思う。
Ambient FinanceのDEXで動くswapプログラムをhttps://x.com/leidream1さんが公開(https://github.com/reidream/Ambient_userCmd/blob/master/userCmd.md)してくださっているので、これをただコピペするのではなく、理解しながら自分のコードの書き方に落とし込んでいこうと思います。

この過程をたどることで、別のDEXでも同様の流れでswapプログラムを組むことが可能になるのではないか、という意図です。

最終的な私のコード

完成したコードは以下です。
ScrollネットワークでAmbient Financeのswapで0.1USDT→USDCに交換するプログラムです。

#scroll_ambient.py

import json
from web3 import Web3
from eth_abi import encode

scrollRpc = "https://rpc.scroll.io"
myWallet = 'my_wallet_address'
privateKey = 'my_private_key'

gas = 200000
gas_plus = 1

web3 = Web3(Web3.HTTPProvider(scrollRpc))
if not web3.is_connected():
    print("Error: Unable to connect to the Scroll Network")
    exit()


crocswapdex = '0xaaaaAAAACB71BF2C8CaE522EA5fa455571A74106'
lower_crocswapdex = '0xaaaaaaaacb71bf2c8cae522ea5fa455571a74106'
#to confirm row address is equal to checksum address
checksum_crocswapdex = web3.to_checksum_address(lower_crocswapdex)
print('crocswapdex = ')
print(crocswapdex)
print('checksum_crocswapdex = ')
print(checksum_crocswapdex)
##

USDC = '0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4'
USDT = '0xf55BEC9cafDbE8730f096Aa55dad6D22d44099Df'

#parameter
base = USDC
quote = USDT
poolIdx = 420
isBuy = False
inBaseQty = False
qty = 100000
tip = 0
limitPrice = 65000
minOut = 98500
settleFlags = 0

swap_params = encode(
    ['address', 'address', 'uint256', 'bool', 'bool', 'uint256', 'uint256', 'uint256', 'uint256', 'uint8'],
    [base, quote, poolIdx, isBuy, inBaseQty, qty, tip, limitPrice, minOut, settleFlags]
)
print("swap_param = ")
print(swap_params)

with open('CrocSwapDex_ABI.json') as f:
   crocswapdexABI = json.load(f)
crocswapdexContract = web3.eth.contract(address=crocswapdex, abi=crocswapdexABI)

nonce = web3.eth.get_transaction_count(myWallet) 
print("nonce = ")
print(nonce)
gasPrice = web3.eth.gas_price
print("gasPrice = ")
print(gasPrice)

#create tx
userCmdfunction = crocswapdexContract.functions.userCmd(1, swap_params)
tx = userCmdfunction.build_transaction({
    "from": myWallet,
    'gas': gas,
    'gasPrice': web3.eth.gas_price + web3.to_wei(gas_plus, 'gwei'),
    'nonce': nonce
})

#sign to tx
signedTx = web3.eth.account.sign_transaction(tx, privateKey)

#send tx
txHash = web3.eth.send_raw_transaction(signedTx.raw_transaction)

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

このファイルと同じフォルダに、CrocSwapDex_ABI.jsonという名前のファイルを作成し、CrocSwapDexのABIをコピペしました。

https://scrollscan.com/address/0xaaaaaaaacb71bf2c8cae522ea5fa455571a74106#code

解説(私の理解とコードを書く流れ)

では、上記のコードをどのように書いていったかを説明します。

DEXコントラクト情報取得

今回の題材はAmbient Financeです。
USDT→USDCへのスワップを実際にやってみます。

そして、トランザクションを確認します。

https://scrollscan.com/tx/0x32f4037023f4a71a5bef1c70ebe150471ba0047aabe7b7491bcb7d8783593a75

ineracted With(To):にあるのがAmbient Financeのコントラクトです。

これで、CrocSwapDexのコントラクトアドレスが分かりましたので下記のコードが書けます。

また、USDTやUSDCのページに飛ぶと、USDTトークンおよびUSDCトークンの情報を見ることができます。トークンのコントラクトアドレスも分かります。

これで、コードの下記の部分が書けます。

今度はCrocSwapContractのページに飛んで、ABIを取得します。ContractタブのCodeの下の方です。CrocSwapDex_ABI.jsonにコピペです。

Ambient FinanceのCrocSwapDexのどの関数で、どんな引数でスマートコントラクトが実行されているのかを確認します。

トランザクション情報(https://scrollscan.com/tx/0x32f4037023f4a71a5bef1c70ebe150471ba0047aabe7b7491bcb7d8783593a75)のInput Data:を見ると、

Function: userCmd(uint16 callpath, bytes cmd)
MethodID: 0xa15112f9
とあります。
ここから、CrocSwapDexコントラクトに含まれるuserCmdという関数をcallpathとcmdという2つの引数を渡して実行すればよいことが分かります。
また、関数のIDが0xa15112f9であるようです。

CrocSwapDexコントラクトのページ

https://scrollscan.com/address/0xaaaaaaaacb71bf2c8cae522ea5fa455571a74106

のContract→Write Contractに当該関数が存在することが分かります。IDも同じです。

これだけでは、callpath、cmdの内容が分かりませんのでAmbient FinanceのDocsに目を通します。

Cold Pathの項目に、お目当ての関数の詳細が説明してありました。
まず、swapはcallpath index 1で呼び出される、とのことですのでcallpathは1で良いことが分かります。

また、cmdにはabi.encode(…)を入れればよいことが分かります。
各パラメータの意味も書いてありました。

poolIdxとsettleFlagsの扱いについては下記にありました。

これで、下記の部分を書くことができました。

checksum(チェックサム)付きアドレス

この部分の説明を飛ばしましたが、結論、最初の1行のみ書いておけばOKです。その他は消していただいてもかまいません(私が確認用に使っただけです)。

web3.to_checksum_address(hoge)とすると、hogeをチェックサム付きアドレスに変換して返します。

イーサリアムのアドレスは大文字と小文字の区別がなく、crockswapdexとlower_crocswapdexの区別はありません。ただ、アドレスの打ち間違えによって資産をGOXするなどの操作を防ごう、という目的である一定の規則に則って(web3.to_checksum_address)、これに当てはまるアルファベットの部分を大文字にしよう、というのがチェックサム付きアドレスです。
イーサリアム的には大文字と小文字の区別は無いのでこの変換は問題ありません。
ただ、チェックサム付きアドレスを用いるとアドレスを打ち間違った場合に、大文字になるべき場所が変化するのでアドレスの打ち間違いを検知できる、ということです。

下記のサイトが分かりやすかったです。

コードの方では、あえてアルファベットを小文字のアドレス(lower_crocswapdex)を作って、これをweb3.to_checksum_addressに入れることでチェックサム付きアドレス(crocswapdex)と同じアドレス(checksum_crocswapdex)が生成されることを確認した、ということです。なので最初の1行のみで問題ないのです。

実行結果でも、crocswapdexとchecksum_crocswapdexの一致が確認できます。

ABIエンコード

あとは、abi.encode(…)の部分。
これについては、型と値を引数として渡すのか・・・ぐらいにしか理解できませんでした。

コードで言うと下記の部分です。

実行結果でもそれっぽい値が得られています。

その他について

その他は基本的に、別のnoteでも取り上げているスマートコントラクトと同じノリです。毎回やる儀式、みたいな感じです。

特記としては、
・gas_plus = 1
・+web3.to_wei(gas_plus, 'gwei')
は、トランザクションが通りにくかったのでガス価格を大きく設定した、というだけで本質的な意味はないです。
むしろこのコードのまま実行するとガス代が高く取られるので実用的にはやめた方が良いです(あくまで今回は実験なのでトランザクションが制限時間:2分内に承認されず、コードの正しさの確認に時間がかかるのが嫌だったのでこうしました)。

my_wallet_addressは自身のパブリックアドレス
my_private_keyはその秘密鍵
です。

今後

今回の作業で、ひとまず私がいつも書く形のコードに動くコードを落とし込むことができたので、
この流れに則って他のDEXのswapにも挑戦してみます。
コードを無料公開してくださっているhttps://x.com/leidream1さんには本当に感謝です。

いいなと思ったら応援しよう!