在以太坊区块链开发中,除了ETH转账外,ERC-20代币的转移也是常见需求。本文将详细介绍如何使用Go语言和go-ethereum库实现ERC-20代币的安全转账。
前置准备
在开始代币转账前,请确保已完成以下准备:
- 已连接以太坊客户端(如Infura)
- 已加载发送方私钥
- 已配置合理的燃气价格参数
若对这些基础操作不熟悉,建议先掌握以太币转账的基本流程。
代币转账核心概念
与ETH转账不同,ERC-20代币转账不需要实际发送ETH,因此交易中的"value"字段应设置为0:
value := big.NewInt(0)代币转账的本质是通过调用代币合约的transfer函数来实现的,这需要在交易的data字段中编码方法调用信息。
构建交易数据
设置接收方地址
首先定义代币接收方地址:
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")确定代币合约地址
每种ERC-20代币都有唯一的合约地址:
tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d")生成方法ID
方法ID是函数签名的Keccak-256哈希的前4个字节:
transferFnSignature := []byte("transfer(address,uint256)")
hash := sha3.NewKeccak256()
hash.Write(transferFnSignature)
methodID := hash.Sum(nil)[:4]格式化参数
接收方地址和代币数量都需要左填充到32字节:
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
amount := new(big.Int)
amount.SetString("1000000000000000000000", 10) // 1000个代币
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)组装数据字段
将方法ID和参数组合成完整的数据字段:
var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)估算和设置燃气限制
使用客户端的EstimateGas方法估算所需燃气:
gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
To: &toAddress,
Data: data,
})构建和发送交易
创建交易对象时,关键点是to字段应设置为代币合约地址,而不是接收方地址:
tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data)使用EIP155签名器对交易进行签名:
chainID, err := client.NetworkID(context.Background())
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)最后广播交易到网络:
err = client.SendTransaction(context.Background(), signedTx)👉 查看实时交易状态工具 可以帮助您监控交易确认进度。
完整代码示例
package main
import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
log.Fatal(err)
}
privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
log.Fatal(err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
}
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
log.Fatal(err)
}
value := big.NewInt(0) // 0 ETH
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d")
transferFnSignature := []byte("transfer(address,uint256)")
hash := sha3.NewKeccak256()
hash.Write(transferFnSignature)
methodID := hash.Sum(nil)[:4]
paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
amount := new(big.Int)
amount.SetString("1000000000000000000000", 10) // 1000个代币
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
var data []byte
data = append(data, methodID...)
data = append(data, paddedAddress...)
data = append(data, paddedAmount...)
gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
To: &toAddress,
Data: data,
})
if err != nil {
log.Fatal(err)
}
tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data)
chainID, err := client.NetworkID(context.Background())
if err != nil {
log.Fatal(err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
log.Fatal(err)
}
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
}常见问题
ERC-20代币转账与ETH转账有何不同?
ETH转账直接通过交易的价值字段完成,而ERC-20代币转账是通过调用代币合约的transfer函数实现的,交易的价值字段设为0,实际转账信息编码在数据字段中。
为什么需要估算燃气限制?
因为代币转账涉及智能合约执行,不同合约的复杂程度不同,需要的计算资源也不同。准确估算燃气可以避免交易因燃气不足而失败,同时避免支付过多燃气费用。
如何处理代币小数位数?
ERC-20代币通常有18位小数,但在处理时需要查询具体代币的小数位数。转账数量应以最小单位(类似wei)表示,例如1000个代币需要转换为1000 * 10^18。
交易发送后如何确认?
交易发送后会返回交易哈希,可以使用区块链浏览器通过该哈希查询交易状态。交易需要经过网络确认后才算完成,确认时间取决于网络拥堵程度和设置的燃气价格。
👉 获取进阶开发技巧 可以深入了解更多以太坊开发高级话题。
如果交易失败怎么办?
交易可能因多种原因失败:燃气不足、余额不足、合约错误等。失败的交易仍然会被记录在区块链上,且燃气费用不会被退还。建议在发送前仔细检查所有参数。
如何保证私钥安全?
私钥安全至关重要。在开发环境中,应使用测试网络和测试私钥。生产环境中必须使用安全的密钥管理方案,如硬件钱包或专业的密钥管理服务,避免将私钥硬编码在代码中。