前置准备
本指南基于 Go 语言示范,所有示例均通过 以太坊 RPC 接口 完成,无需下载完整归档节点。为了节省时间,使用公共网络提供的节点;在本地开发阶段,再切换到自行搭建的 Dev 网络。
👉 想要快速体验链上交互而不重复造轮子?这里能帮你一键测试
首先安装 go-ethereum:
go get -u -v github.com/ethereum/go-ethereum/ethclient查询转账记录
以太坊的交易信息里默认不携带发送者地址。我们需要从交易签名中 重推导公钥,再换算出地址,这便是一次“逆向寻址”。
package main
import (
"context"
"log"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/godump/doa"
)
func main() {
ethClient := doa.Try(ethclient.Dial("https://mainnet.infura.io/v3/5c17ecf14e0d4756aa81b6a1154dc599"))
blockNumber := doa.Try(ethClient.BlockNumber(context.Background()))
block := doa.Try(ethClient.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber))))
for _, tx := range block.Transactions() {
sender := doa.Try(types.Sender(
types.LatestSignerForChainID(tx.ChainId()),
tx,
))
amount, _ := tx.Value().Float64()
log.Printf("发送者 %s → 接收者 %s 数量 %.6f ETH", sender, *tx.To(), amount/1e18)
}
}运行后将输出最新区块中所有交易的 交易发送者、接收者 与 ETH 数量,帮助分析链上资金流向。
查询 USDT-ERC20 转账记录
USDT 是以太坊上最具代表性的 ERC-20 代币。通过 Transfer 事件日志(Topic: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef)即可精准定位所有 USDT 转账。
package main
import (
"context"
"log"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/godump/doa"
)
func main() {
ethClient := doa.Try(ethclient.Dial("https://mainnet.infura.io/v3/5c17ecf14e0d4756aa81b6a1154dc599"))
blockNumber := doa.Try(ethClient.BlockNumber(context.Background()))
query := ethereum.FilterQuery{
Addresses: []common.Address{
common.HexToAddress("0xdac17f958d2ee523a2206206994597c13d831ec7"),
},
Topics: [][]common.Hash{
{common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")},
},
FromBlock: big.NewInt(int64(blockNumber)),
ToBlock: big.NewInt(int64(blockNumber)),
}
txlog := doa.Try(ethClient.FilterLogs(context.Background(), query))
for _, e := range txlog {
amount := new(big.Int).SetBytes(e.Data)
tokenAmount, _ := amount.Float64()
log.Printf("交易哈希 %s:%s → %s 数量 %.6f USDT-ERC20",
e.TxHash.Hex(),
common.HexToAddress(e.Topics[1].Hex()),
common.HexToAddress(e.Topics[2].Hex()),
tokenAmount/1e6,
)
}
}查询地址 USDT 余额
无交易 也能读取余额,只需要调用合约的只读函数 balanceOf(address)。使用 ABI 规则的函数签名 70a08231 构造交易输入即可。
package main
import (
"context"
"encoding/hex"
"log"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/godump/doa"
)
func main() {
ethClient := doa.Try(ethclient.Dial("https://mainnet.infura.io/v3/5c17ecf14e0d4756aa81b6a1154dc599"))
target := common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")
data := doa.Try(hex.DecodeString(
"70a08231000000000000000000000000F977814e90dA44bFA03b6295A0616a897441aceC",
))
ret := doa.Try(ethClient.CallContract(context.Background(), ethereum.CallMsg{
To: &target,
Data: data,
}, nil))
balance := new(big.Int).SetBytes(ret)
total, _ := balance.Float64()
log.Printf("当前 USDT 余额:%.6f", total/1e6)
}查询随机地址余额(趣味彩票)
演示 随机私钥 与 公钥地址 的派生逻辑并实时查询余额。尽管成功几率极低,它仍是学习密钥派生及链上资产管理的好方法。
package main
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"log"
"math/big"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/godump/doa"
)
func main() {
ethClient := doa.Try(ethclient.Dial("https://mainnet.infura.io/v3/5c17ecf14e0d4756aa81b6a1154dc599"))
for {
pri := doa.Try(crypto.GenerateKey())
pub := pri.Public().(*ecdsa.PublicKey)
adr := crypto.PubkeyToAddress(*pub)
bal, _ := ethClient.BalanceAt(context.Background(), adr, nil)
log.Println(
"尝试私钥:", "0x"+hex.EncodeToString(crypto.FromECDSA(pri)),
"地址:", adr,
"余额:", bal,
)
if bal.Cmp(big.NewInt(0)) > 0 {
log.Println("撞库成功!")
break
}
}
}👉 想测试链上随机私钥但并不想消耗主网燃料?一键切换测试网即可
签名与验签
去中心化场景中,数字签名 确保数据完整性。以 secp256k1 算法为例,示范生成密钥、签名及二次验证。
package main
import (
"crypto/rand"
"encoding/hex"
"log"
"github.com/ethereum/go-ethereum/crypto"
"github.com/godump/doa"
)
func main() {
pri := doa.Try(crypto.HexToECDSA(
"0000000000000000000000000000000000000000000000000000000000000001",
))
pub := pri.Public()
msg := make([]byte, 32)
rand.Read(msg)
sig := doa.Try(crypto.Sign(crypto.Keccak256(msg), pri))
ok := crypto.VerifySignature(
crypto.CompressPubkey(pub.(*ecdsa.PublicKey)),
crypto.Keccak256(msg),
sig[:64],
)
log.Println("验证结果:", ok)
}构建并发送 ETH 转账交易
向黑洞地址转账 1 ETH,演示完整生命周期:获取 nonce、估算 GasPrice、签名广播。
package main
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/godump/doa"
)
func main() {
client := doa.Try(ethclient.Dial("https://mainnet.infura.io/v3/5c17ecf14e0d4756aa81b6a1154dc599"))
priKey := doa.Try(crypto.HexToECDSA("...")) // 填写私钥
pubKey := priKey.Public()
from := crypto.PubkeyToAddress(*pubKey.(*ecdsa.PublicKey))
nonce := doa.Try(client.PendingNonceAt(context.Background(), from))
value := big.NewInt(1e18) // 1 ETH in wei
gasLimit := uint64(21000)
gasPrice := doa.Try(client.SuggestGasPrice(context.Background()))
to := common.HexToAddress("0x0000000000000000000000000000000000000000")
tx := types.NewTransaction(nonce, to, value, gasLimit, gasPrice, nil)
signedTx := doa.Try(types.SignTx(tx, types.NewEIP155Signer(doa.Try(client.NetworkID(context.Background()))), priKey))
doa.Nil(client.SendTransaction(context.Background(), signedTx))
fmt.Println("交易哈希:", signedTx.Hash())
}发行 ERC-20 或自定义合约
Solidity Storage 小合约:编译后字节码上传链上,即可存取任意数字。
pragma solidity ^0.8.0;
contract Storage {
uint256 number;
function set(uint256 num) public { number = num; }
function get() public view returns (uint256) { return number; }
}部署流程:本地读取已编译字节码 storage → 组合交易 → 签名 → 上链。
data, _ := os.ReadFile("storage")
// ... 剩余部署代码同上调用合约只读函数
无需签名,利用 CallContract 即可向合约 只读函数 调阅数据:
ret, _ := client.CallContract(context.Background(), ethereum.CallMsg{
To: &contractAddress,
Data: crypto.Keccak256([]byte("get()"))[:4],
}, nil)
fmt.Println("当前存储:", big.NewInt(0).SetBytes(ret))要 写状态,则将函数选择器 + 参数拼接字节码后通过交易发送,示例可参考上节 data 构造。
本地开发节点快速启动
git clone https://github.com/ethereum/go-ethereum --branch release/1.13
cd go-ethereum && make geth
./build/bin/geth --dev --http --http.api eth,web3另开终端:
geth attach http://127.0.0.1:8545
> eth.sendTransaction({
from: eth.accounts[0],
to: '0x7e5f4552091a69125d5dfcb7b8c2659029395bdf',
value: web3.toWei(10000, 'ether')
})本地链秒级出块、无限 ETH,是真正适合脚本联调的沙盒环境。
常见问题(FAQ)
Q1:为什么一定要计算交易签名的发送者地址?
A1:以太坊交易本身只携带签名字段(r, s, v),节点通过 ECDSA 从签名推出公钥,再派生地址,相当于做一次“身份验证”。
Q2:USDT 余额与 ETH 余额查询有什么区别?
A2:ETH 余额通过 eth_getBalance 一条 RPC 即可获得;USDT 作为 ERC-20 合约,需要调用合约内 balanceOf(address) 函数。
Q3:如何在测试网而非主网运行示例?
A3:将节点 URL 换成 Goerli、Sepolia 任意公开 RPC,并在合约、地址、链 ID 全部更新即可。
Q4:部署合约的手续费何时确认?
A4:交易广播后等待出块即可。Dev 链几秒内出块;主网则需要参看所付 gasPrice 与网络拥堵情况。
Q5:为什么我签名通过后验证仍旧失败?
A5:请校验哈希前后有无额外填充,务必使用 crypto.Keccak256(str) 而非直接对字符串签名。
Q6:如何批量查询多个地址的 USDT 余额?
A6:可并发代用 CallContract,但更高效的是离线计算 ContractCall 的 calldata,再一次性发送到多调用打包合约(如 Multicall)。