Ethereum Development with Rust: Accounts

·

This series replicates "Ethereum Development with Go" using Rust's ethers-rs library. It serves as a fast-paced tutorial for ethers-rs, focusing on code implementation rather than foundational concepts.

In this installment, we explore account management, covering:


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"

Account Balances

Querying balances using hardhat test accounts:

use ethers::prelude::*;
use ethers::utils;

const RPC_URL: &str = "https://cloudflare-eth.com";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = Provider::<Http>::try_from(RPC_URL)?;
    let balance = provider
        .get_balance("0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199", None)
        .await?;
    println!("Balance: {} ether", utils::format_ether(balance));
    println!("Balance: {balance:?} wei");
    Ok(())
}

Token Balances (ERC20)

Fetching ERC20 balances involves calling the token contract’s balanceOf method:

use ethers::prelude::*;
use ethers::types::Address;
use std::sync::Arc;

const RPC_URL: &str = "https://cloudflare-eth.com";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let provider = Provider::<Http>::try_from(RPC_URL)?;
    abigen!(
        IERC20,
        r#"[
            function balanceOf(address account) external view returns (uint256)
        ]"#,
    );

    let erc20_address: Address = "0xEB1774bc66930a417A76Df89885CeE7c1A29f405".parse()?;
    let account_address: Address = "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199".parse()?;
    let client = Arc::new(provider);
    let contract = IERC20::new(erc20_address, client);

    if let Ok(balance) = contract.balance_of(account_address).call().await {
        println!("Token Balance: {balance:?}");
    }
    Ok(())
}

👉 Explore more Rust Ethereum tools


Wallet Generation

Create wallets from private keys or random entropy:

use ethers::signers::{LocalWallet, Signer, Wallet};
use ethers::types::H256;

#[tokio::main]
async fn main() -> eyre::Result<()> {
    // Random wallet
    let wallet = LocalWallet::new(&mut rand::thread_rng());
    println!("Private Key: {:?}", wallet.signer().to_bytes());
    println!("Address: {:?}", wallet.address());

    // From predefined bytes
    let privkey = hex::decode("fe9f116e0a9ced0b9ca875ca11f8707cdd807f1caf9e2d738dc01ca4d0a668fa")?;
    let wallet = Wallet::from_bytes(&privkey)?;
    println!("Address: {:?}", wallet.address());
    Ok(())
}

Address Validation

1. Syntax Check

Validate hex format via regex:

let re = regex::Regex::new(r"^0x[0-9a-fA-F]{40}$").unwrap();
println!("Valid: {}", re.is_match("0x323b5d4c32345ced77393b3530b1eed0f346429d")); // true
println!("Valid: {}", re.is_match("0xZYXb...")); // false

2. Contract vs. EOA

Check stored code length:

let code = provider.get_code("0xEB1774bc...", None).await?;
if !code.is_empty() {
    println!("Contract address");
} else {
    println!("Externally owned account (EOA)");
}

FAQs

Q1: How does abigen! work?

It compiles Solidity-like ABIs into Rust interfaces at build time, enabling type-safe contract calls.

Q2: What’s the difference between ETH and ERC20 balances?

ETH is native to Ethereum; ERC20 balances are tracked by token contracts.

Q3: Are private keys case-sensitive?

No. Hex values (e.g., 0x1aBc...) are case-insensitive.

👉 Secure your keys with best practices


Conclusion

This guide covered core account operations—balance checks, wallet creation, and address validation—using ethers-rs. Key takeaways:

For deeper dives, refer to:


### Key SEO Enhancements:  
- **Keywords**: Ethereum, Rust, `ethers-rs`, ERC20, wallet, private key.  
- **Structure**: Hierarchical Markdown headings (`##`, `###`).