bridgeについてプログラムを書いてみた(orbiter, owlto, stargate)@python
今回は、Base MainnetからLinea MainnetへETHをブリッジする、ということについてOrbiter Finance, Owlto Finance, Stargate Financeの3つでpythonコードを書く。
※安易な流用でGOX等が起こっても責任はとれませんのでご理解をお願いします。
Orbiter/Owlto/Stargateの違いをざっくりと
概念ではなく、プログラムを組む視点で違いをざっくりと確認しようと思います。
Orbiter Finance
並行してそれぞれを調べてみて、まずはOrbiterから話すのが順番的に良いと思いました。
簡単に言うと、Sender(ブリッジしたい人:私)とMaker(ブリッジしてくれる人)がいて、identification codeと呼ばれる、転送先ネットワークを示すコードを送金額の最小4桁に含んだ状態でMakerに送金するとブリッジ処理をしてくれる(所望のネットワーク側で資金を確認できる)、という感じ。
つまり、ただの送金トランザクションです。
また、MakerがEOA(Externally Owned Address)でコントラクトアドレスではないという点が驚きでした。当該アドレスをブロックエクスプローラーで確認するとContractタブがありません。
つまり、このEOAが何らかのコントラクトと連携しており、自動的にブリッジが実行されると推測しています。
Owlto Finance
Orbiter Financeと同じです。
厳密に言うとコンセプトは異なりますが、個人的にはpythonのコードに落とし込む作業を主眼としているので同じです。
Orbiterの後にOwltoを持ってきたのは、Owltoは公式のDocsにあまり情報が無いです。OrbiterのDocsを見て、Owltoを使ってみて、ブロックエクスプローラでトランザクションを比べたら同じだと分かる、というような理解の流れです。
identification codeについてもOrbiterと違って情報提供されておらず、GUI経由でブリッジを実行してみてブロックエクスプローラを見ないと分かりません。
一応Docsを貼っておきますが、OrbiterのDocsを見て、OrbiterとOwltoどちらも実行した結果、同じだ、と気づくことになると思います。
Stargate Finance
OrbiterとOwltoは似ていましたが(コード的には同じ)、Stargateは全く異なります。
EOAではなくコントラクトアドレスとやり取りすることになります。
今まで取り組んできたSwapと同じようなやり方、ということです。
Docsを貼っておきますが、実際にGUIでブリッジを実行してみて、ブロックエクスプローラでトランザクションを確認することでコードを書くところまでたどり着きました。
(おそらくこのDocsを理解するにはSolidityを書けないとですね。)
pythonコードを書く
Orbiter Finance
まずは、コードを貼ります。
Base MainnetからLinea Mainnetに0.001ETH送るコードです。
#orbiter.py
import json
from web3 import Web3
from datetime import datetime
baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'wallet_private_key'
orbiter = '0x80C67432656d59144cEFf962E8fAF8926599bCF8'
web3 = Web3(Web3.HTTPProvider(baseRpc))
now = int(datetime.now().timestamp())
nonce = web3.eth.get_transaction_count(walletAddress)
withholdingFee = web3.to_wei(0.00024, 'ether')
identificationCode = 9023
tx = {
'value': web3.to_wei(0.001, 'ether') + withholdingFee + identificationCode,
'gas': 200000,
'gasPrice': web3.eth.gas_price,
'nonce': nonce,
'to': orbiter
}
signedTx = web3.eth.account.sign_transaction(tx, walletKey)
txHash = web3.eth.send_raw_transaction(signedTx.raw_transaction)
print('bridge(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')
基本的には、タダの送金トランザクションです。
orbiterのEOAに、ブリッジしたい金額+withholdingFee+identificationCodeを送金しています。
withholdingFeeとは、送金先ネットワークでのガス代です。
これについてはどのネットワークではいくらかかるかの対応表は無かったのでGUI経由で実行して確認することになります(分からなくて適当に設定しても送金先ネットワークで勝手に減らされて着金します)。
実際にBase MainnetからLinea Mainnetへ0.001ETHブリッジをGUI経由でやってみます。
SENDを押すと、下記のようにWithholding FeeとIdentification Codeが表示されます。
下記でブロックエクスプローラにてトランザクションの確認ができます。
https://basescan.org/tx/0xf22d41abc7ef9d13a8ccf2b725e98ebed6a9f83948e4896fc322e9854ee94f81
ブロックエクスプローラで確認できる送金先(Orbiter Finance: Bridge 1)を確認すると、Contractのタブが無いことが確認できます(EOAだからです)。
https://basescan.org/address/0x80c67432656d59144ceff962e8faf8926599bcf8
なので、ABIファイルも必要ありません。ただ送金するだけです。
Owlto Finance
コードを示します(Base MainnetからLinea Mainnetへ0.001ETH送金)。
Orbiterのものと送金先、identificationCode、withholdingFeeが異なるだけです。
#orbiter.py
import json
from web3 import Web3
from datetime import datetime
baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'wallet_private_key'
owlto = '0x5e809A85Aa182A9921EDD10a4163745bb3e36284'
web3 = Web3(Web3.HTTPProvider(baseRpc))
now = int(datetime.now().timestamp())
nonce = web3.eth.get_transaction_count(walletAddress)
withholdingFee = web3.to_wei(0.00038, 'ether')
identificationCode = 7
tx = {
'value': web3.to_wei(0.001, 'ether') + withholdingFee + identificationCode,
'gas': 200000,
'gasPrice': web3.eth.gas_price,
'nonce': nonce,
'to': owlto
}
signedTx = web3.eth.account.sign_transaction(tx, walletKey)
txHash = web3.eth.send_raw_transaction(signedTx.raw_transaction)
print('bridge(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')
withholdingFeeについてはGUIで実行すれば分かります。
Destination TX costがwithholdingFeeです。
ブロックエクスプローラで見ると下記です。
https://basescan.org/tx/0xa4e48f863e9559886e8b776b78eeb8ab2cbb6fd0a2ebd3422d68fcbe14a3538e
また、identificationCodeについては、Docsにも明示されていないので、ブロックエクスプローラから読み取ります。
「7」だと分かります(Linea Mainnetに送る場合)。
Stargate Finance
コードを示します。
Base MainnetからLinea Mainnetに0.001ETH送るものです。
使用する場合は、sendParamsの2番目の右詰めになっているアドレスを自分のwalletアドレスに変えてください。
また、Stargate: Pool NativeコントラクトのABIファイルをpythonコードと同じディレクトリに置いてください。
#stargate.py
import json
from web3 import Web3
from datetime import datetime
baseRpc = "https://mainnet.base.org"
walletAddress = 'my_wallet_address'
walletKey = 'wallet_private_key'
stargate = '0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7'
web3 = Web3(Web3.HTTPProvider(baseRpc))
with open('stargate_ABI.json') as f:
stargateABI = json.load(f)
stargateContract = web3.eth.contract(address=stargate, abi=stargateABI)
now = int(datetime.now().timestamp())
nonce = web3.eth.get_transaction_count(walletAddress)
sendParams = [
30183,
'0x000000000000000000000000f69fd1acae36de01e61e722cdf44b5bf9baadd5c',
web3.to_wei(0.001, 'ether'),
web3.to_wei(0.000995, 'ether'),
web3.to_bytes(hexstr='0x'),
web3.to_bytes(hexstr='0x'),
web3.to_bytes(hexstr='0x01')
]
quote_send = stargateContract.functions.quoteSend(
sendParams,
False
)
quote_result = quote_send.call()
native_fee = quote_result[0]
lz_token_fee = quote_result[1]
print('native_fee = ')
print(native_fee)
print('lz_token_fee = ')
print(lz_token_fee)
feeParams = [
native_fee,
lz_token_fee
]
funcBridge = stargateContract.functions.send(
sendParams,
feeParams,
web3.to_checksum_address(walletAddress)
)
tx = funcBridge.build_transaction({
'value': web3.to_wei(0.001, 'ether') + native_fee,
'gas': 200000,
'gasPrice': web3.eth.gas_price,
'nonce': nonce
})
signedTx = web3.eth.account.sign_transaction(tx, walletKey)
txHash = web3.eth.send_raw_transaction(signedTx.raw_transaction)
print('bridge 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')
Docsを読んでも分からなかったので、GUIでブリッジしてみてブロックエクスプローラから解読しました。
ブロックエクスプローラで確認すると、
https://basescan.org/tx/0xc3349d153123f4b0e5d109829b7562fbbb915a1673052c1f630b77c8823b3436
Stargate: Pool Nativeコントラクトのsend関数を使っていることが分かります。
Decode Input Dataで、
各パラメータの説明はDocsにあります。
dstEid(destination endpoint ID)がOrbiterやOwltoで言うdistinationCodeです。各ネットワークの対応表もありませんので実際にGUIで実行して確かめることになります。Lineaは30183だと解釈しました。
SendParam構造体のメンバーについては、Decode Input Dataで確認されたものを流用した。
ただ、fee構造体のnativeFeeについては、GUIでのブリッジ実行のたびに計算されているようで、変わることがあった。
これについては、ひとまずブロックエクスプローラで得られた値を入れてやってみて、エラーした場合はChatGPTにエラー内容とStargate: Pool NativeコントラクトのABIを渡すと解決に近づくと思います。
→やりとりの中でquoteSend関数でnativeFeeを動的に確認できることが分かりました(コードはその実装になっています)。