1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| import json from web3 import Web3
def addr_to_32_bytes(addr: str) -> bytes: """ 将一个标准的以太坊地址 (20字节) 转换为 32字节 的表示形式。
Args: addr (str): 输入的以太坊地址字符串,例如 "0x..."
Returns: bytes: 转换后的 32字节 bytes 对象。 """ hex_20 = Web3.to_checksum_address(addr)[2:] padded = hex_20.zfill(64) return bytes.fromhex(padded)
def main(): sepolia_rpc_url = "https://blockchain.googleapis.com/v1/projects/constant-rig-425116-j4/locations/us-central1/endpoints/ethereum-sepolia/rpc?key=AIzaSyCjLfQQq3AToHjT5OF62yXzIrMjh0Nreyk" w3 = Web3(Web3.HTTPProvider(sepolia_rpc_url))
if not w3.is_connected(): print("错误:无法连接到以太坊节点!请检查您的 RPC URL。") return
print(f"成功连接到链 ID: {w3.eth.chain_id}")
private_key = "0x你的私钥
# 根据私钥创建账户对象 owner = w3.eth.account.from_key(private_key) print(f"操作账户地址: {owner.address}")
# --- 3. 定义常量和地址 --- SEPOLIA_USDT0 = Web3.to_checksum_address("0xc4DCC311c028e341fd8602D8eB89c5de94625927") SEPOLIA_USDT0_OAPP = Web3.to_checksum_address("0xc099cD946d5efCC35A99D64E808c1430cEf08126") RECEIVER_EID = 40374 # 目标链的 LayerZero 端点ID
# --- 4. 定义合约 ABI --- # ERC20 的最小化 ABI,包含 approve 和 balanceOf ERC20_ABI = json.dumps([ {"constant": False, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "payable": False, "stateMutability": "nonpayable", "type": "function"}, {"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"} ])
# OFTAdapter 实现合约的正确 ABI (从 Etherscan 获取) # 注意:我们与代理合约地址交互,但需要使用实现合约的ABI OFT_ADAPTER_ABI = """ [ {"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"},{"internalType":"bool","name":"_payInLzToken","type":"bool"}],"name":"quoteSend","outputs":[{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"fee","type":"tuple"}],"stateMutability":"view","type":"function"}, {"inputs":[{"components":[{"internalType":"uint32","name":"dstEid","type":"uint32"},{"internalType":"bytes32","name":"to","type":"bytes32"},{"internalType":"uint256","name":"amountLD","type":"uint256"},{"internalType":"uint256","name":"minAmountLD","type":"uint256"},{"internalType":"bytes","name":"extraOptions","type":"bytes"},{"internalType":"bytes","name":"composeMsg","type":"bytes"},{"internalType":"bytes","name":"oftCmd","type":"bytes"}],"internalType":"struct SendParam","name":"_sendParam","type":"tuple"},{"components":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"lzTokenFee","type":"uint256"}],"internalType":"struct MessagingFee","name":"_fee","type":"tuple"},{"internalType":"address","name":"_refundAddress","type":"address"}],"name":"send","outputs":[{"components":[{"internalType":"bytes32","name":"guid","type":"bytes32"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"uint32","name":"eid","type":"uint32"}],"internalType":"struct MsgReceipt","name":"","type":"tuple"}],"stateMutability":"payable","type":"function"} ] """
# --- 5. 创建合约实例 --- usdt0_contract = w3.eth.contract(address=SEPOLIA_USDT0, abi=ERC20_ABI) oft_adapter_contract = w3.eth.contract(address=SEPOLIA_USDT0_OAPP, abi=OFT_ADAPTER_ABI)
# --- 6. 授权 (Approve) --- # 授权 OFTAdapter 合约可以从我们的账户中提取指定数量的 USDT amount_to_send = w3.to_wei(1, 'ether') # 将 1 个 ether 单位转换为 wei (10^18)
print(f"准备授权合约 {SEPOLIA_USDT0_OAPP} 使用 {w3.from_wei(amount_to_send, 'ether')} USDT...")
# 构造 approve 交易 approve_tx = usdt0_contract.functions.approve( SEPOLIA_USDT0_OAPP, amount_to_send ).build_transaction({ 'from': owner.address, 'nonce': w3.eth.get_transaction_count(owner.address), 'gas': 200000, # Gas limit,对于 approve 通常足够 'gasPrice': w3.eth.gas_price })
# 签名并发送交易 signed_approve_tx = w3.eth.account.sign_transaction(approve_tx, private_key) approve_tx_hash = w3.eth.send_raw_transaction(signed_approve_tx.raw_transaction)
# 等待交易确认 print(f"Approve 交易已发送, 哈希: {approve_tx_hash.hex()}") w3.eth.wait_for_transaction_receipt(approve_tx_hash) print("✅ Approve 交易成功!")
# --- 7. 准备 LayerZero send 参数 --- # `Options.newOptions().addExecutorLzReceiveOption(0, 0).toBytes()` 在JS中生成的字节串 # 这个值表示在目标链上不为执行交易额外提供gas (gas=0, value=0) options = bytes.fromhex("00030100110100000000000000000000000000000000")
# 构建 send 函数需要的参数结构体,使用字典方便阅读 send_params = { "dstEid": RECEIVER_EID, "to": addr_to_32_bytes(owner.address), # 将接收者地址转换为 bytes32 "amountLD": amount_to_send, "minAmountLD": amount_to_send, "extraOptions": options, "composeMsg": b"", # 空字节字符串 "oftCmd": b"", # 空字节字符串 }
# --- 8. 获取跨链费用 (quoteSend) --- print("\n正在获取跨链费用...") # 调用合约函数时,如果参数是结构体,web3.py 需要传入一个列表或元组 fee_struct = oft_adapter_contract.functions.quoteSend( list(send_params.values()), # 将字典的值按顺序转为列表 False # _payInLzToken 参数 ).call()
# fee_struct 是一个元组,格式为 (nativeFee, lzTokenFee) native_fee = fee_struct[0] print(f"获取到预估的原生代币费用: {w3.from_wei(native_fee, 'ether')} ETH")
# --- 9. 执行跨链发送 (send) --- print("\n准备执行跨链发送...")
# 构造 send 交易 send_tx = oft_adapter_contract.functions.send( list(send_params.values()), # 第一个参数:_sendParam 结构体 fee_struct, # 第二个参数:_fee 结构体 owner.address # 第三个参数:_refundAddress ).build_transaction({ 'from': owner.address, 'value': native_fee, # 交易的 msg.value,用于支付跨链费用 'nonce': w3.eth.get_transaction_count(owner.address), 'gas': 3500000, # 跨链交易通常需要更高的 gas limit 'gasPrice': w3.eth.gas_price })
# 签名并发送交易 signed_send_tx = w3.eth.account.sign_transaction(send_tx, private_key) send_tx_hash = w3.eth.send_raw_transaction(signed_send_tx.raw_transaction)
# 等待交易确认 print(f"Send 交易已发送, 哈希: {send_tx_hash.hex()}") w3.eth.wait_for_transaction_receipt(send_tx_hash) print("✅ 跨链 Send 交易成功!")
if __name__ == "__main__": main()
|