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を動的に確認できることが分かりました(コードはその実装になっています)。




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