Getting Started
Overview
Alloy is a high-performance Rust toolkit for Ethereum and EVM-based blockchains providing developers with:
- High Performance: Optimized primitives with up to 60% faster U256 operations and 10x faster ABI encoding
- Developer Experience: Intuitive API for interacting with Smart contracts via the
sol!
macro - Chain-Agnostic Type System: Multi-chain support without feature flags or type casting
- Extensibility: Customizable provider architecture with layers and fillers
Installation
Install alloy to any cargo project using the command line. See Installation for more details on the various features flags.
cargo add alloy
Quick Start
1. Sending Transactions
This example shows how to send 100 ETH using the TransactionBuilder
on a local anvil node
use alloy::{
network::TransactionBuilder,
primitives::{
address,
utils::{format_ether, Unit},
U256,
},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::local::PrivateKeySigner,
};
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Initialize a signer with a private key
let signer: PrivateKeySigner =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse()?;
// Instantiate a provider with the signer and a local anvil node
let provider = ProviderBuilder::new()
.wallet(signer)
.connect("http://127.0.0.1:8545")
.await?;
// Prepare a transaction request to send 100 ETH to Alice
let alice = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
let value = Unit::ETHER.wei().saturating_mul(U256::from(100));
let tx = TransactionRequest::default()
.with_to(alice)
.with_value(value);
// Send the transaction and wait for the broadcast
let pending_tx = provider.send_transaction(tx).await?;
println!("Pending transaction... {}", pending_tx.tx_hash());
// Wait for the transaction to be included and get the receipt
let receipt = pending_tx.get_receipt().await?;
println!(
"Transaction included in block {}",
receipt.block_number.expect("Failed to get block number")
);
println!("Transferred {:.5} ETH to {alice}", format_ether(value));
Ok(())
}
2. Interacting with Smart Contracts
Alloy's sol!
macro makes working with smart contracts intuitive by letting you write Solidity directly in Rust:
use alloy::{
primitives::{
address,
utils::{format_ether, Unit},
U256,
},
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
sol,
};
use std::error::Error;
// Generate bindings for the WETH9 contract
sol! {
#[sol(rpc)]
contract WETH9 {
function deposit() public payable;
function balanceOf(address) public view returns (uint256);
function withdraw(uint amount) public;
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Initialize a signer with a private key
let signer: PrivateKeySigner =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse()?;
// Instantiate a provider with the signer
let provider = ProviderBuilder::new()
.wallet(signer)
.connect_anvil_with_config(|a| a.fork("https://reth-ethereum.ithaca.xyz/rpc"));
// Setup WETH contract instance
let weth_address = address!("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
let weth = WETH9::new(weth_address, provider.clone());
// Read initial balance
let from_address = signer.address();
let initial_balance = weth.balanceOf(from_address).call().await?;
println!("Initial WETH balance: {} WETH", format_ether(initial_balance));
// Write: Deposit ETH to get WETH
let deposit_amount = Unit::ETHER.wei().saturating_mul(U256::from(10));
let deposit_tx = weth.deposit().value(deposit_amount).send().await?;
let deposit_receipt = deposit_tx.get_receipt().await?;
println!(
"Deposited ETH in block {}",
deposit_receipt.block_number.expect("Failed to get block number")
);
// Read: Check updated balance after deposit
let new_balance = weth.balanceOf(from_address).call().await?;
println!("New WETH balance: {} WETH", format_ether(new_balance));
// Write: Withdraw some WETH back to ETH
let withdraw_amount = Unit::ETHER.wei().saturating_mul(U256::from(5));
let withdraw_tx = weth.withdraw(withdraw_amount).send().await?;
let withdraw_receipt = withdraw_tx.get_receipt().await?;
println!(
"Withdrew ETH in block {}",
withdraw_receipt.block_number.expect("Failed to get block number")
);
// Read: Final balance check
let final_balance = weth.balanceOf(from_address).call().await?;
println!("Final WETH balance: {} WETH", format_ether(final_balance));
Ok(())
}
3. Monitoring Blockchain Activity
This example shows how to monitor blocks and track the WETH balance of a Uniswap V3 WETH-USDC contract in real-time:
use alloy::{
primitives::{address, utils::format_ether},
providers::{Provider, ProviderBuilder, WsConnect},
sol,
};
use futures_util::StreamExt;
sol! {
#[sol(rpc)]
contract WETH {
function balanceOf(address) external view returns (uint256);
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to an Ethereum node via WebSocket
let ws = WsConnect::new("wss://reth-ethereum.ithaca.xyz/ws");
let provider = ProviderBuilder::new().connect_ws(ws).await?;
// Uniswap V3 WETH-USDC Pool address
let uniswap_pool = address!("0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8");
// Setup the WETH contract instance
let weth_addr = address!("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let weth = WETH::new(weth_addr, &provider);
// Subscribe to new blocks
let mut block_stream = provider.subscribe_blocks().await?.into_stream();
println!("🔄 Monitoring for new blocks...");
// Process each new block as it arrives
while let Some(block) = block_stream.next().await {
println!("🧱 Block #{}: {}", block.number, block.hash);
// Get contract balance at this block
let balance = weth.balanceOf(uniswap_pool).block(block.number.into()).call().await?;
// Format the balance in ETH
println!("💰 Uniswap V3 WETH-USDC pool balance: {} WETH", format_ether(balance));
}
Ok(())
}
Crate Features
Alloy can be consumed in multiple ways with numerous combinations of features for different use cases.
Meta crate
The alloy meta crate is useful when you want to quickly get started without dealing with multiple installations or features.
It comes with the following features as default:
default = ["std", "reqwest", "alloy-core/default", "essentials"]
# std
std = [
"alloy-core/std",
"alloy-eips?/std",
"alloy-genesis?/std",
"alloy-serde?/std",
"alloy-consensus?/std",
]
# enable basic network interactions out of the box.
essentials = ["contract", "provider-http", "rpc-types", "signer-local"]
Find the full feature list here.
Individual crates
Alloy is a collection of modular crates that can be used independently.
Meta-crates can lead to dependencies bloat increasing compile-time. For large projects where compile time can be an issue, we recommend using crates independently as in when required.
[dependencies]
alloy-primitives = { version = "1.0", default-features = false, features = ["rand", "serde", "map-foldhash"] }
alloy-provider = { version = "0.15", default-features = false, features = ["ipc"] }
# ..snip..
This allows you to have granular control over the dependencies and features you use in your project.
Find the full list of the crates here.
no_std
crates
Most of the crates in Alloy are not no_std
as they are primarily network-focused. Having said that we do support no_std
implementation for crucial crates such as:
-
alloy-eips: Consists of Ethereum's current and future EIP types and spec implementations.
-
alloy-genesis: The Ethereum genesis file definitions.
-
alloy-serde: Serialization and deserialization of helpers for alloy.
-
alloy-consensus: The Ethereum consensus interface. It contains constants, types, and functions for implementing Ethereum EL consensus and communication. This includes headers, blocks, transactions, EIP-2718 envelopes, EIP-2930, EIP-4844, and more.
Guides
Check out our Guides to see more practical use cases, including: