Alloy RS - Complete Guide
Alloy is a modern, high-performance Ethereum library for Rust developers. Built from the ground up with type safety, performance, and developer experience in mind.
🎯What is Alloy?
Alloy is a high-performance, modern Ethereum library for Rust. It's the successor to ethers-rs and provides a complete toolkit for interacting with Ethereum and EVM-compatible blockchains.
Why Alloy?
- Type Safety: Leverages Rust's type system for compile-time correctness
- Performance: Zero-cost abstractions and efficient memory usage
- Modern: Built with async/await from the ground up
- Modular: Use only what you need with feature flags
- Well-Documented: Comprehensive docs and examples
✨Key Features
🔌 Providers
Connect to Ethereum nodes via HTTP, WebSocket, or IPC. Support for multiple networks and RPC providers.
🔐 Signers
Sign transactions with private keys, hardware wallets, or custom signing implementations.
📝 Contracts
Type-safe contract interactions with automatic ABI encoding/decoding.
🧮 Primitives
Native types for addresses, hashes, bytes, and large integers (U256, U512).
💰 Transactions
Build, sign, and send transactions with support for EIP-1559, EIP-2930, and legacy formats.
🔍 Events
Listen to and filter blockchain events with type-safe event parsing.
⚙️Setup & Installation
1. Add Alloy to Your Project
[dependencies]
alloy = { version = "0.5", features = ["full"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
2. Common Feature Flags
| Feature | Description | When to Use |
|---|---|---|
full |
All features enabled | Quick prototyping, learning |
provider-http |
HTTP provider support | Basic RPC connections |
provider-ws |
WebSocket provider | Real-time event listening |
signers |
Wallet signing functionality | Sending transactions |
signer-local |
Local private key signing | Development, testing |
contract |
Smart contract interactions | DApp development |
🔌Providers: Connecting to Ethereum
Providers are your gateway to the Ethereum network. They handle communication with Ethereum nodes.
What Can Providers Do?
- Query blockchain data (blocks, transactions, balances)
- Send transactions to the network
- Call smart contract functions
- Subscribe to real-time events
- Estimate gas costs
Creating an HTTP Provider
use alloy::providers::{Provider, ProviderBuilder};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to Ethereum mainnet via Infura
let rpc_url = "https://mainnet.infura.io/v3/YOUR_KEY";
let provider = ProviderBuilder::new()
.on_http(rpc_url.parse()?);
// Get latest block number
let block_number = provider.get_block_number().await?;
println!("Latest block: {}", block_number);
Ok(())
}
Common Provider Methods
// Get balance of an address
let balance = provider.get_balance(address).await?;
// Get current gas price
let gas_price = provider.get_gas_price().await?;
// Get transaction by hash
let tx = provider.get_transaction_by_hash(tx_hash).await?;
// Get block by number
let block = provider.get_block_by_number(block_num, false).await?;
// Estimate gas for a transaction
let gas = provider.estimate_gas(&tx).await?;
// Get transaction receipt
let receipt = provider.get_transaction_receipt(tx_hash).await?;
🎯 Use Case: Portfolio Tracker
Situation: You want to build an app that shows users their token balances and transaction history.
Solution: Use a Provider to query balances, read contract states, and fetch historical transactions without needing to send any transactions.
🧮Primitives: Ethereum Data Types
Alloy provides native Rust types for Ethereum data structures.
Core Primitive Types
Address - Ethereum Addresses
use alloy::primitives::Address;
// Parse from string
let addr: Address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".parse()?;
// Create from bytes
let addr = Address::from([0u8; 20]);
// Zero address
let zero = Address::ZERO;
When to use: Wallet addresses, contract addresses, recipient addresses
U256 - Large Numbers (Wei, Tokens)
use alloy::primitives::U256;
// Create from various types
let amount = U256::from(1_000_000_000_000_000_000u128); // 1 ETH in wei
let from_str = U256::from_str("1000000000000000000")?;
// Arithmetic operations
let doubled = amount * U256::from(2);
let half = amount / U256::from(2);
// Convert to ETH
let eth_value = amount.to_string().parse::()? / 1e18;
When to use: Token amounts, balances, gas calculations, any value > u64::MAX
Bytes - Raw Data
use alloy::primitives::Bytes;
// Create from hex string
let data = Bytes::from(hex::decode("0x1234")?);
// Use in transactions
let tx = TransactionRequest::default()
.input(data);
When to use: Contract call data, transaction input, raw blockchain data
🎯 Use Case: Token Balance Display
Situation: You need to show a user's USDC balance (6 decimals) in a readable format.
Solution: Use U256 to store the raw balance, then convert to human-readable format by dividing by 10^6.
let balance_raw: U256 = token_contract.balance_of(user_address).await?;
let balance_usdc = balance_raw.to_string().parse::()? / 1e6;
println!("Balance: ${:.2} USDC", balance_usdc);
💰Transactions: Sending Data to Blockchain
Transactions are how you change the state of the blockchain.
When Do You Need Transactions?
- Sending ETH to another address
- Calling state-changing contract functions
- Deploying smart contracts
- Minting/burning tokens
- Updating contract storage
Building a Simple Transaction
use alloy::rpc::types::TransactionRequest;
use alloy::primitives::{Address, U256};
// Send ETH to an address
let to: Address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".parse()?;
let value = U256::from(1_000_000_000_000_000_000u128); // 1 ETH
let tx = TransactionRequest::default()
.to(to)
.value(value);
// Send it (requires wallet attached to provider)
let pending = provider.send_transaction(tx).await?;
let receipt = pending.get_receipt().await?;
Contract Interaction Transaction
// Call a contract function (e.g., deposit() on WETH)
let contract_address: Address = "0xC02...".parse()?;
let function_selector = hex::decode("d0e30db0")?; // deposit()
let tx = TransactionRequest::default()
.to(contract_address)
.value(U256::from(1_000_000_000_000_000_000u128))
.input(function_selector.into());
let pending = provider.send_transaction(tx).await?;
let receipt = pending.get_receipt().await?;
println!("Transaction mined in block: {}", receipt.block_number);
Transaction with Custom Gas Settings
use alloy::rpc::types::TransactionRequest;
let tx = TransactionRequest::default()
.to(recipient)
.value(amount)
.gas_limit(21_000)
.max_fee_per_gas(50_000_000_000) // 50 gwei
.max_priority_fee_per_gas(2_000_000_000); // 2 gwei priority
🎯 Use Case: DEX Token Swap
Situation: User wants to swap USDC for ETH on Uniswap.
Solution:
- Approve Uniswap router to spend USDC (transaction 1)
- Call Uniswap's swap function (transaction 2)
- Wait for confirmations
Both steps require transactions with proper function selectors and parameters.
📝Smart Contracts: Type-Safe Interactions
Alloy makes interacting with smart contracts type-safe and easy.
Two Ways to Interact with Contracts
1. Manual Contract Calls (Low-Level)
// Read-only call (no gas, no transaction)
let data = hex::decode("70a08231...")?; // balanceOf(address)
let call = TransactionRequest::default()
.to(contract_address)
.input(data.into());
let result = provider.call(&call).await?;
let balance = U256::from_be_bytes(result.as_ref());
// State-changing call (costs gas, creates transaction)
let data = hex::decode("a9059cbb...")?; // transfer(address,uint256)
let tx = TransactionRequest::default()
.to(contract_address)
.input(data.into());
let pending = provider.send_transaction(tx).await?;
2. Type-Safe Contract Bindings (Recommended)
// Generate bindings from ABI
sol! {
#[sol(rpc)]
contract ERC20 {
function balanceOf(address owner) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
}
// Use the generated bindings
let contract = ERC20::new(contract_address, provider);
// Read balance (no transaction)
let balance = contract.balanceOf(user_address).call().await?;
// Transfer tokens (creates transaction)
let tx = contract.transfer(recipient, amount);
let pending = tx.send().await?;
let receipt = pending.get_receipt().await?;
🎯 Use Case: NFT Marketplace
Situation: Build an NFT marketplace where users can list and buy NFTs.
Solution: Use contract bindings to:
- Read NFT ownership:
ownerOf(tokenId) - Check approvals:
isApprovedForAll(owner, operator) - Transfer NFTs:
transferFrom(from, to, tokenId) - Listen to Transfer events for real-time updates
🔐Signers & Wallets: Transaction Signing
To send transactions, you need a signer to cryptographically sign them with a private key.
Types of Signers
1. Local Private Key Signer
use alloy::signers::local::PrivateKeySigner;
use alloy::network::EthereumWallet;
// From hex string
let signer: PrivateKeySigner = "your_private_key_hex".parse()?;
// Create wallet
let wallet = EthereumWallet::from(signer);
// Get address
let address = wallet.default_signer().address();
// Attach to provider
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http(rpc_url.parse()?);
When to use: Development, testing, automated bots, backend services
2. Provider with Wallet (Complete Setup)
use alloy::providers::{Provider, ProviderBuilder};
use alloy::signers::local::PrivateKeySigner;
use alloy::network::EthereumWallet;
let signer: PrivateKeySigner = private_key.parse()?;
let wallet = EthereumWallet::from(signer);
// This provider can both read AND send transactions
let provider = ProviderBuilder::new()
.with_recommended_fillers() // Auto-fills gas, nonce, chain ID
.wallet(wallet) // Enables transaction signing
.on_http(rpc_url.parse()?);
// Now you can send transactions
let tx = TransactionRequest::default()
.to(recipient)
.value(amount);
let pending = provider.send_transaction(tx).await?;
- Never hardcode private keys in source code
- Never commit private keys to version control
- Use environment variables or secure vaults in production
- Consider hardware wallets for high-value operations
🎯 Use Case: Trading Bot
Situation: Create an automated bot that monitors prices and executes trades.
Solution: Use a local signer with API keys stored securely:
// Load from environment variable
let private_key = std::env::var("PRIVATE_KEY")?;
let signer: PrivateKeySigner = private_key.parse()?;
let wallet = EthereumWallet::from(signer);
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http(rpc_url.parse()?);
// Bot can now automatically sign and send trades
loop {
let price = check_price(&provider).await?;
if should_buy(price) {
execute_trade(&provider, amount).await?;
}
tokio::time::sleep(Duration::from_secs(60)).await;
}
🎯Real-World Use Cases
1. 💼 Wallet Application
What you need:
- Provider: Read balances, gas prices
- Signer: Sign transactions
- Primitives: Handle addresses and amounts
Features: View balance, send ETH, view transaction history, estimate fees
2. 🎨 NFT Minting Platform
What you need:
- Contract bindings: Interact with NFT contract
- Event listeners: Monitor mint events
- IPFS integration: Store metadata
Features: Mint NFTs, check ownership, transfer NFTs, royalty payments
3. 📊 DeFi Dashboard
What you need:
- Multiple providers: Connect to different protocols
- Contract calls: Read pool data, positions
- Batch queries: Efficient data fetching
Features: Portfolio tracking, APY calculations, yield farming opportunities
4. 🤖 MEV Bot
What you need:
- WebSocket provider: Real-time mempool monitoring
- Fast transaction submission
- Gas estimation: Optimize for profit
Features: Arbitrage detection, frontrunning (ethically), backrunning
5. 🎮 GameFi Backend
What you need:
- Smart contract interactions: Game state, items
- Batch transactions: Efficient player actions
- Event subscriptions: Real-time game updates
Features: Item minting, trading, leaderboards, rewards distribution
6. 💱 Token Swapper (Like Your Project!)
What you need:
- Provider with wallet: Send transactions
- Contract interactions: DEX protocols (Uniswap, Curve)
- Gas estimation: Show users costs
Features: Swap tokens, wrap/unwrap ETH, slippage protection
💻Complete Code Examples
Example 1: Check Multiple Wallet Balances
use alloy::providers::{Provider, ProviderBuilder};
use alloy::primitives::Address;
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
let provider = ProviderBuilder::new()
.on_http("https://mainnet.infura.io/v3/YOUR_KEY".parse()?);
let addresses = vec![
"0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
];
for addr_str in addresses {
let addr: Address = addr_str.parse()?;
let balance = provider.get_balance(addr).await?;
let eth = balance.to_string().parse::()? / 1e18;
println!("{}: {:.4} ETH", addr_str, eth);
}
Ok(())
}
Example 2: Monitor Gas Prices
use alloy::providers::{Provider, ProviderBuilder};
use std::time::Duration;
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
let provider = ProviderBuilder::new()
.on_http("https://mainnet.infura.io/v3/YOUR_KEY".parse()?);
loop {
let gas_price = provider.get_gas_price().await?;
let gwei = gas_price / 1_000_000_000;
println!("Current gas price: {} gwei", gwei);
if gwei < 30 {
println!("⚡ Low gas detected! Good time to transact.");
}
tokio::time::sleep(Duration::from_secs(60)).await;
}
}
Example 3: Transaction with Retry Logic
use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::types::TransactionRequest;
use alloy::primitives::{Address, U256};
use eyre::Result;
async fn send_with_retry(
provider: &impl Provider,
tx: TransactionRequest,
max_retries: u32
) -> Result<()> {
for attempt in 1..=max_retries {
match provider.send_transaction(tx.clone()).await {
Ok(pending) => {
let receipt = pending.get_receipt().await?;
println!("✅ Success! Tx: {:?}", receipt.transaction_hash);
return Ok(());
}
Err(e) if attempt < max_retries => {
println!("⚠️ Attempt {} failed: {}", attempt, e);
tokio::time::sleep(Duration::from_secs(5)).await;
}
Err(e) => return Err(e.into()),
}
}
Ok(())
}
📚Additional Resources
- Official Alloy Documentation
- GitHub Repository
- Official Examples
- API Documentation (docs.rs)
- Ethereum Developer Docs
🎓Learning Path
- Week 1: Setup and basic provider usage (read-only operations)
- Week 2: Understanding primitives and working with addresses/amounts
- Week 3: Building and sending simple transactions on testnet
- Week 4: Smart contract interactions with manual calls
- Week 5: Type-safe contract bindings and event listening
- Week 6: Build a complete DApp (like your WETH wrapper!)