本篇章是《用Rust来做以太坊开发》系列的第二篇,专门带你用 ethers-rs 从零实现 账户余额查询、ERC20代币查询、钱包创建、地址验证 等核心能力。即使你刚入门 Rust,也能一步步跟上节奏。为什么要用Rust写以太坊应用
随着以太坊 去中心化 需求上升,性能、内存安全、并发 成为开发生命线。Rust 的类型系统与零成本抽象天然适合加密货币场景,ethers-rs 又提供了完整的高层级 API,让 Rust 开发者一键接入以太坊生态。下面我们正式进入主题:账户。
前置依赖
请在 Cargo.toml 中一次性补足依赖,后续所有示例都无需再改:
[dependencies]
ethers = { version = "2.0", features = ["rustls", "ws"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
hex = { package = "const-hex", version = "1.6", features = ["hex"] }
regex = "1.10.2"1. 查询ETH余额:简单两步骤
关键词:Provider、get_balance、wei转换、utils
场景:节点已运行,目标地址 0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199,需要立即知道账户持有多少 ETH。
use ethers::{prelude::*, utils};
const RPC_URL: &str = "https://cloudflare-eth.com";
#[tokio::main]
async fn main() -> eyre::Result<()> {
let provider = Provider::<Http>::try_from(RPC_URL)?;
let balance_wei = provider
.get_balance("0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199".parse()?, None)
.await?;
println!("余额: {} 枚 ETH", utils::format_ether(balance_wei));
println!("余额: {} wei", balance_wei);
Ok(())
}要点回顾
get_balance返回U256(单位 wei)- 使用
utils::format_ether直接转换为人可读的 ether 数量
2. 查询任意ERC20代币
关键词:ERC20、abigen、智能合约、合约地址、代币余额
继续查询同地址持有的某个 ERC20 代币,原理就是调用该合约的 balanceOf 方法。use ethers::{prelude::*, utils};
use std::sync::Arc;
abigen!(
IERC20,
r#"[
function balanceOf(address holder) external view returns (uint256)
function totalSupply() external view returns (uint256)
]"#,
);
const RPC_URL: &str = "https://cloudflare-eth.com";
#[tokio::main]
async fn main() -> eyre::Result<()> {
let provider = Provider::<Http>::try_from(RPC_URL)?;
let client = Arc::new(provider);
let token: Address = "0xEB1774bc66930a417A76Df89885CeE7c1A29f405".parse()?;
let account: Address = "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199".parse()?;
let contract = IERC20::new(token, client.clone());
let bal = contract.balance_of(account).call().await?;
println!("ERC20 合约余额: {bal} wei");
Ok(())
}温馨提示
👉 深入理解 abigen!3分钟带你掌握 Rust 生成合约绑定
3. 从零创建新钱包
关键词:LocalWallet、私钥、助记词、wallet、H256、Signer
钱包本质是 256 位二进制私钥;这里的演示包含:
- 随机生成新钱包
- 从固定字节数组恢复
- 从十六进制字符串恢复
use ethers::{core::rand::thread_rng, signers::*};
use ethers::types::H256;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// 1. 随机创建
let random_wallet = LocalWallet::new(&mut thread_rng());
println!("新私钥: {:?}", hex::encode(random_wallet.signer().to_bytes()));
println!("地址: {:x}", random_wallet.address());
// 2. 从固定字节恢复
let prikey_bytes: [u8; 32] = hex_literal::hex!(
"fe9f116e0a9ced0b9ca875ca11f8707cdd807f1caf9e2d738dc01ca4d0a668fa"
);
let restored_wallet = Wallet::from_bytes(&prikey_bytes)?;
println!("恢复地址: {:x}", restored_wallet.address());
Ok(())
}隐私提醒
- 私钥关乎资产,切勿提交到 GitHub
- 真实场景可用 助记词+加密 Keystore 保护密钥
4. 在Rust里验证以太坊地址
关键词:regex、地址验证、十六进制、校验格式
编写正则,两秒判断:
#[tokio::main]
async fn main() -> eyre::Result<()> {
let re = regex::Regex::new(r"^0x[0-9a-fA-F]{40}$")?;
println!("有效地址: {}", re.is_match("0x323b5d4c32345ced77393b3530b1eed0f346429d"));
println!("非法字符: {}", !re.is_match("0xZYXb5d4c32345ced77393b3530b1eed0f346429d"));
Ok(())
}5. 区分合约地址与普通账户
关键词:字节码、代码大小、合约、EOA(外部账户)
以太坊账户只分为两类:合约账户 与 EOA。简单规则:查询 get_code,长度 > 1 即为合约。
use ethers::prelude::*;
const RPC_URL: &str = "https://cloudflare-eth.com";
#[tokio::main]
async fn main() -> eyre::Result<()> {
let provider = Provider::<Http>::try_from(RPC_URL)?;
let code = provider
.get_code("0xEB1774bc66930a417A76Df89885CeE7c1A29f405".parse()?, None)
.await?;
if !code.is_empty() {
println!("该地址 *是* 合约");
} else {
println!("该地址 *不是* 合约");
}
Ok(())
}补充知道:
👉 想知道更多合约部署细节?这份全链路指南不容错过
实战总结
至此,你已能用Rust完成:
- 用 Provider 查询 原生 ETH 及任意 ERC20 余额
- 随机或指定私钥,生成并 导出钱包地址
- 用一行正则过滤账户字符串
- 辨别一个地址是普通账户还是 智能合约
下一步,你可以把交易签名、与 DApp 交互、调用链上计算统统收入囊中。
常见问题 FAQ
- Q:
get_balance始终没时间戳参数?
A:第二个参数为可选BlockId,传None即取最新区块高度,亦可传BlockNumber::Latest。 - Q:ERC20
balanceOf报错ContractRevert?
A:大概率传错合约地址或方法签名,对一下abigen生成的接口与链上字节码一致性。 - Q:为什么钱包初始化还要
thread_rng()?
A:Rust 标准库故意限制加密随机源;必须用randcrate 的ThreadRng获取 安全随机数。 - Q:验证合约时返回空字节数组仍被误判为合约?
A:极少数空合约部署后会留下大小为0x0的代码,可结合事件日志二次确认。 - Q:私钥放内存不安全怎么办?
A:实战用 Keystore 文件+密码,或 硬件钱包(Trezor/Ledger);可集成eth-keystorecrate 实现加密持久化。