web3.py使用笔记
2025-04-11 10:19 第二个卿老师 阅读(120) 评论(0) 收藏 举报平时测试智能合约用到了web3.py,作为新手主要出问题的是根据abi文件构造交易这块,格式匹配不上,最近有时间,就结合自己经验总结下:
Solidity ABI类型 ↔ Python类型对照表
1. 基础类型
| Solidity 类型 | Python 类型 | 示例 & 注意事项 |
|---|---|---|
| uint8 ~ uint256 | int | 123 → 123(需确保数值不超过类型范围,如 uint8 最大为 255) |
| int8 ~ int256 | int | -42 → -42 |
| bool | bool | True/False → True/False |
| address | ChecksumAddress | 必须使用校验和地址:Web3.to_checksum_address("0xabc...") |
| bytes(动态长度) | bytes 或 Hex字符串 | b"hello" 或 "0x68656c6c6f" |
| bytes1~bytes32 | bytes 或 Hex字符串 | 长度必须严格匹配:bytes2 → 2字节(如 b"\x01\x02" 或 "0x0102") |
| string | str | "Hello Web3" → "Hello Web3" |
2. 数组类型
| Solidity 类型 | Python 类型 | 示例 & 注意事项 |
|---|---|---|
| T[k](静态数组) | list | uint8[3] → [1, 2, 3](长度必须为 3) |
| T[](动态数组) | list | address[] → ["0xAbc...", "0xDef..."](支持空列表 []) |
| T[][](嵌套数组) | list of list | uint256[][] → [[1, 2], [3]] |
3. 复杂类型
| Solidity 类型 | Python 类型 | 示例 & 注意事项 |
|---|---|---|
| 结构体(Struct) | dict 或 tuple | { "id": 1, "name": "Alice" } 或 (1, "Alice")(字段顺序必须与 ABI 一致) |
| 元组(返回值) | tuple | 多返回值会自动转为元组:(123, "abc", True) |
| 枚举(Enum) | int | 映射为整数,如 MyEnum.VALUE → 0(需手动转换语义) |
| 定点数(fixed/ufixed) | 手动处理 | 建议用字符串或整数(如 "123.45" 或 12345,需自行处理精度) |
关键注意事项
整数范围
uint8最大值为 255,若传入 256 会报错。- Python 的
int无大小限制,但需符合 Solidity 类型范围。
地址校验和
直接传递普通字符串地址会报错,必须转换:
address = Web3.to_checksum_address("0xabc...")
字节类型长度
bytes32 必须严格 32 字节:
# 正确写法(32字节 Hex)
my_bytes = "0x" + "00" * 32 # 全零的 bytes32
结构体字段顺序
如果用 tuple 传递结构体,字段顺序必须与 ABI 定义完全一致:
# Solidity struct { uint a; string b; }
args = (1, "test") # 正确
args = ("test", 1) # 错误!
实战示例
以下是我一个实践项目的合约abi与对应部分代码:
# 合约abi
"abi": [
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "nftOwner",
"type": "address"
},
{
"internalType": "string",
"name": "handle",
"type": "string"
},
{
"internalType": "string",
"name": "imageURI",
"type": "string"
},
{
"internalType": "address",
"name": "followModule",
"type": "address"
},
{
"internalType": "bytes",
"name": "followModuleInitData",
"type": "bytes"
},
{
"internalType": "string",
"name": "followNFTURI",
"type": "string"
},
{
"internalType": "address payable",
"name": "inviter",
"type": "address"
}
],
"internalType": "struct DataTypes.CreateProfileData[]",
"name": "vars",
"type": "tuple[]"
}
],
"name": "batchCreateAccount",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
]
##################################################
#################测试脚本#########################
from web3 import Web3
# 连接以太坊节点
w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3"))
key4 = '53......575'
account = Account.from_key(private_key=key)
addr = account.address
# 定义合约
hbo_contract = w3.eth.contract(address=contract_addr, abi=abi)
# 合约交互方法
def buy_tba(key, num=1, invite_tba='0x0000000000000000000000000000000000000000', type='old'):
create_profile_data = {}
create_profile_data['tokenAddress'] = '0x0000000000000000000000000000000000000000' # NFT合约地址
create_profile_data['tokenId'] = 0 # NFT tokenID
create_profile_data['chainId'] = 0 # 链ID
owner_addr = w3.to_checksum_address('0xcf.....5') # 代理地址
create_profile_data['nftOwner'] = owner_addr # nft所有者
create_profile_data['handle'] = 'NFT_Name' # 默认 NFT名#tokenId
create_profile_data['imageURI'] = 'https://storage.xxx/files/my.jpg'
create_profile_data['followModule'] = followModule # 依赖合约地址
create_profile_data['followModuleInitData'] = '0x' # 默认传 []
create_profile_data['followNFTURI'] = 'https://storage.xxx/files/my.jpg'
create_profile_data['inviter'] = w3.to_checksum_address(invite_tba) # 邀请者地址
gas = 2028431 * num
base_price = Decimal("0.006")
if type == 'new':
base_price = base_price / Decimal('2')
tba_fee = base_price * Decimal(num) # 价格
data = [create_profile_data]
print("data:", data)
if num > 1: # #创建多个
for i in range(num-1):
data.append(create_profile_data)
try:
hbo_txn = hbo_contract.functions.batchCreateAccount(data).build_transaction({
'gas': gas, #(1TBA/2000000000)
'maxFeePerGas': Web3.to_wei('0.107000010499', 'gwei'),
'maxPriorityFeePerGas': Web3.to_wei('0.107000010499', 'gwei'),
'value': Web3.to_wei(tba_fee, 'ether'),
'nonce': w3.eth.get_transaction_count(addr),
})
print("构造交易:", hbo_txn)
signed_txn = w3.eth.account.sign_transaction(hbo_txn, private_key=key)
print("签署交易", signed_txn)
txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
print("广播交易", w3.to_hex(txn_hash))
w3.eth.wait_for_transaction_receipt(txn_hash)
print("上链成功", w3.to_hex(txn_hash))
except Exception as e:
print(e)
return False
从代码上看,入参构造算是比较麻烦的,其中部分打印如下:
data: [{'tokenAddress': '0x0000000000000000000000000000000000000000', 'tokenId': 0, 'chainId': 0, 'nftOwner': '0xcF0e1CAf97DD1F49f4f9eff3096fbebE0caeD975', 'handle': 'PoPP-Spacefarer', 'imageURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'followModule': '0x63eeA5D273ce497FD2D4Ab464E835DfCdBbB', 'followModuleInitData': '0x', 'followNFTURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'inviter': '0x0000000000000000000000000000000000000000'}, {'tokenAddress': '0x0000000000000000000000000000000000000000', 'tokenId': 0, 'chainId': 0, 'nftOwner': '0xcF0e1CAf97DD1F49f4f9eff3096fbebE0ca', 'handle': 'PoPP-Spacefarer', 'imageURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'followModule': '0x63eeA5D273ce497FD2D4Ab464E835DfCdB', 'followModuleInitData': '0x', 'followNFTURI': 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', 'inviter': '0x0000000000000000000000000000000000000000'}]
构造交易 {'chainId': 167009, 'gas': 4056862, 'maxFeePerGas': 107000010, 'maxPriorityFeePerGas': 107000010, 'value': 12000000000000000, 'nonce': 217, 'to': '0x226EDbC009611a974D48D9bBB5745324855', 'data': '0xcb5e08b900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf0e1caf97dd1f49f4f9eff3096fbebe0caed9750000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000063eea5d273ce497fd2d4ab464e835dfcdbbb484f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f506f50502d537061636566617265720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a7067000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a706700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf0e1caf97dd1f49f4f9eff3096fbebe0caed9750000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000063eea5d273ce497fd2d4ab464e835dfcdbbb484f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f506f50502d537061636566617265720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a7067000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003368747470733a2f2f73746f726167652e706f70702e636c75622f66696c65732f506f50502d537061636566617265722e6a706700000000000000000000000000'}
由上可知一些注意点:
- 如0x地址需要补齐40个0,即"0x0000000000000000000000000000000000000000"
- 正常地址要使用to_checksum_address方法
- 关于bytes类型,这里填的是'0X',通常"0x"开头的16进制字符串,web3.py会自动将其识别为空bytes数据,当然也可以直接填写b''.
- 最后有个vars参数,我起初以为要构造一个vars的字典,后面发现在ABI编码中,字段名称只是为了增强可读性,不参与底层编码。对于结构体参数,ABI编码是根据各个组件的顺序和类型进行编码的,所以这里传值用列表套列表也行,如下:
[['0x0000000000000000000000000000000000000000', 0, 0, '0xcF0e1CAf97DD1F49f4f9eff3096fbebE0caeD975', 'PoPP-Spacefarer', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x63eeA5D273ce497FD2D4Ab464E835DfC', b'aaaaa', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x0000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000', 0, 0, '0xcF0e1CAf97DD1F49f4f9eff3096', 'PoPP-Spacefarer', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x63eeA5D273ce497FD2D4Ab464E835Dfc', b'aaaaa', 'https://storage.popp.club/files/PoPP-Spacefarer.jpg', '0x0000000000000000000000000000000000000000']]
浙公网安备 33010602011771号