在区块链应用开发中,与以太坊生态的交互是常见需求,尤其是ERC20代币的转账操作,ERC20是以太坊上最主流的代币标准,稳定币(如USDT)、治理代币(如UNI)等都基于它实现,本文将详细介绍如何使用Python语言,结合web3.py库,实现ERC20代币的转账功能,涵盖环境准备、代码实现、错误处理及注意事项。
环境准备:开发工具与依赖安装
在开始编码前,需确保开发环境满足以下要求:
安装Python
推荐使用Python 3.8及以上版本,可通过官网或包管理器安装。
安装web3.py库
web3.py是与以太坊节点交互的核心Python库,提供了调用智能合约、发送交易等功能,通过pip安装:
pip install web3
准备以太坊节点
与以太坊网络交互需要连接到以太坊节点,常见方式有:
- Infura:公共节点服务(需注册获取项目ID),适合测试和开发。
- Alchemy:另一类公共节点服务,提供稳定接口。
- 本地节点:运行Geth或OpenEthereum客户端,适合本地调试。
本文以Infura为例,需在Infura官网创建项目,获取HTTPS节点地址(格式:https://mainnet.infura.io/v3/YOUR_PROJECT_ID)。
准备钱包与私钥
ERC20代币转账需要以太坊账户,需提前准备:
- 钱包地址:接收代币的目标地址。
- 发送方私钥:用于签名交易(注意:私钥需妥善保管,切勿泄露)。
- ETH余额:发送方地址需有足够ETH支付矿工费(Gas),因为ERC20转账本质是以太坊上的合约调用交易。
ERC20代币转账核心原理
ERC20代币的转账并非直接转移代币,而是通过调用代币合约的transfer函数实现的,代币合约需遵循ERC20标准,定义了transfer函数的接口:
function transfer(address recipient, uint256 amount) external returns (bool);
参数说明:
recipient:接收代币的地址。amount:转账代币数量(注意:ERC20代币通常有18位小数,需按实际精度处理)。
调用transfer函数时,需向以太坊网络发送一笔交易,交易中包含:
- 接收方地址(代币合约地址)。
- 调用的函数名(
transfer)。 - 函数参数(接收地址和转账数量)。
- 发送方签名(由私钥生成)。
Python实现ERC20代币转账
初始化Web3连接
使用Web3.py连接到以太坊节点:
from web3 import Web3
# 初始化Web3实例
infura_url = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
w3 = Web3(Web3.HTTPProvider(infura_url))
# 检查连接是否成功
print(f"连接状态: {w3.is_connected()}")
定义代币合约ABI与地址
ERC20合约的ABI(Application Binary Interface)定义了函数接口,transfer函数的ABI片段如下:
erc20_abi = [
{
"constant": False,
"inputs": [
{"name": "_to", "type": "address"},
{"name": "_value", "type": "uint256"}
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
}
]
代币地址需提前确认,例如USDT(主网)地址为0xdAC17F958D2ee523a2206206994597C13D831ec7。
加载合约实例
使用合约地址和ABI加载合约实例:
token_address = "0xdAC17F958D2ee523a2206206994597C13D831ec7" # USDT地址 contract = w3.eth.contract(address=token_address, abi=erc20_abi)
构建转账交易
调用transfer函数前,需构建交易数据,核心步骤包括:
- 确定转账数量:ERC20代币通常有18位小数,需将实际数量乘以
10^18转换为合约中的整数,例如转账100 USDT,数量为100 * 10**18。 - 获取Nonce:发送方账户的交易计数器,用于防止重放攻击。
- 估算Gas:计算交易所需的Gas限制(Gas Limit)和Gas价格(Gas Price)。
# 发送方配置
sender_private_key = "YOUR_PRIVATE_KEY" # 替换为发送方私钥
sender_address = "0xYourSenderAddress" # 替换为发送方地址
# 接收方配置
receiver_address = "0xYourReceiverAddress" # 替换为接收方地址
# 转账数量(假设代币精度为18位,转账100个代币)
amount = 100 * 10**18
# 获取Nonce
nonce = w3.eth.get_transaction_count(sender_address)
# 估算Gas
try:
gas_estimate = contract.functions.transfer(receiver_address, amount).estimate_gas({'from': sender_address})
print(f"预估Gas: {gas_estimate}")
except Exception as e:
print(f"Gas估算失败: {e}")
gas_estimate = 300000 # 手动设置默认Gas Limit(ERC20转账通常约6万Gas)
# 设置Gas Price(根据网络情况调整,单位:Gwei)
gas_price = w3.to_wei(20, 'gwei') # 20 Gwei
# 构建交易
transaction = contract.functions.transfer(receiver_address, amount).build_transaction({
'from': sender_address,
'nonce': nonce,
'gas': gas_estimate,
'gasPrice': gas_price,
})
签名并发送交易
使用发送方私钥对交易进行签名,然后发送到以太坊网络:
# 签名交易
signed_txn = w3.eth.account.sign_transaction(transaction, sender_private_key)
# 发送交易
tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
print(f"交易哈希: {w3.to_hex(tx_hash)}")
# 等待交易上链
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"交易回执: {receipt}")
完整代码示例
将上述步骤整合为完整代码:
from web3 import Web3
def transfer_erc20():
# 初始化Web3
infura_url = "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
w3 = Web3(Web3.HTTPProvider(infura_url))
if not w3.is_connected():
print("连接以太坊节点失败")
return
# 代币配置(USDT)
token_address = "0xdAC17F958D2ee523a2206206994597C13D831ec7"
erc20_abi = [
{
"constant": False,
"inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
}
]
contract = w3.eth.contract(address=token_address, abi=erc20_abi)
# 账户配置
sender_private_key = "YOUR_PRIVATE_KEY"
sender_address = "0xYourSenderAddress"
receiver_address = "0xYourReceiverAddress"
# 转账数量(100 USDT,精度18位)
amount = 100 * 10**18
try:
# 获取Nonce
nonce = w3.eth.get_transaction_count(sender_address)
# 估算Gas
gas_estimate = contract.functions.transfer(receiver_address, amount).estimate_gas({'from': sender_address})
gas_price = w3.to_wei(20, 'gwei') # 20 Gwei
# 构建交易
transaction = contract.functions.transfer(receiver_address, amount).build_transaction({
'from': sender_address,
'nonce': nonce,
'gas': gas_estimate,
'gasPrice': gas_price,
})
# 签名并发送
signed_txn = w3.eth.account.sign_transaction(transaction, sender_private_key)
tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
print(f"交易哈希: {w3.to_hex(tx_hash)}")
# 等待