Welcome to the hands-on guide for getting started with Alloy!
Alloy connects applications to blockchains.
Alloy is a high performance rewrite of
ethers-rs
from the ground up with exciting new
features.
📖 Documentation
You can find the official documentation for
alloy
here and foralloy-core
here.
✨ Contributing
You can contribute to this book on GitHub.
Sections
Getting Started
To get started with Alloy, install Alloy and take your first steps.
Migrating from Ethers
A practical guide to migrate from Ethers to Alloy.
Building with Alloy
A walkthrough of building with Alloy.
Highlights
Highlighted features of Alloy.
Examples
This section will give you practical examples of how Alloy can be used in your codebase.
Contributing
Thanks for your help improving the project! We are so happy to have you! We have a contributing guide to help you get involved in the Alloy project.
Installation
Alloy consists of a number of crates that provide a range of functionality essential for interfacing with any Ethereum-based blockchain.
The easiest way to get started is to add the alloy
crate with the full
feature flag from the command-line using Cargo:
cargo add alloy --features full
Alternatively, you can add the following to your Cargo.toml
file:
alloy = { version = "0.6", features = ["full"] }
For a more fine-grained control over the features you wish to include, you can add the individual crates to your Cargo.toml
file, or use the alloy
crate with the features you need.
After alloy
is added as a dependency you can now import alloy
as follows:
use alloy::{
network::EthereumWallet,
node_bindings::Anvil,
primitives::U256,
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
sol,
};
Features
The
alloy
meta-crate defines a number of feature flags:
Default
std
reqwest
Full, a set of the most commonly used flags to get started with alloy
.
full
General
consensus
contract
eips
genesis
network
node-bindings
Providers
providers
provider-http
provider-ipc
provider-ws
RPC
rpc
json-rpc
rpc-client
rpc-client-ipc
rpc-client-ws
rpc-types
rpc-types-admin
rpc-types-anvil
rpc-types-beacon
rpc-types-debug
rpc-types-engine
rpc-types-eth
rpc-types-json
rpc-types-mev
rpc-types-trace
rpc-types-txpool
Signers
signers
signer-aws
signer-gcp
signer-ledger
signer-ledger-browser
signer-ledger-node
signer-local
signer-trezor
signer-keystore
signer-mnemonic
signer-mnemonic-all-languages
signer-yubihsm
By default alloy
uses
reqwest
as HTTP client. Alternatively one can switch to
hyper
.
The reqwest
and hyper
feature flags are mutually exclusive.
A complete list of available features can be found on docs.rs or in the
alloy
crate’s Cargo.toml
.
The feature flags largely correspond with and enable features from the following individual crates.
Crates
alloy
consists out of the following crates:
- alloy - Meta-crate for the entire project, including
alloy-core
- alloy-consensus - Ethereum consensus interface
- alloy-contract - Interact with on-chain contracts
- alloy-eips - Ethereum Improvement Proposal (EIP) implementations
- alloy-genesis - Ethereum genesis file definitions
- alloy-json-rpc - Core data types for JSON-RPC 2.0 clients
- alloy-network - Network abstraction for RPC types
- alloy-network-primitives - Primitive types for the network abstraction
- alloy-node-bindings - Ethereum execution-layer client bindings
- alloy-provider - Interface with an Ethereum blockchain
- alloy-pubsub - Ethereum JSON-RPC publish-subscribe tower service and type definitions
- alloy-rpc-client - Low-level Ethereum JSON-RPC client implementation
- alloy-rpc-types - Meta-crate for all Ethereum JSON-RPC types
- alloy-rpc-types-admin - Types for the
admin
Ethereum JSON-RPC namespace - alloy-rpc-types-anvil - Types for the Anvil development node’s Ethereum JSON-RPC namespace
- alloy-rpc-types-beacon - Types for the Ethereum Beacon Node API
- alloy-rpc-types-engine - Types for the
engine
Ethereum JSON-RPC namespace - alloy-rpc-types-eth - Types for the
eth
Ethereum JSON-RPC namespace - alloy-rpc-types-mev - Types for the MEV bundle JSON-RPC namespace.
- alloy-rpc-types-trace - Types for the
trace
Ethereum JSON-RPC namespace - alloy-rpc-types-txpool - Types for the
txpool
Ethereum JSON-RPC namespace
- alloy-rpc-types-admin - Types for the
- alloy-serde - Serde-related utilities
- alloy-signer - Ethereum signer abstraction
- alloy-signer-aws - AWS KMS signer implementation
- alloy-signer-gcp - GCP KMS signer implementation
- alloy-signer-ledger - Ledger signer implementation
- alloy-signer-local - Local (private key, keystore, mnemonic, YubiHSM) signer implementations
- alloy-signer-trezor - Trezor signer implementation
- alloy-transport - Low-level Ethereum JSON-RPC transport abstraction
- alloy-transport-http - HTTP transport implementation
- alloy-transport-ipc - IPC transport implementation
- alloy-transport-ws - WS transport implementation
alloy-core
consists out of the following crates:
- alloy-core - Meta-crate for the entire project
- alloy-dyn-abi - Run-time ABI and EIP-712 implementations
- alloy-json-abi - Full Ethereum JSON-ABI implementation
- alloy-primitives - Primitive integer and byte types
- alloy-sol-macro-expander - Expander used in the Solidity to Rust procedural macro
- alloy-sol-macro-input - Input types for
sol!
-like macros - alloy-sol-macro - The
sol!
procedural macro - alloy-sol-type-parser - A simple parser for Solidity type strings
- alloy-sol-types - Compile-time ABI and EIP-712 implementations
- syn-solidity -
syn
-powered Solidity parser
First steps
Alloy allows applications to connect the blockchain using providers. Providers act as an interface between applications and an Ethereum node, allowing you to send requests and receive responses via JSON-RPC messages.
Some common actions you can perform using a provider include:
- Getting the current block number
- Getting the balance of an Ethereum address
- Sending a transaction to the blockchain
- Calling a smart contract function
- Subscribe logs and smart contract events
- Getting the transaction history of an address
After installing alloy
let’s create an example of using the HTTP provider and fetching the latest block number.
Install
tokio
and
eyre
as dependencies and define the body as follows:
//! Example of creating an HTTP provider using the `on_http` method on the `ProviderBuilder`.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// ...
Ok(())
}
Next, add the following section to the body to create a provider with HTTP transport:
// Set up the HTTP transport which is consumed by the RPC client.
let rpc_url = "https://eth.merkle.io".parse()?;
// Create a provider with the HTTP transport using the `reqwest` crate.
let provider = ProviderBuilder::new().on_http(rpc_url);
Finally we fetch the latest block number using the provider:
// Get latest block number.
let latest_block = provider.get_block_number().await?;
// Print the block number.
println!("Latest block number: {latest_block}");
The complete and runnable example can be found here, one of the many runnable examples of Alloy to explore.
Reference
ethers-rs has been deprecated in favor of Alloy and Foundry.
The following is a reference guide for finding the migration path for your specific crate, dependency or information source.
Documentation
- Book:
ethers-rs/book
->
alloy-rs/book
Examples
- Examples:
ethers-rs/examples
->
alloy-rs/examples
Crates
-
Address book:
ethers::addressbook
->
Not planned -
Compilers:
ethers::solc
->
foundry-compilers
-
Contract:
ethers::contract
->
alloy::contract
-
Core:
ethers::core
->
alloy::core
- Types:
ethers::core::types::*
->
See Types section
- Types:
-
Etherscan:
ethers::etherscan
->
foundry-block-explorers
-
Middleware:
ethers::middleware
->
Fillersalloy::provider::{fillers, layers}
- Gas oracle:
ethers::middleware::GasOracleMiddleware
->
Gas filleralloy::provider::GasFiller
- Gas escalator:
ethers::middleware::GasEscalatorMiddleware
->
Not planned - Transformer:
ethers::middleware::TransformerMiddleware
->
Not planned - Policy:
ethers::middleware::policy::*
->
Not planned - Timelag:
ethers::middleware::timelag::*
->
Not planned - Nonce manager:
ethers::middleware::NonceManagerMiddleware
->
Nonce filleralloy::provider::NonceFiller
- Signer:
ethers::middleware::Signer
->
Wallet filleralloy::provider::WalletFiller
- Gas oracle:
-
Providers:
ethers::providers
->
Provideralloy::providers
-
Transports:
ethers::providers::transports
->
alloy::transports
-
Signers:
ethers::signers
->
Signeralloy::signers
- AWS:
ethers::signers::aws::*
->
alloy::signers::aws
- Ledger:
ethers::signers::ledger::*
->
alloy::signers::ledger
- Trezor:
ethers::signers::trezor::*
->
alloy::signer::trezor
- Wallet:
ethers::signers::wallet::*
->
alloy::signer::local
- AWS:
Types
Primitives
- Address:
ethers::types::Address
->
alloy::primitives::Address
- U64:
ethers::types::U64
->
alloy::primitives::U64
- U128:
ethers::types::U128
->
alloy::primitives::U128
- U256:
ethers::types::U256
->
alloy::primitives::U256
- U512:
ethers::types::U512
->
alloy::primitives::U512
- H32:
ethers::types::H32
->
alloy::primitives::aliases::B32
- H64:
ethers::types::H64
->
alloy::primitives::B64
- H128:
ethers::types::H128
->
alloy::primitives::B128
- H160:
ethers::types::H160
->
alloy::primitives::B160
- H256:
ethers::types::H256
->
alloy::primitives::B256
- H512:
ethers::types::H512
->
alloy::primitives::B512
- Bloom:
ethers::types::Bloom
->
alloy::primitives::Bloom
- TxHash:
ethers::types::TxHash
->
alloy::primitives::TxHash
Due to a limitation in ruint
, BigEndianHash
ethers::types::BigEndianHash
can be expressed as follows:
// `U256` => `B256`
let x = B256::from(u256);
// `B256` => `U256`
let x: U256 = b256.into();
let x = U256::from_be_bytes(b256.into())
Due to Rust issue #50133, the native TryFrom
trait is not supported for Uint
s. Instead, use
UintTryFrom
as follows:
use alloy_primitives::ruint::UintTryFrom;
let x: U512 = uint!(1234_U512);
let y: U256 = U256::uint_try_from(x).unwrap();
RPC
- Bytes:
ethers::types::bytes::*
->
alloy::primitives::Bytes
- Chains:
ethers::types::Chain
->
alloy-rs/chains
- ENS:
ethers::types::ens
->
foundry-common
- Trace:
ethers::types::trace::*
->
alloy::rpc::types::trace
- {Block, Fee, Filter, Log, Syncing, Transaction, TxPool}:
ethers::types::*
->
alloy::rpc::types::eth::*
- Proof:
ethers::types::proof::*
->
Accountalloy::rpc::types::eth::account::*
- Signature:
ethers::types::signature::*
->
alloy::rpc::types::eth::transaction::signature::*
- Withdrawal
ethers::types::withdrawal::*
->
EIP4895alloy::eips::eip4895
- Opcode:
ethers::types::opcode::*
->
syn-solidity
ABI
- Bindings:
abigen!
->
sol!
, available onalloy::sol
.
Conversions
You can use the following traits to easily convert between ethers-rs and Alloy types.
use alloy_primitives::{Address, Bloom, Bytes, B256, B64, I256, U256, U64};
use alloy_rpc_types::{AccessList, AccessListItem, BlockNumberOrTag};
use alloy_signer_wallet::LocalWallet;
use ethers_core::types::{
transaction::eip2930::{
AccessList as EthersAccessList, AccessListItem as EthersAccessListItem,
},
BlockNumber, Bloom as EthersBloom, Bytes as EthersBytes, H160, H256, H64, I256 as EthersI256,
U256 as EthersU256, U64 as EthersU64,
};
pub trait ToAlloy {
/// The corresponding Alloy type.
type To;
/// Converts the Ethers type to the corresponding Alloy type.
fn to_alloy(self) -> Self::To;
}
pub trait ToEthers {
/// The corresponding Ethers type.
type To;
/// Converts the Alloy type to the corresponding Ethers type.
fn to_ethers(self) -> Self::To;
}
impl ToAlloy for EthersBytes {
type To = Bytes;
#[inline(always)]
fn to_alloy(self) -> Self::To {
Bytes(self.0)
}
}
impl ToAlloy for H64 {
type To = B64;
#[inline(always)]
fn to_alloy(self) -> Self::To {
B64::new(self.0)
}
}
impl ToAlloy for H160 {
type To = Address;
#[inline(always)]
fn to_alloy(self) -> Self::To {
Address::new(self.0)
}
}
impl ToAlloy for H256 {
type To = B256;
#[inline(always)]
fn to_alloy(self) -> Self::To {
B256::new(self.0)
}
}
impl ToAlloy for EthersBloom {
type To = Bloom;
#[inline(always)]
fn to_alloy(self) -> Self::To {
Bloom::new(self.0)
}
}
impl ToAlloy for EthersU256 {
type To = U256;
#[inline(always)]
fn to_alloy(self) -> Self::To {
U256::from_limbs(self.0)
}
}
impl ToAlloy for EthersI256 {
type To = I256;
#[inline(always)]
fn to_alloy(self) -> Self::To {
I256::from_raw(self.into_raw().to_alloy())
}
}
impl ToAlloy for EthersU64 {
type To = U64;
#[inline(always)]
fn to_alloy(self) -> Self::To {
U64::from_limbs(self.0)
}
}
impl ToAlloy for u64 {
type To = U256;
#[inline(always)]
fn to_alloy(self) -> Self::To {
U256::from(self)
}
}
impl ToEthers for alloy_signer_wallet::LocalWallet {
type To = ethers_signers::LocalWallet;
fn to_ethers(self) -> Self::To {
ethers_signers::LocalWallet::new_with_signer(
self.signer().clone(),
self.address().to_ethers(),
self.chain_id().unwrap(),
)
}
}
impl ToEthers for Vec<LocalWallet> {
type To = Vec<ethers_signers::LocalWallet>;
fn to_ethers(self) -> Self::To {
self.into_iter().map(ToEthers::to_ethers).collect()
}
}
impl ToAlloy for EthersAccessList {
type To = AccessList;
fn to_alloy(self) -> Self::To {
AccessList(self.0.into_iter().map(ToAlloy::to_alloy).collect())
}
}
impl ToAlloy for EthersAccessListItem {
type To = AccessListItem;
fn to_alloy(self) -> Self::To {
AccessListItem {
address: self.address.to_alloy(),
storage_keys: self.storage_keys.into_iter().map(ToAlloy::to_alloy).collect(),
}
}
}
impl ToEthers for Address {
type To = H160;
#[inline(always)]
fn to_ethers(self) -> Self::To {
H160(self.0 .0)
}
}
impl ToEthers for B256 {
type To = H256;
#[inline(always)]
fn to_ethers(self) -> Self::To {
H256(self.0)
}
}
impl ToEthers for U256 {
type To = EthersU256;
#[inline(always)]
fn to_ethers(self) -> Self::To {
EthersU256(self.into_limbs())
}
}
impl ToEthers for U64 {
type To = EthersU64;
#[inline(always)]
fn to_ethers(self) -> Self::To {
EthersU64(self.into_limbs())
}
}
impl ToEthers for Bytes {
type To = EthersBytes;
#[inline(always)]
fn to_ethers(self) -> Self::To {
EthersBytes(self.0)
}
}
impl ToEthers for BlockNumberOrTag {
type To = BlockNumber;
#[inline(always)]
fn to_ethers(self) -> Self::To {
match self {
BlockNumberOrTag::Number(n) => BlockNumber::Number(n.into()),
BlockNumberOrTag::Earliest => BlockNumber::Earliest,
BlockNumberOrTag::Latest => BlockNumber::Latest,
BlockNumberOrTag::Pending => BlockNumber::Pending,
BlockNumberOrTag::Finalized => BlockNumber::Finalized,
BlockNumberOrTag::Safe => BlockNumber::Safe,
}
}
}
Basic building blocks
- Using big numbers
- Basic hash and address types
- Common conversions
- Creating instances
- Comparisons and equivalence
Using big numbers
Ethereum uses big numbers (also known as “bignums” or “arbitrary-precision integers”) to represent certain values in its codebase and in blockchain transactions. This is necessary because the EVM operates on a 256-bit word size, which is different from the usual 32-bit or 64-bit of modern machines. This was chosen for the ease of use with 256-bit cryptography (such as Keccak-256 hashes or secp256k1 signatures).
It is worth noting that Ethereum is not the only blockchain or cryptocurrency that uses big numbers. Many other blockchains and cryptocurrencies also use big numbers to represent values in their respective systems.
Utilities
In order to create an application, it is often necessary to convert between the representation of values that is easily understood by humans (such as ether) and the machine-readable form that is used by contracts and math functions (such as wei). This is particularly important when working with Ethereum, as certain values, such as balances and gas prices, must be expressed in wei when sending transactions, even if they are displayed to the user in a different format, such as ether or gwei. To help with this conversion, alloy::primitives::utils
provides two functions,
parse_units
and
format_units
, which allow you to easily convert between human-readable and machine-readable forms of values. parse_units can be used to convert a string representing a value in ether, such as “1.1”, into a big number in wei, which can be used in contracts and math functions. format_units can be used to convert a big number value into a human-readable string, which is useful for displaying values to users.
Example: math_operations
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example math_operations
//! Example of performing arithmetic operations with `U256`.
use alloy::primitives::{utils::format_units, U256};
use eyre::Result;
use std::ops::{Div, Mul};
/// `U256` implements traits in `std::ops`, that means it supports arithmetic operations
/// using standard Rust operators `+`, `-`. `*`, `/`, `%`, along with additional utilities to
/// perform common mathematical tasks.
fn main() -> Result<()> {
let a = U256::from(10);
let b = U256::from(2);
// addition
let sum = a + b;
assert_eq!(sum, U256::from(12));
// subtraction
let difference = a - b;
assert_eq!(difference, U256::from(8));
// multiplication
let product = a * b;
assert_eq!(product, U256::from(20));
// division
let quotient = a / b;
assert_eq!(quotient, U256::from(5));
// modulo
let remainder = a % b;
assert_eq!(remainder, U256::ZERO); // equivalent to `U256::from(0)`
// exponentiation
let power = a.pow(b);
assert_eq!(power, U256::from(100));
// Multiply two 'ether' numbers:
// Big numbers are integers, that can represent fixed point numbers.
// For instance, 1 ether has 18 fixed
// decimal places (1.000000000000000000), and its big number
// representation is 10^18 = 1000000000000000000.
// When we multiply such numbers we are summing up their exponents.
// So if we multiply 10^18 * 10^18 we get 10^36, that is obviously incorrect.
// In order to get the correct result we need to divide by 10^18.
let eth1 = U256::from(10_000000000000000000_u128); // 10 ether
let eth2 = U256::from(20_000000000000000000_u128); // 20 ether
let base = U256::from(10).pow(U256::from(18));
let mul = eth1.mul(eth2).div(base); // We also divide by 10^18
let s: String = format_units(mul, "ether")?;
assert_eq!(s, "200.000000000000000000"); // 200
Ok(())
}
Find the source code on Github here.
Example: math_utilities
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example math_utilities
//! Example of using math utilities to handle big numbers in 'wei' units.
use alloy::primitives::{
utils::{format_units, parse_units},
U256,
};
use eyre::Result;
fn main() -> Result<()> {
parse_units_example()?;
format_units_example()?;
Ok(())
}
/// dApps business logics handle big numbers in 'wei' units (i.e. sending transactions, on-chain
/// math, etc.). We provide convenient methods to map user inputs (usually in 'ether' or 'gwei')
/// into 'wei' format.
fn parse_units_example() -> Result<()> {
let pu = parse_units("1.0", "wei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1));
let pu = parse_units("1.0", "kwei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000));
let pu = parse_units("1.0", "mwei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000));
let pu = parse_units("1.0", "gwei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000));
let pu = parse_units("1.0", "szabo")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000_u128));
let pu = parse_units("1.0", "finney")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000000_u128));
let pu = parse_units("1.0", "ether")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000000000_u128));
let pu = parse_units("1.0", 18)?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000000000_u128));
Ok(())
}
/// dApps business logics handle big numbers in 'wei' units (i.e. sending transactions, on-chain
/// math, etc.). On the other hand it is useful to convert big numbers into user readable formats
/// when displaying on a UI. Generally dApps display numbers in 'ether' and 'gwei' units,
/// respectively for displaying amounts and gas. The `format_units` function will format a big
/// number into a user readable string.
fn format_units_example() -> Result<()> {
// 1 ETHER = 10^18 WEI
let one_ether = U256::from(1000000000000000000_u128);
let num: String = format_units(one_ether, "wei")?;
assert_eq!(num, "1000000000000000000.0");
let num: String = format_units(one_ether, "gwei")?;
assert_eq!(num, "1000000000.000000000");
let num: String = format_units(one_ether, "ether")?;
assert_eq!(num, "1.000000000000000000");
// 1 GWEI = 10^9 WEI
let one_gwei = U256::from(1000000000_u128);
let num: String = format_units(one_gwei, 0)?;
assert_eq!(num, "1000000000.0");
let num: String = format_units(one_gwei, "wei")?;
assert_eq!(num, "1000000000.0");
let num: String = format_units(one_gwei, "kwei")?;
assert_eq!(num, "1000000.000");
let num: String = format_units(one_gwei, "mwei")?;
assert_eq!(num, "1000.000000");
let num: String = format_units(one_gwei, "gwei")?;
assert_eq!(num, "1.000000000");
let num: String = format_units(one_gwei, "szabo")?;
assert_eq!(num, "0.001000000000");
let num: String = format_units(one_gwei, "finney")?;
assert_eq!(num, "0.000001000000000");
let num: String = format_units(one_gwei, "ether")?;
assert_eq!(num, "0.000000001000000000");
Ok(())
}
Find the source code on Github here.
Basic hash and address types
Example: bytes_and_address_types
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example bytes_and_address_types
//! Example of basic usage of bytes and address types and macros.
use alloy::primitives::{
address, b128, b256, b512, b64, bytes, fixed_bytes, Address, Bytes, FixedBytes,
};
use eyre::Result;
fn main() -> Result<()> {
// Bytes type
let a = bytes!("0123abcd");
assert_eq!(a, Bytes::from(&[0x01, 0x23, 0xab, 0xcd]));
assert_eq!(a.len(), 4);
// Address type
let b = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
assert_eq!(
b,
Address::from(&[
0xf3, 0x9f, 0xd6, 0xe5, 0x1a, 0xad, 0x88, 0xf6, 0xf4, 0xce, 0x6a, 0xb8, 0x82, 0x72,
0x79, 0xcf, 0xff, 0xb9, 0x22, 0x66
])
);
assert_eq!(b.len(), 20);
// FixedBytes<8> type
let c = b64!("0102030405060708");
assert_eq!(c, FixedBytes::from(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]));
assert_eq!(c.len(), 8);
// FixedBytes<16> type
let d = b128!("0102030405060708090a0b0c0d0e0f10");
assert_eq!(
d,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
])
);
assert_eq!(d.len(), 16);
// FixedBytes<32> type
let e = b256!("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
assert_eq!(
e,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20,
]),
);
assert_eq!(e.len(), 32);
// FixedBytes<64> type
let f = b512!("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40");
assert_eq!(
f,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
]),
);
assert_eq!(f.len(), 64);
// FixedBytes<20> type, determined by the length of the input
let g = fixed_bytes!("0102030405060708090a0b0c0d0e0f1011121314");
assert_eq!(
g,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
]),
);
assert_eq!(g.len(), 20);
Ok(())
}
Find the source code on Github here.
Example: hashing_functions
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example hashing_functions
//! Example of basic usage of hashing functions.
use alloy::primitives::{eip191_hash_message, keccak256};
use eyre::{Ok, Result};
fn main() -> Result<()> {
// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
let hash = keccak256(b"hello world");
assert_eq!(
hash.to_string(),
"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"
);
assert_eq!(hash.len(), 32);
// Hash a message according to [EIP-191] (version `0x01`).
//
// The final message is a UTF-8 string, encoded as follows:
// `"\x19Ethereum Signed Message:\n" + message.length + message`
//
// This message is then hashed using [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3.
//
// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
let eip191_hash = eip191_hash_message(b"hello_world");
assert_eq!(
eip191_hash.to_string(),
"0xd52de6e039c023a7c77752126e4d9d99e2a7dacea3d19e97e9c2ebcb3ecf1c00"
);
assert_eq!(eip191_hash.len(), 32);
Ok(())
}
Find the source code on Github here.
Common conversions
Example: conversion
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example conversion
//! Example of converting `U256` to native Rust types.
use alloy::primitives::{utils::format_units, U256};
use eyre::Result;
/// `U256` provides useful conversion functions to enable transformation into native Rust types.
///
/// It is important to note that converting a big-number to a floating point type (such as a `f32`
/// or `f64`) can result in a loss of precision, since you cannot fit 256 bits of information into
/// 64 bits.
///
/// However, there may be cases where you want to perform conversions for presentation purposes.
/// For example, you may want to display a large number to the user in a more readable format.
fn main() -> Result<()> {
let num = U256::from(42_u8);
let a: u128 = num.to::<u128>();
assert_eq!(a, 42);
let b: u64 = num.to::<u64>();
assert_eq!(b, 42);
let c: u32 = num.to::<u32>();
assert_eq!(c, 42);
let d: usize = num.to::<usize>();
assert_eq!(d, 42);
let e: String = num.to_string();
assert_eq!(e, "42");
let f: String = format_units(num, 4)?;
assert_eq!(f, "0.0042");
Ok(())
}
Find the source code on Github here.
Creating instances
Example: create_instances
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example create_instances
//! Example of creating instances of `U256` from strings and numbers.
use alloy::primitives::{
utils::{parse_units, ParseUnits},
U256,
};
use eyre::Result;
use std::str::FromStr;
fn main() -> Result<()> {
// From strings
let a = U256::from_str("42")?;
assert_eq!(a.to_string(), "42");
let amount = "42";
let units = 4;
let b: ParseUnits = parse_units(amount, units)?;
assert_eq!(b.to_string(), "420000");
// From numbers
let c = U256::from(42_u8);
assert_eq!(c.to_string(), "42");
let d = U256::from(42_u16);
assert_eq!(d.to_string(), "42");
let e = U256::from(42_u32);
assert_eq!(e.to_string(), "42");
let f = U256::from(42_u64);
assert_eq!(f.to_string(), "42");
let g = U256::from(42_u128);
assert_eq!(g.to_string(), "42");
let h = U256::from(0x2a);
assert_eq!(h.to_string(), "42");
let i = U256::from(42);
assert_eq!(i.to_string(), "42");
Ok(())
}
Find the source code on Github here.
Comparisons and equivalence
Example: comparison_equivalence
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example comparison_equivalence
//! Example of comparison and equivalence of `U256` instances.
use alloy::primitives::U256;
/// `U256` implements traits in `std::cmp`, that means `U256` instances
/// can be easily compared using standard Rust operators.
fn main() {
// a == b
let a = U256::from(100_u32);
let b = U256::from(100_u32);
assert!(a == b);
// a < b
let a = U256::from(1_u32);
let b = U256::from(100_u32);
assert!(a < b);
// a <= b
let a = U256::from(100_u32);
let b = U256::from(100_u32);
assert!(a <= b);
// a > b
let a = U256::from(100_u32);
let b = U256::from(1_u32);
assert!(a > b);
// a >= b
let a = U256::from(100_u32);
let b = U256::from(100_u32);
assert!(a >= b);
// a == 0
let a = U256::ZERO;
assert!(a.is_zero());
}
Find the source code on Github here.
Connecting to a blockchain
Setting up a Provider
A
Provider
is an abstraction of a connection to the Ethereum network, providing a concise, consistent interface to standard Ethereum node functionality.
Builder
The correct way of creating a
Provider
is through the
ProviderBuilder
, a builder.
Alloy provides concrete transport implementations for HTTP
, WS
(WebSockets) and IPC
(Inter-Process Communication), as well as higher level transports which wrap a single or multiple transports.
//! Example of using the HTTP provider using the `on_http` method.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Set up the HTTP transport which is consumed by the RPC client.
let rpc_url = "https://eth.merkle.io".parse()?;
// Create a provider with the HTTP transport using the `reqwest` crate.
let provider = ProviderBuilder::new().on_http(rpc_url);
Ok(())
}
Next, lets look at the HTTP Provider.
HTTP Provider
The Http
provider establishes an HTTP connection with a node, allowing you to send JSON-RPC requests to the node to fetch data, simulate calls, send transactions and much more.
Initializing an Http Provider
The recommended way of initializing a Http
provider is by using the
on_http
method on the
ProviderBuilder
.
//! Example of creating an HTTP provider using the `on_http` method on the `ProviderBuilder`.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Set up the HTTP transport which is consumed by the RPC client.
let rpc_url = "https://eth.merkle.io".parse()?;
// Create a provider with the HTTP transport using the `reqwest` crate.
let provider = ProviderBuilder::new().on_http(rpc_url);
Ok(())
}
An alternative way of initializing is to use the
on_builtin
method on the
ProviderBuilder
. This method will automatically determine the connection type (Http
, Ws
or Ipc
) depending on the format of the URL. This method is particularly useful if you need a boxed transport.
//! Example of creating an HTTP provider using the `on_builtin` method on the `ProviderBuilder`.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Create a provider with the HTTP transport using the `reqwest` crate.
let provider = ProviderBuilder::new().on_builtin("https://eth.merkle.io").await?;
Ok(())
}
Example: http
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example http
//! Example of using the HTTP provider with the `reqwest` crate to get the latest block number.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider with the HTTP transport using the `reqwest` crate.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get latest block number.
let latest_block = provider.get_block_number().await?;
println!("Latest block number: {latest_block}");
Ok(())
}
Find the source code on Github here.
WS Provider
The Ws
provider establishes an WebSocket connection with a node, allowing you to send JSON-RPC requests to the node to fetch data, simulate calls, send transactions and much more. The Ws
provider can be used with any Ethereum node that supports WebSocket connections. This allows programs to interact with the network in real-time without the need for HTTP polling for things like new block headers and filter logs.
Initializing a Ws
Provider
The recommended way of initializing a Ws
provider is by using the
on_ws
method on the
ProviderBuilder
with a
WsConnect
configuration.
//! Example of creating an WS provider using the `on_ws` method on the `ProviderBuilder`.
use alloy::providers::{Provider, ProviderBuilder, WsConnect};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Set up the WS transport which is consumed by the RPC client.
let rpc_url = "wss://eth-mainnet.g.alchemy.com/v2/your-api-key";
// Create the provider.
let ws = WsConnect::new(rpc_url);
let provider = ProviderBuilder::new().on_ws(ws).await?;
Ok(())
}
An alternative way of initializing is to use the
on_builtin
method on the
ProviderBuilder
. This method will automatically determine the connection type (Http
, Ws
or Ipc
) depending on the format of the URL. This method is particularly useful if you need a boxed transport.
//! Example of creating an WS provider using the `on_builtin` method on the `ProviderBuilder`.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Create a provider with the WS transport.
let provider = ProviderBuilder::new().on_builtin("wss://eth-mainnet.g.alchemy.com/v2/your-api-key").await?;
Ok(())
}
Similar to the other providers, you can also establish an authorized connection with a node via websockets.
Example: ws
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ws
//! Example of using the WS provider to subscribe to new blocks.
use alloy::providers::{Provider, ProviderBuilder, WsConnect};
use eyre::Result;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Create the provider.
let rpc_url = "wss://eth-mainnet.g.alchemy.com/v2/your-api-key";
let ws = WsConnect::new(rpc_url);
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Subscribe to new blocks.
let sub = provider.subscribe_blocks().await?;
// Wait and take the next 4 blocks.
let mut stream = sub.into_stream().take(4);
println!("Awaiting block headers...");
// Take the stream and print the block number upon receiving a new block.
let handle = tokio::spawn(async move {
while let Some(header) = stream.next().await {
println!("Latest block number: {}", header.number);
}
});
handle.await?;
Ok(())
}
Find the source code on Github here.
Example: ws_with_auth
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ws_with_auth
//! Example of using the WS provider with auth to subscribe to new blocks.
use alloy::{
providers::{Provider, ProviderBuilder, WsConnect},
transports::Authorization,
};
use eyre::Result;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Create authorization methods.
let auth = Authorization::basic("username", "password");
let auth_bearer = Authorization::bearer("bearer-token");
// Create the WS connection object with authentication.
let rpc_url = "wss://your-ws-endpoint.com/";
let ws_basic = WsConnect::new(rpc_url).with_auth(auth);
let ws_bearer = WsConnect::new(rpc_url).with_auth(auth_bearer);
// Create the provider.
let provider_basic = ProviderBuilder::new().on_ws(ws_basic).await?;
let provider_bearer = ProviderBuilder::new().on_ws(ws_bearer).await?;
// Subscribe to new block headers.
let sub_basic = provider_basic.subscribe_blocks();
let sub_bearer = provider_bearer.subscribe_blocks();
// Wait and take the next 4 block headers
let mut stream_basic = sub_basic.await?.into_stream().take(4);
let mut stream_bearer = sub_bearer.await?.into_stream().take(4);
println!("Awaiting block headers...");
// Take the basic stream and print the block number upon receiving a new block header.
let basic_handle = tokio::spawn(async move {
while let Some(header) = stream_basic.next().await {
println!("Latest block number (basic): {}", header.number);
}
});
// Take the bearer stream and print the block number upon receiving a new block header.
let bearer_handle = tokio::spawn(async move {
while let Some(header) = stream_bearer.next().await {
println!("Latest block number (bearer): {}", header.number);
}
});
// Wait for both tasks to complete.
let _ = tokio::try_join!(basic_handle, bearer_handle)?;
Ok(())
}
Find the source code on Github here.
IPC Provider
The IPC (Inter-Process Communication) transport allows our program to communicate with a node over a local Unix domain socket or Windows named pipe.
Using the IPC transport allows the ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without the need for a network connection or HTTP server. This can be useful for interacting with a local Ethereum node that is running on the same network. Using IPC is faster than RPC, however you will need to have a local node that you can connect to.
Initializing an Ipc
Provider
The recommended way of initializing an Ipc
provider is by using the
on_ipc
method on the
ProviderBuilder
with an
IpcConnect
configuration.
//! Example of creating an IPC provider using the `on_ipc` method on the `ProviderBuilder`.
use alloy::providers::{IpcConnect, Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Set up the IPC transport which is consumed by the RPC client.
let ipc_path = "/tmp/reth.ipc";
// Create the provider.
let ipc = IpcConnect::new(ipc_path.to_string());
let provider = ProviderBuilder::new().on_ipc(ipc).await?;
Ok(())
}
An alternative way of initializing is to use the
on_builtin
method on the
ProviderBuilder
. This method will automatically determine the connection type (Http
, Ws
or Ipc
) depending on the format of the URL. This method is particularly useful if you need a boxed transport.
//! Example of creating an IPC provider using the `on_builtin` method on the `ProviderBuilder`.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Create a provider with the IPC transport.
let provider = ProviderBuilder::new().on_builtin("/tmp/reth.ipc").await?;
Ok(())
}
Example: ipc
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ipc
//! Example of using the IPC provider to get the latest block number.
use alloy::providers::{IpcConnect, Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Set up the IPC transport which is consumed by the RPC client.
let ipc_path = "/tmp/reth.ipc";
// Create the provider.
let ipc = IpcConnect::new(ipc_path.to_string());
let provider = ProviderBuilder::new().on_ipc(ipc).await?;
let latest_block = provider.get_block_number().await?;
println!("Latest block: {latest_block}");
Ok(())
}
Find the source code on Github here.
Understanding Fillers
Fillers decorate a
Provider
, filling transaction details before they are sent to the network. Fillers are used to set the nonce, gas price, gas limit, and other transaction details, and are called before any other layer.
Example: recommended_fillers
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example recommended_fillers
//! Example of using the `.with_recommended_fillers()` method in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Adds the `ChainIdFiller`, `GasFiller` and the `NonceFiller` layers.
// This is the recommended way to set up the provider.
.with_recommended_fillers()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
// Notice that the `nonce` field is set by the `NonceFiller`.
// Notice that the gas related fields are set by the `GasFiller`.
// Notice that the `chain_id` field is set by the `ChainIdFiller`.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Example: gas_filler
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example gas_filler
//! Example of using the `GasFiller` in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Add the `GasFiller` to the provider.
// It is generally recommended to use the `.with_recommended_fillers()` method, which
// includes the `GasFiller`.
.with_gas_estimation()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default()
.with_to(vitalik)
.with_value(U256::from(100))
// Notice that without the `NonceFiller`, you need to set `nonce` field.
.with_nonce(0)
// Notice that without the `ChainIdFiller`, you need to set the `chain_id` field.
.with_chain_id(provider.get_chain_id().await?);
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Update the nonce and send the transaction again.
let tx = tx.with_nonce(1);
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Example: nonce_filler
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example nonce_filler
//! Example of using the `NonceFiller` in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
/// In Ethereum, the nonce of a transaction is a number that represents the number of transactions
/// that have been sent from a particular account. The nonce is used to ensure that transactions are
/// processed in the order they are intended, and to prevent the same transaction from being
/// processed multiple times.
///
/// The nonce manager in Alloy is a layer that helps you manage the nonce
/// of transactions by keeping track of the current nonce for a given account and automatically
/// incrementing it as needed. This can be useful if you want to ensure that transactions are sent
/// in the correct order, or if you want to avoid having to manually manage the nonce yourself.
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Add the `NonceFiller` to the provider.
// It is generally recommended to use the `.with_recommended_fillers()` method, which
// includes the `NonceFiller`.
//
// The `NonceFiller` has two types: `Cached` and `Simple`.
// Unlike `Cached`, `Simple` does not store the transaction count locally,
// which results in more frequent calls to the provider, but it is more resilient to chain
// reorganizations.
.with_cached_nonce_management()
// .with_simple_nonce_management()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default()
.with_to(vitalik)
.with_value(U256::from(100))
// Notice that without the `GasFiller`, you need to set the gas related fields.
.with_gas_limit(21_000)
.with_max_fee_per_gas(20_000_000_000)
.with_max_priority_fee_per_gas(1_000_000_000)
// Notice that without the `ChainIdFiller`, you need to set the `chain_id` field.
.with_chain_id(provider.get_chain_id().await?);
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Example: wallet_filler
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example wallet_filler
//! Example of using the `WalletFiller` in the provider.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
node_bindings::Anvil,
primitives::{address, b256, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
signers::local::PrivateKeySigner,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().try_spawn()?;
// Set up signer from the first default Anvil account (Alice).
let signer: PrivateKeySigner = anvil.keys()[0].clone().into();
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let rpc_url = anvil.endpoint_url();
let provider = ProviderBuilder::new()
// Add the `WalletFiller` to the provider
.wallet(wallet)
.on_http(rpc_url);
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default()
.with_to(vitalik)
.with_value(U256::from(100))
// Notice that without the `ChainIdFiller`, you need to set the `chain_id` field.
.with_chain_id(provider.get_chain_id().await?)
// Notice that without the `NonceFiller`, you need to manually set the nonce field.
.with_nonce(0)
// Notice that without the `GasFiller`, you need to set the gas related fields.
.max_fee_per_gas(20_000_000_000)
.max_priority_fee_per_gas(1_000_000_000)
.with_gas_limit(21_000);
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
println!(
"Node hash matches expected hash: {}",
node_hash == b256!("eb56033eab0279c6e9b685a5ec55ea0ff8d06056b62b7f36974898d4fbb57e64")
);
// Send the transaction and wait for the broadcast.
let pending_tx = builder.register().await?;
println!("Pending transaction hash matches node hash: {}", *pending_tx.tx_hash() == node_hash);
let tx_hash = pending_tx.await?;
assert_eq!(tx_hash, node_hash);
println!("Transaction hash matches node hash: {}", tx_hash == node_hash);
// Wait for the transaction to be included and get the receipt.
let receipt =
provider.get_transaction_receipt(tx_hash).await?.expect("Transaction receipt not found");
let receipt_hash = receipt.transaction_hash;
assert_eq!(receipt_hash, node_hash);
println!("Transaction receipt hash matches node hash: {}", receipt_hash == node_hash);
Ok(())
}
Find the source code on Github here.
Queries
Example: query_contract_storage
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example query_contract_storage
//! Example of querying contract storage from the Ethereum network.
use alloy::{
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get storage slot 0 from the Uniswap V3 USDC-ETH pool on Ethereum mainnet.
let pool_address = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640");
let storage_slot = U256::from(0);
// The provider calls the RPC at the latest block by default. A block can exlpicitly be set
// using `.block()`.
let storage = provider.get_storage_at(pool_address, storage_slot).await?;
println!("Slot 0: {storage:?}");
Ok(())
}
Find the source code on Github here.
Example: query_deployed_bytecode
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example query_deployed_bytecode
//! Example of querying deployed bytecode of a contract on the Ethereum network.
use alloy::{
primitives::address,
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get the bytecode of the Uniswap V3 USDC-ETH pool on Ethereum mainnet.
let pool_address = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640");
let bytecode = provider.get_code_at(pool_address).await?;
println!("Bytecode: {bytecode:?}");
Ok(())
}
Find the source code on Github here.
Example: query_logs
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example query_logs
//! Example of querying logs from the Ethereum network.
use alloy::{
primitives::{address, b256},
providers::{Provider, ProviderBuilder},
rpc::types::Filter,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get logs from the latest block
let latest_block = provider.get_block_number().await?;
// Create a filter to get all logs from the latest block.
let filter = Filter::new().from_block(latest_block);
// Get all logs from the latest block that match the filter.
let logs = provider.get_logs(&filter).await?;
for log in logs {
println!("{log:?}");
}
// Get all logs from the latest block that match the transfer event signature/topic.
let transfer_event_signature =
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
let filter = Filter::new().event_signature(transfer_event_signature).from_block(latest_block);
// You could also use the event name instead of the event signature like so:
// .event("Transfer(address,address,uint256)")
// Get all logs from the latest block that match the filter.
let logs = provider.get_logs(&filter).await?;
for log in logs {
println!("Transfer event: {log:?}");
}
// Get all logs from the latest block emitted by the UNI token address.
let uniswap_token_address = address!("1f9840a85d5aF5bf1D1762F925BDADdC4201F984");
let filter = Filter::new().address(uniswap_token_address).from_block(latest_block);
// Get all logs from the latest block that match the filter.
let logs = provider.get_logs(&filter).await?;
for log in logs {
println!("Uniswap token logs: {log:?}");
}
Ok(())
}
Find the source code on Github here.
Transactions
- Using the Transaction Builder
- Sending an EIP-1559 transaction
- Sending a legacy transaction
- Sending an EIP-4844 transaction
- Sending an EIP-7702 transaction
- Using access lists
Using the TransactionBuilder
The
TransactionBuilder
is a network specific transaction builder configurable with .with_*
methods.
Common fields one can configure are:
- with_from
- with_to
- with_nonce
- with_chain_id
- with_value
- with_gas_limit
- with_max_priority_fee_per_gas
- with_max_fee_per_gas
It is generally recommended to use the builder pattern, as shown, rather than directly setting values (with_to
versus set_to
).
//! Example showing how to build a transaction using the `TransactionBuilder`
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_chain_id(provider.get_chain_id().await?)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
It is recommended to use the .with_recommended_fillers()
method on the ProviderBuilder to automatically fill fields for you.
Example: recommended_fillers
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example recommended_fillers
//! Example of using the `.with_recommended_fillers()` method in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Adds the `ChainIdFiller`, `GasFiller` and the `NonceFiller` layers.
// This is the recommended way to set up the provider.
.with_recommended_fillers()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
// Notice that the `nonce` field is set by the `NonceFiller`.
// Notice that the gas related fields are set by the `GasFiller`.
// Notice that the `chain_id` field is set by the `ChainIdFiller`.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Sending an EIP-1559 transaction
Example: send_eip1559_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_eip1559_transaction
//! Example showing how to send an [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) transaction.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_chain_id(provider.get_chain_id().await?)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
Find the source code on Github here.
Sending a legacy transaction
Example: send_legacy_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_legacy_transaction
//! Example showing how to send a legacy transaction.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// // Spin up a local Anvil node.
// // Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_value(U256::from(100))
.with_gas_price(20_000_000_000)
.with_gas_limit(21_000);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
Find the source code on Github here.
Sending an EIP-4844 transaction
Example: send_eip4844_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_eip4844_transaction
//! Example showing how to send an [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) transaction.
use alloy::{
consensus::{SidecarBuilder, SimpleCoder},
eips::eip4844::DATA_GAS_PER_BLOB,
network::{TransactionBuilder, TransactionBuilder4844},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node with the Cancun hardfork enabled.
// Ensure `anvil` is available in $PATH.
let provider =
ProviderBuilder::new().on_anvil_with_config(|anvil| anvil.args(["--hardfork", "cancun"]));
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Create a sidecar with some data.
let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(b"Blobs are fun!");
let sidecar = sidecar.build()?;
// Build a transaction to send the sidecar from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let gas_price = provider.get_gas_price().await?;
let eip1559_est = provider.estimate_eip1559_fees(None).await?;
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_max_fee_per_blob_gas(gas_price)
.with_max_fee_per_gas(eip1559_est.max_fee_per_gas)
.with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas)
.with_blob_sidecar(sidecar);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
assert_eq!(
receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"),
DATA_GAS_PER_BLOB as u128
);
Ok(())
}
Find the source code on Github here.
Sending an EIP-7702 transaction
Example: send_eip7702_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_eip7702_transaction
//! Example showing how to send an [EIP-7702](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md) transaction.
use alloy::{
eips::eip7702::Authorization,
network::{EthereumWallet, TransactionBuilder, TransactionBuilder7702},
node_bindings::Anvil,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::{local::PrivateKeySigner, SignerSync},
sol,
};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
// solc v0.8.25 Log.sol --via-ir --optimize --bin
sol!(
#[allow(missing_docs)]
#[sol(rpc, bytecode = "6080806040523460135760c9908160188239f35b5f80fdfe6004361015600b575f80fd5b5f3560e01c80637b3ab2d014605f57639ee1a440146027575f80fd5b34605b575f366003190112605b577f2d67bb91f17bca05af6764ab411e86f4ddf757adb89fcec59a7d21c525d417125f80a1005b5f80fd5b34605b575f366003190112605b577fbcdfe0d5b27dd186282e187525415c57ea3077c34efb39148111e4d342e7ab0e5f80a100fea2646970667358221220f6b42b522bc9fb2b4c7d7e611c7c3e995d057ecab7fd7be4179712804c886b4f64736f6c63430008190033")]
contract Log {
#[derive(Debug)]
event Hello();
event World();
function emitHello() public {
emit Hello();
}
function emitWorld() public {
emit World();
}
}
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node with the Prague hardfork enabled.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().arg("--hardfork").arg("prague").try_spawn()?;
// Create two users, Alice and Bob.
// Alice will sign the authorization and Bob will send the transaction.
let alice: PrivateKeySigner = anvil.keys()[0].clone().into();
let bob: PrivateKeySigner = anvil.keys()[1].clone().into();
// Create a provider with the wallet for only Bob (not Alice).
let rpc_url = anvil.endpoint_url();
let wallet = EthereumWallet::from(bob.clone());
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Deploy the contract Alice will authorize.
let contract = Log::deploy(&provider).await?;
// Create an authorization object for Alice to sign.
let authorization = Authorization {
chain_id: anvil.chain_id(),
// Reference to the contract that will be set as code for the authority.
address: *contract.address(),
nonce: provider.get_transaction_count(alice.address()).await?,
};
// Alice signs the authorization.
let signature = alice.sign_hash_sync(&authorization.signature_hash())?;
let signed_authorization = authorization.into_signed(signature);
// Collect the calldata required for the transaction.
let call = contract.emitHello();
let emit_hello_calldata = call.calldata().to_owned();
// Build the transaction.
let tx = TransactionRequest::default()
.with_to(alice.address())
.with_authorization_list(vec![signed_authorization])
.with_input(emit_hello_calldata);
// 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")
);
assert!(receipt.status());
assert_eq!(receipt.from, bob.address());
assert_eq!(receipt.to, Some(alice.address()));
assert_eq!(receipt.inner.logs().len(), 1);
assert_eq!(receipt.inner.logs()[0].address(), alice.address());
Ok(())
}
Find the source code on Github here.
Using access lists
Example: with_access_list
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example with_access_list
//! Example of sending a EIP-1559 transaction with access list.
use alloy::{
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
sol,
};
use eyre::Result;
// Codegen from artifact.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
SimpleStorage,
"examples/artifacts/SimpleStorage.json"
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Deploy the `SimpleStorage` contract.
let contract_address = SimpleStorage::deploy_builder(provider.clone(), "initial".to_string())
.from(alice)
.deploy()
.await?;
let contract = SimpleStorage::new(contract_address, provider.clone());
// Build a transaction to set the values of the contract.
// The `from` field is automatically filled to the first signer's address (Alice).
let set_value_call = contract.setValues("hello".to_string(), "world".to_string());
let calldata = set_value_call.calldata().to_owned();
let tx = TransactionRequest::default().from(bob).to(contract_address).input(calldata.into());
// Create an access list for the transaction.
let access_list_with_gas_used = provider.create_access_list(&tx).await?;
// Add the access list to the transaction.
let tx_with_access_list = tx.access_list(access_list_with_gas_used.access_list);
// Send the transaction with the access list.
let tx_hash = provider.send_transaction(tx_with_access_list).await?.watch().await?;
println!("Transaction hash: {tx_hash}");
// Check the value of the contract.
let value = contract.getValue().call().await?._0;
assert_eq!(value, "hello");
Ok(())
}
Find the source code on Github here.
The sol!
procedural macro
The sol!
procedural macro parses Solidity syntax to generate types that implement alloy-sol-types traits. It uses
syn-solidity
, a syn-powered Solidity parser. It aims to mimic the behavior of the official Solidity compiler (solc
) when it comes to parsing valid Solidity code. This means that all valid Solidity code, as recognized by solc
0.5.0
and above is supported.
In its most basic form sol!
is used like this:
use alloy::{primitives::U256, sol};
// Declare a Solidity type in standard Solidity
sol! {
#[derive(Debug)] // ... with attributes too!
struct Foo {
uint256 bar;
bool baz;
}
}
// A corresponding Rust struct is generated:
// #[derive(Debug)]
// pub struct Foo {
// pub bar: U256,
// pub baz: bool,
// }
let foo = Foo { bar: U256::from(42), baz: true };
println!("{foo:#?}");
Usage
There are multiple ways to use the sol!
macro.
You can directly write Solidity code:
sol! {
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
Or provide a path to a Solidity file:
sol!(
"artifacts/Counter.sol"
);
Alternatively, if you enable the json
feature flag, you can provide an ABI, or a path to one, in JSON format:
sol!(
ICounter,
r#"[
{
"type": "function",
"name": "increment",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "number",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "setNumber",
"inputs": [
{
"name": "newNumber",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [],
"stateMutability": "nonpayable"
}
]"#
);
This is the same as:
sol! {
interface ICounter {
uint256 public number;
function setNumber(uint256 newNumber);
function increment();
}
}
Alternatively, you can load an ABI by file; the format is either a JSON ABI array
or an object containing an "abi"
key. It supports common artifact formats like Foundry’s:
sol!(
ICounter,
"abi/Counter.json"
);
You can also use functions directly:
sol!(
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
);
println!("Decoding https://etherscan.io/tx/0xd1b449d8b1552156957309bffb988924569de34fbf21b51e7af31070cc80fe9a");
let input = hex::decode("0x38ed173900000000000000000000000000000000000000000001a717cc0a3e4f84c00000000000000000000000000000000000000000000000000000000000000283568400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000201f129111c60401630932d9f9811bd5b5fff34e000000000000000000000000000000000000000000000000000000006227723d000000000000000000000000000000000000000000000000000000000000000200000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7")?;
// Decode the input using the generated `swapExactTokensForTokens` bindings.
let decoded = swapExactTokensForTokensCall::abi_decode(&input, false);
Attributes
Combined with the sol!
macro’s #[sol(rpc)]
attribute,
CallBuilder
can be used to interact with
on-chain contracts. The #[sol(rpc)]
attribute generates a method for each function in a contract
that returns a CallBuilder
for that function.
If #[sol(bytecode = "0x...")]
is provided, the contract can be deployed with Counter::deploy
and a new instance will be created.
The bytecode is also loaded from Foundry-style JSON artifact files.
//! Example showing how to use the `#[sol(rpc)]` and #[sol(bytecode = "0x...")] attributes
//! interacting with it.
use alloy::{primitives::U256, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
sol! {
#[allow(missing_docs)]
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
#[sol(rpc, bytecode="6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract.
let contract = Counter::deploy(&provider).await?;
println!("Deployed contract at address: {}", contract.address());
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
let number = builder.call().await?.number.to_string();
println!("Retrieved number: {number}");
Ok(())
}
The transaction lifecycle
This article will walk you through the process of defining a transaction to send 100 wei
from Alice
to Bob
, signing the transaction and broadcasting the signed transaction to the Ethereum network.
Let’s express our intent in the form of a
TransactionRequest
:
// Build a transaction to send 100 wei from Alice to Bob.
let tx = TransactionRequest::default()
.with_from(alice)
.with_to(bob)
.with_nonce(nonce)
.with_chain_id(chain_id)
.with_value(U256::from(100))
.with_gas_price(gas_price)
.with_gas_limit(gas_limit);
Setup
First we will set up our environment:
We start by defining the RPC URL of our local Ethereum node Anvil node.
If you do not have Anvil
installed see the Foundry installation instructions.
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().try_spawn()?;
// Get the RPC URL.
let rpc_url = anvil.endpoint().parse()?;
// Alternatively you can use any valid RPC URL found on https://chainlist.org/
let rpc_url = "https://eth.merkle.io".parse()?;
Next let’s define a signer
for Alice. By default Anvil
defines a mnemonic phrase: "test test test test test test test test test test test junk"
. Make sure to not use this mnemonic phrase outside of testing environments. We register the signer in an
EthereumWallet
to be used in the Provider
to sign our future transaction.
Derive the first key of the mnemonic phrase for Alice
:
// Set up signer from the first default Anvil account (Alice).
let signer: PrivateKeySigner = anvil.keys()[0].clone().into();
let wallet = EthereumWallet::from(signer);
Next lets grab the address of our users Alice
and Bob
:
// Create two users, Alice and Bob.
let alice = anvil.addresses()[0];
let bob = anvil.addresses()[1];
Next we can build the
Provider
using the
ProviderBuilder
.
// Create a provider with the wallet.
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http(rpc_url);
Note that we use .with_recommended_fillers()
method on the ProviderBuilder to automatically fill fields.
Let’s modify our original TransactionRequest
to make use of the RecommendedFiller installed on the Provider
to automatically fill out transaction details.
The RecommendedFillers
includes the following fillers:
Because of we are using RecommendedFillers
our TransactionRequest
we only need a subset of the original fields:
// Build a transaction to send 100 wei from Alice to Bob.
let tx = TransactionRequest::default()
- .with_from(alice)
.with_to(bob)
- .with_nonce(nonce)
- .with_chain_id(chain_id)
.with_value(U256::from(100))
- .with_gas_price(gas_price)
- .with_gas_limit(gas_limit);
Changes to:
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_value(U256::from(100));
Much better!
Signing and broadcasting the transaction
Given that we have configured a signer on our Provider
we can sign the transaction locally and broadcast in a single line:
There are three ways to listen for transaction inclusion after broadcasting the transaction, depending on your requirements:
// Send the transaction and listen for the transaction to be broadcasted.
let pending_tx = provider.send_transaction(tx).await?.register().await?;
// Send the transaction and listen for the transaction to be included.
let tx_hash = provider.send_transaction(tx).await?.watch().await?;
// Send the transaction and fetch the receipt after the transaction was included.
let tx_receipt = provider.send_transaction(tx).await?.get_receipt().await?;
Let’s dive deeper into what we just did.
By calling:
let tx_builder = provider.send_transaction(tx).await?;
The
Provider::send_transaction
method returns a
PendingTransactionBuilder
for configuring the pending transaction watcher.
On it we can for example, set the
required_confirmations
or set a
timeout
:
// Configure the pending transaction.
let pending_tx_builder = provider.send_transaction(tx)
.await?
.with_required_confirmations(2)
.with_timeout(Some(std::time::Duration::from_secs(60)));
By passing the TransactionRequest
, we populate any missing fields. This involves filling in details such as the nonce, chain ID, gas price, and gas limit:
// Build a transaction to send 100 wei from Alice to Bob.
let tx = TransactionRequest::default()
+ .with_from(alice)
.with_to(bob)
+ .with_nonce(nonce)
+ .with_chain_id(chain_id)
.with_value(U256::from(100))
+ .with_gas_price(gas_price)
+ .with_gas_limit(gas_limit);
As part Wallet’s fill
method, registered on the Provider
, we build a signed transaction from the populated TransactionRequest
using our signer, Alice.
At this point, the TransactionRequest
becomes a TransactionEnvelope
, ready to send across the network. By calling either
register
,
watch
or
get_receipt
we can broadcast the transaction and track the status of the transaction.
For instance:
// Send the transaction and fetch the receipt after the transaction was included.
let tx_receipt = provider.send_transaction(tx).await?.get_receipt().await?;
The
TransactionReceipt
provides a comprehensive record of the transaction’s journey and outcome, including the transaction hash, block details, gas used, and addresses involved.
pub struct TransactionReceipt {
// ...
/// Transaction Hash.
pub transaction_hash: TxHash,
/// Index within the block.
pub transaction_index: Option<TxIndex>,
/// Hash of the block this transaction was included within.
pub block_hash: Option<BlockHash>,
/// Number of the block this transaction was included within.
pub block_number: Option<BlockNumber>,
/// Gas used by this transaction alone.
pub gas_used: u128,
/// Address of the sender.
pub from: Address,
/// Address of the receiver. None when its a contract creation transaction.
pub to: Option<Address>,
/// Contract address created, or None if not a deployment.
pub contract_address: Option<Address>,
// ...
}
This completes the journey of broadcasting a signed transaction. Once the transaction is included in a block, it becomes an immutable part of the Ethereum blockchain, ensuring that the transfer of 100 wei
from Alice
to Bob
is recorded permanently.
Putting it all together
//! Example of how to transfer ETH from one account to another.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx =
TransactionRequest::default().with_from(alice).with_to(bob).with_value(U256::from(100));
// Send the transaction and listen for the transaction to be included.
let tx_hash = provider.send_transaction(tx).await?.watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Big numbers
Example: comparison_equivalence
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example comparison_equivalence
//! Example of comparison and equivalence of `U256` instances.
use alloy::primitives::U256;
/// `U256` implements traits in `std::cmp`, that means `U256` instances
/// can be easily compared using standard Rust operators.
fn main() {
// a == b
let a = U256::from(100_u32);
let b = U256::from(100_u32);
assert!(a == b);
// a < b
let a = U256::from(1_u32);
let b = U256::from(100_u32);
assert!(a < b);
// a <= b
let a = U256::from(100_u32);
let b = U256::from(100_u32);
assert!(a <= b);
// a > b
let a = U256::from(100_u32);
let b = U256::from(1_u32);
assert!(a > b);
// a >= b
let a = U256::from(100_u32);
let b = U256::from(100_u32);
assert!(a >= b);
// a == 0
let a = U256::ZERO;
assert!(a.is_zero());
}
Find the source code on Github here.
Example: conversion
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example conversion
//! Example of converting `U256` to native Rust types.
use alloy::primitives::{utils::format_units, U256};
use eyre::Result;
/// `U256` provides useful conversion functions to enable transformation into native Rust types.
///
/// It is important to note that converting a big-number to a floating point type (such as a `f32`
/// or `f64`) can result in a loss of precision, since you cannot fit 256 bits of information into
/// 64 bits.
///
/// However, there may be cases where you want to perform conversions for presentation purposes.
/// For example, you may want to display a large number to the user in a more readable format.
fn main() -> Result<()> {
let num = U256::from(42_u8);
let a: u128 = num.to::<u128>();
assert_eq!(a, 42);
let b: u64 = num.to::<u64>();
assert_eq!(b, 42);
let c: u32 = num.to::<u32>();
assert_eq!(c, 42);
let d: usize = num.to::<usize>();
assert_eq!(d, 42);
let e: String = num.to_string();
assert_eq!(e, "42");
let f: String = format_units(num, 4)?;
assert_eq!(f, "0.0042");
Ok(())
}
Find the source code on Github here.
Example: create_instances
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example create_instances
//! Example of creating instances of `U256` from strings and numbers.
use alloy::primitives::{
utils::{parse_units, ParseUnits},
U256,
};
use eyre::Result;
use std::str::FromStr;
fn main() -> Result<()> {
// From strings
let a = U256::from_str("42")?;
assert_eq!(a.to_string(), "42");
let amount = "42";
let units = 4;
let b: ParseUnits = parse_units(amount, units)?;
assert_eq!(b.to_string(), "420000");
// From numbers
let c = U256::from(42_u8);
assert_eq!(c.to_string(), "42");
let d = U256::from(42_u16);
assert_eq!(d.to_string(), "42");
let e = U256::from(42_u32);
assert_eq!(e.to_string(), "42");
let f = U256::from(42_u64);
assert_eq!(f.to_string(), "42");
let g = U256::from(42_u128);
assert_eq!(g.to_string(), "42");
let h = U256::from(0x2a);
assert_eq!(h.to_string(), "42");
let i = U256::from(42);
assert_eq!(i.to_string(), "42");
Ok(())
}
Find the source code on Github here.
Example: math_operations
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example math_operations
//! Example of performing arithmetic operations with `U256`.
use alloy::primitives::{utils::format_units, U256};
use eyre::Result;
use std::ops::{Div, Mul};
/// `U256` implements traits in `std::ops`, that means it supports arithmetic operations
/// using standard Rust operators `+`, `-`. `*`, `/`, `%`, along with additional utilities to
/// perform common mathematical tasks.
fn main() -> Result<()> {
let a = U256::from(10);
let b = U256::from(2);
// addition
let sum = a + b;
assert_eq!(sum, U256::from(12));
// subtraction
let difference = a - b;
assert_eq!(difference, U256::from(8));
// multiplication
let product = a * b;
assert_eq!(product, U256::from(20));
// division
let quotient = a / b;
assert_eq!(quotient, U256::from(5));
// modulo
let remainder = a % b;
assert_eq!(remainder, U256::ZERO); // equivalent to `U256::from(0)`
// exponentiation
let power = a.pow(b);
assert_eq!(power, U256::from(100));
// Multiply two 'ether' numbers:
// Big numbers are integers, that can represent fixed point numbers.
// For instance, 1 ether has 18 fixed
// decimal places (1.000000000000000000), and its big number
// representation is 10^18 = 1000000000000000000.
// When we multiply such numbers we are summing up their exponents.
// So if we multiply 10^18 * 10^18 we get 10^36, that is obviously incorrect.
// In order to get the correct result we need to divide by 10^18.
let eth1 = U256::from(10_000000000000000000_u128); // 10 ether
let eth2 = U256::from(20_000000000000000000_u128); // 20 ether
let base = U256::from(10).pow(U256::from(18));
let mul = eth1.mul(eth2).div(base); // We also divide by 10^18
let s: String = format_units(mul, "ether")?;
assert_eq!(s, "200.000000000000000000"); // 200
Ok(())
}
Find the source code on Github here.
Example: math_utilities
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example math_utilities
//! Example of using math utilities to handle big numbers in 'wei' units.
use alloy::primitives::{
utils::{format_units, parse_units},
U256,
};
use eyre::Result;
fn main() -> Result<()> {
parse_units_example()?;
format_units_example()?;
Ok(())
}
/// dApps business logics handle big numbers in 'wei' units (i.e. sending transactions, on-chain
/// math, etc.). We provide convenient methods to map user inputs (usually in 'ether' or 'gwei')
/// into 'wei' format.
fn parse_units_example() -> Result<()> {
let pu = parse_units("1.0", "wei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1));
let pu = parse_units("1.0", "kwei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000));
let pu = parse_units("1.0", "mwei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000));
let pu = parse_units("1.0", "gwei")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000));
let pu = parse_units("1.0", "szabo")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000_u128));
let pu = parse_units("1.0", "finney")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000000_u128));
let pu = parse_units("1.0", "ether")?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000000000_u128));
let pu = parse_units("1.0", 18)?;
let num: U256 = pu.into();
assert_eq!(num, U256::from(1000000000000000000_u128));
Ok(())
}
/// dApps business logics handle big numbers in 'wei' units (i.e. sending transactions, on-chain
/// math, etc.). On the other hand it is useful to convert big numbers into user readable formats
/// when displaying on a UI. Generally dApps display numbers in 'ether' and 'gwei' units,
/// respectively for displaying amounts and gas. The `format_units` function will format a big
/// number into a user readable string.
fn format_units_example() -> Result<()> {
// 1 ETHER = 10^18 WEI
let one_ether = U256::from(1000000000000000000_u128);
let num: String = format_units(one_ether, "wei")?;
assert_eq!(num, "1000000000000000000.0");
let num: String = format_units(one_ether, "gwei")?;
assert_eq!(num, "1000000000.000000000");
let num: String = format_units(one_ether, "ether")?;
assert_eq!(num, "1.000000000000000000");
// 1 GWEI = 10^9 WEI
let one_gwei = U256::from(1000000000_u128);
let num: String = format_units(one_gwei, 0)?;
assert_eq!(num, "1000000000.0");
let num: String = format_units(one_gwei, "wei")?;
assert_eq!(num, "1000000000.0");
let num: String = format_units(one_gwei, "kwei")?;
assert_eq!(num, "1000000.000");
let num: String = format_units(one_gwei, "mwei")?;
assert_eq!(num, "1000.000000");
let num: String = format_units(one_gwei, "gwei")?;
assert_eq!(num, "1.000000000");
let num: String = format_units(one_gwei, "szabo")?;
assert_eq!(num, "0.001000000000");
let num: String = format_units(one_gwei, "finney")?;
assert_eq!(num, "0.000001000000000");
let num: String = format_units(one_gwei, "ether")?;
assert_eq!(num, "0.000000001000000000");
Ok(())
}
Find the source code on Github here.
Contracts
- Deploy from artifact
- Deploy from bytecode
- Deploy from contract
- Interact with ABI
- Interact with contract instance
- Handling unknown return types
Example: deploy_from_artifact
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example deploy_from_artifact
//! Example of deploying a contract from an artifact using the `sol!` macro to Anvil and interacting
//! with it.
use alloy::{primitives::U256, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from artifact.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
Counter,
"examples/artifacts/Counter.json"
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract.
let contract = Counter::deploy(&provider).await?;
println!("Deployed contract at address: {}", contract.address());
// Set the number to 42.
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
// Note: because the artifact generated by `solc` does not include named return values it is
// not possible to derive the return value name `number` from the artifact. This means that the
// return value must be accessed by index - as if it is an unnamed value.
// If you prefer to use named return values, it is recommended to embed the Solidity code
// directly in the `sol!` macro as shown in `deploy_from_contract.rs`.
let number = builder.call().await?._0;
println!("Retrieved number: {number}");
Ok(())
}
Find the source code on Github here.
Example: deploy_from_bytecode
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example deploy_from_bytecode
//! Example of deploying a contract at runtime from Solidity bytecode to Anvil and interacting with
//! it.
use alloy::{
hex,
network::{ReceiptResponse, TransactionBuilder},
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
sol,
};
use eyre::Result;
// If you have the bytecode known at build time, use the `deploy_from_contract` example.
// This method benefits from using bytecode at runtime, e.g., from newly deployed contracts, to
// analyze the behavior.
sol! {
#[allow(missing_docs)]
#[sol(rpc)]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract from bytecode at runtime.
let bytecode = hex::decode(
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
"6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
)?;
let tx = TransactionRequest::default().with_deploy_code(bytecode);
// Deploy the contract.
let receipt = provider.send_transaction(tx).await?.get_receipt().await?;
let contract_address = receipt.contract_address().expect("Failed to get contract address");
let contract = Counter::new(contract_address, &provider);
println!("Deployed contract at address: {}", contract.address());
// Set number
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
let number = builder.call().await?.number.to_string();
println!("Retrieved number: {number}");
Ok(())
}
Find the source code on Github here.
Example: deploy_from_contract
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example deploy_from_contract
//! Example of deploying a contract from Solidity code using the `sol!` macro to Anvil and
//! interacting with it.
use alloy::{primitives::U256, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
sol! {
#[allow(missing_docs)]
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
#[sol(rpc, bytecode="6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract.
let contract = Counter::deploy(&provider).await?;
println!("Deployed contract at address: {}", contract.address());
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
let number = builder.call().await?.number.to_string();
println!("Retrieved number: {number}");
Ok(())
}
Find the source code on Github here.
Example: interact_with_abi
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example interact_with_abi
//! Example of generating code from ABI file using the `sol!` macro to interact with the contract.
use alloy::{primitives::address, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from ABI file to interact with the contract.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
IWETH9,
"examples/abi/IWETH9.json"
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let provider =
ProviderBuilder::new().on_anvil_with_wallet_and_config(|anvil| anvil.fork(rpc_url));
// Create a contract instance.
let contract = IWETH9::new(address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), provider);
// Call the contract, retrieve the total supply.
let total_supply = contract.totalSupply().call().await?._0;
println!("WETH total supply is {total_supply}");
Ok(())
}
Find the source code on Github here.
Example: interact_with_contract_instance
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example interact_with_contract_instance
//! This example demonstrates how to interact with a contract that is already deployed onchain using
//! the `ContractInstance` interface.
use alloy::{
contract::{ContractInstance, Interface},
dyn_abi::DynSolValue,
network::{Ethereum, TransactionBuilder},
primitives::{hex, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
transports::http::{Client, Http},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract from bytecode at runtime.
let bytecode = hex::decode(
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
//
// contract Counter {
// uint256 public number;
//
// function setNumber(uint256 newNumber) public {
// number = newNumber;
// }
//
// function increment() public {
// number++;
// }
// }
"6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033"
)?;
let tx = TransactionRequest::default().with_deploy_code(bytecode);
let contract_address = provider
.send_transaction(tx)
.await?
.get_receipt()
.await?
.contract_address
.expect("Failed to get contract address");
// Get the contract ABI.
let path = std::env::current_dir()?.join("examples/contracts/examples/artifacts/Counter.json");
// Read the artifact which contains `abi`, `bytecode`, `deployedBytecode` and `metadata`.
let artifact = std::fs::read(path).expect("Failed to read artifact");
let json: serde_json::Value = serde_json::from_slice(&artifact)?;
// Get `abi` from the artifact.
let abi_value = json.get("abi").expect("Failed to get ABI from artifact");
let abi = serde_json::from_str(&abi_value.to_string())?;
// Create a new `ContractInstance` of the `Counter` contract from the abi
let contract: ContractInstance<Http<Client>, _, Ethereum> =
ContractInstance::new(contract_address, provider.clone(), Interface::new(abi));
// Set the number to 42.
let number_value = DynSolValue::from(U256::from(42));
let tx_hash = contract.function("setNumber", &[number_value])?.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let tx_hash = contract.function("increment", &[])?.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let number_value = contract.function("number", &[])?.call().await?;
let number = number_value.first().unwrap().as_uint().unwrap().0;
assert_eq!(U256::from(43), number);
println!("Retrieved number: {number}");
// Try calling a function that does not exist.
let unknown_function = contract.function("decrement", &[]).unwrap_err();
assert!(unknown_function.to_string().contains("function decrement does not exist"));
Ok(())
}
Find the source code on Github here.
Example: jsonrpc_error_decoding
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example jsonrpc_error_decoding
//! This example demonstrates how to decode a custom JSON RPC error.
use alloy::{primitives::U256, rpc::json_rpc::ErrorPayload, sol};
use eyre::Result;
// Define a custom error using the sol! macro.
sol! {
#[allow(missing_docs)]
library Errors {
error SomeCustomError(uint256 a);
}
}
fn main() -> Result<()> {
// Sample JSON error payload from an Ethereum JSON RPC response.
let json = r#"{"code":3,"message":"execution reverted: ","data":"0x810f00230000000000000000000000000000000000000000000000000000000000000001"}"#;
// Parse the JSON into an `ErrorPayload` struct.
let payload: ErrorPayload = serde_json::from_str(json)?;
// Attempt to decode the error payload as our custom error.
let Errors::ErrorsErrors::SomeCustomError(value) =
payload.as_decoded_error::<Errors::ErrorsErrors>(false).unwrap();
assert_eq!(value.a, U256::from(1));
Ok(())
}
Find the source code on Github here.
Example: unknown_return_types
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example unknown_return_types
//! Example demonstrating how one can handle unknown / complex return types using `DynSol`.
use alloy::{
contract::{ContractInstance, Interface},
dyn_abi::DynSolValue,
json_abi::JsonAbi,
network::{Ethereum, TransactionBuilder},
primitives::{hex, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
transports::http::{Client, Http},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Get the first account from the wallet, Alice.
let alice = provider.get_accounts().await?[0];
let bytecode = hex::decode(
// contract Colors {
// struct Color {
// uint8 r;
// uint8 g;
// uint8 b;
// }
//
// mapping(address => Color) public colors;
//
// function setColor(uint8 r, uint8 g, uint8 b) public {
// colors[msg.sender] = Color(r, g, b);
// }
//
// function getColor(address user) public view returns (Color memory) {
// return colors[user];
// }
//
// function getColorAsTuple(
// address user
// ) public view returns (uint8, uint8, uint8) {
// return (colors[user].r, colors[user].g, colors[user].b);
// }
// }
"6080604052348015600f57600080fd5b506105fb8061001f6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063610c76f01461005157806384b5e5961461006d57806399efff171461009d578063befdb4f6146100cf575b600080fd5b61006b60048036038101906100669190610435565b610101565b005b610087600480360381019061008291906104e6565b6101ce565b6040516100949190610564565b60405180910390f35b6100b760048036038101906100b291906104e6565b61027d565b6040516100c69392919061058e565b60405180910390f35b6100e960048036038101906100e491906104e6565b61037c565b6040516100f89392919061058e565b60405180910390f35b60405180606001604052808460ff1681526020018360ff1681526020018260ff168152506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000160006101000a81548160ff021916908360ff16021790555060208201518160000160016101000a81548160ff021916908360ff16021790555060408201518160000160026101000a81548160ff021916908360ff160217905550905050505050565b6101d66103cd565b6000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206040518060600160405290816000820160009054906101000a900460ff1660ff1660ff1681526020016000820160019054906101000a900460ff1660ff1660ff1681526020016000820160029054906101000a900460ff1660ff1660ff16815250509050919050565b60008060008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160009054906101000a900460ff166000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160019054906101000a900460ff166000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000160029054906101000a900460ff169250925092509193909250565b60006020528060005260406000206000915090508060000160009054906101000a900460ff16908060000160019054906101000a900460ff16908060000160029054906101000a900460ff16905083565b6040518060600160405280600060ff168152602001600060ff168152602001600060ff1681525090565b600080fd5b600060ff82169050919050565b610412816103fc565b811461041d57600080fd5b50565b60008135905061042f81610409565b92915050565b60008060006060848603121561044e5761044d6103f7565b5b600061045c86828701610420565b935050602061046d86828701610420565b925050604061047e86828701610420565b9150509250925092565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104b382610488565b9050919050565b6104c3816104a8565b81146104ce57600080fd5b50565b6000813590506104e0816104ba565b92915050565b6000602082840312156104fc576104fb6103f7565b5b600061050a848285016104d1565b91505092915050565b61051c816103fc565b82525050565b6060820160008201516105386000850182610513565b50602082015161054b6020850182610513565b50604082015161055e6040850182610513565b50505050565b60006060820190506105796000830184610522565b92915050565b610588816103fc565b82525050565b60006060820190506105a3600083018661057f565b6105b0602083018561057f565b6105bd604083018461057f565b94935050505056fea2646970667358221220ce426adf2fbf80a861f23a5eb1e99a281bb07e427b9beed059e09c285f16db6c64736f6c634300081a0033"
)?;
let deploy_tx = TransactionRequest::default().from(alice).with_deploy_code(bytecode);
let contract_address = provider
.send_transaction(deploy_tx)
.await?
.get_receipt()
.await?
.contract_address
.expect("Failed to get contract address");
// Get the contract abi.
let path = std::env::current_dir()?.join("examples/contracts/examples/abi/Colors.json");
let contents = std::fs::read(path)?;
let abi: JsonAbi = serde_json::from_slice(&contents)?;
// Create a new `ContractInstance` of the Counter contract from the abi.
let counter_instance: ContractInstance<Http<Client>, _, Ethereum> =
ContractInstance::new(contract_address, provider.clone(), Interface::new(abi));
// Interact with the contract.
assert_eq!(counter_instance.abi().functions().count(), 4);
// Set color to white.
let r = DynSolValue::Uint(U256::from(255), 8); // uint8
let g = DynSolValue::Uint(U256::from(255), 8); // uint8
let b = DynSolValue::Uint(U256::from(255), 8); // uint8
let set_color_func = counter_instance.function("setColor", &[r, g, b])?;
let set_color_receipt = set_color_func.send().await?.get_receipt().await?;
assert!(set_color_receipt.status());
// Get the color.
let get_color_func = counter_instance.function("getColor", &[DynSolValue::Address(alice)])?;
let get_color_result = get_color_func.call().await?;
// The `r`, `g`, `b` values in the `Color` struct get converted to a `DynSolValue::Tuple`.
assert!(get_color_result.len() == 1);
for value in get_color_result {
if let DynSolValue::Tuple(struct_as_tuple) = value {
println!("{struct_as_tuple:?}");
}
}
// Get the color as tuple.
let get_color_tuple =
counter_instance.function("getColorAsTuple", &[DynSolValue::Address(alice)])?;
let get_color_tuple_result = get_color_tuple.call().await?;
// The `r`, `g`, `b` are returned as a solidity tuple and hence represented as individual
// `DynSolValue::Uint`.
assert!(get_color_tuple_result.len() == 3);
for value in get_color_tuple_result {
println!("{value:?}");
}
Ok(())
}
Find the source code on Github here.
Fillers
Example: gas_filler
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example gas_filler
//! Example of using the `GasFiller` in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Add the `GasFiller` to the provider.
// It is generally recommended to use the `.with_recommended_fillers()` method, which
// includes the `GasFiller`.
.with_gas_estimation()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default()
.with_to(vitalik)
.with_value(U256::from(100))
// Notice that without the `NonceFiller`, you need to set `nonce` field.
.with_nonce(0)
// Notice that without the `ChainIdFiller`, you need to set the `chain_id` field.
.with_chain_id(provider.get_chain_id().await?);
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Update the nonce and send the transaction again.
let tx = tx.with_nonce(1);
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Example: nonce_filler
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example nonce_filler
//! Example of using the `NonceFiller` in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
/// In Ethereum, the nonce of a transaction is a number that represents the number of transactions
/// that have been sent from a particular account. The nonce is used to ensure that transactions are
/// processed in the order they are intended, and to prevent the same transaction from being
/// processed multiple times.
///
/// The nonce manager in Alloy is a layer that helps you manage the nonce
/// of transactions by keeping track of the current nonce for a given account and automatically
/// incrementing it as needed. This can be useful if you want to ensure that transactions are sent
/// in the correct order, or if you want to avoid having to manually manage the nonce yourself.
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Add the `NonceFiller` to the provider.
// It is generally recommended to use the `.with_recommended_fillers()` method, which
// includes the `NonceFiller`.
//
// The `NonceFiller` has two types: `Cached` and `Simple`.
// Unlike `Cached`, `Simple` does not store the transaction count locally,
// which results in more frequent calls to the provider, but it is more resilient to chain
// reorganizations.
.with_cached_nonce_management()
// .with_simple_nonce_management()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default()
.with_to(vitalik)
.with_value(U256::from(100))
// Notice that without the `GasFiller`, you need to set the gas related fields.
.with_gas_limit(21_000)
.with_max_fee_per_gas(20_000_000_000)
.with_max_priority_fee_per_gas(1_000_000_000)
// Notice that without the `ChainIdFiller`, you need to set the `chain_id` field.
.with_chain_id(provider.get_chain_id().await?);
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Example: recommended_fillers
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example recommended_fillers
//! Example of using the `.with_recommended_fillers()` method in the provider.
use alloy::{
consensus::Transaction,
network::TransactionBuilder,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
// Adds the `ChainIdFiller`, `GasFiller` and the `NonceFiller` layers.
// This is the recommended way to set up the provider.
.with_recommended_fillers()
.on_anvil_with_wallet();
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
// Notice that the `nonce` field is set by the `NonceFiller`.
// Notice that the gas related fields are set by the `GasFiller`.
// Notice that the `chain_id` field is set by the `ChainIdFiller`.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction, the nonce (0) is automatically managed by the provider.
let builder = provider.send_transaction(tx.clone()).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 0);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
// Send the transaction, the nonce (1) is automatically managed by the provider.
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
let pending_tx =
provider.get_transaction_by_hash(node_hash).await?.expect("Pending transaction not found");
assert_eq!(pending_tx.nonce(), 1);
println!("Transaction sent with nonce: {}", pending_tx.nonce());
Ok(())
}
Find the source code on Github here.
Example: wallet_filler
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example wallet_filler
//! Example of using the `WalletFiller` in the provider.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
node_bindings::Anvil,
primitives::{address, b256, U256},
providers::{Provider, ProviderBuilder},
rpc::types::request::TransactionRequest,
signers::local::PrivateKeySigner,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().try_spawn()?;
// Set up signer from the first default Anvil account (Alice).
let signer: PrivateKeySigner = anvil.keys()[0].clone().into();
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let rpc_url = anvil.endpoint_url();
let provider = ProviderBuilder::new()
// Add the `WalletFiller` to the provider
.wallet(wallet)
.on_http(rpc_url);
// Build an EIP-1559 type transaction to send 100 wei to Vitalik.
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default()
.with_to(vitalik)
.with_value(U256::from(100))
// Notice that without the `ChainIdFiller`, you need to set the `chain_id` field.
.with_chain_id(provider.get_chain_id().await?)
// Notice that without the `NonceFiller`, you need to manually set the nonce field.
.with_nonce(0)
// Notice that without the `GasFiller`, you need to set the gas related fields.
.max_fee_per_gas(20_000_000_000)
.max_priority_fee_per_gas(1_000_000_000)
.with_gas_limit(21_000);
let builder = provider.send_transaction(tx).await?;
let node_hash = *builder.tx_hash();
println!(
"Node hash matches expected hash: {}",
node_hash == b256!("eb56033eab0279c6e9b685a5ec55ea0ff8d06056b62b7f36974898d4fbb57e64")
);
// Send the transaction and wait for the broadcast.
let pending_tx = builder.register().await?;
println!("Pending transaction hash matches node hash: {}", *pending_tx.tx_hash() == node_hash);
let tx_hash = pending_tx.await?;
assert_eq!(tx_hash, node_hash);
println!("Transaction hash matches node hash: {}", tx_hash == node_hash);
// Wait for the transaction to be included and get the receipt.
let receipt =
provider.get_transaction_receipt(tx_hash).await?.expect("Transaction receipt not found");
let receipt_hash = receipt.transaction_hash;
assert_eq!(receipt_hash, node_hash);
println!("Transaction receipt hash matches node hash: {}", receipt_hash == node_hash);
Ok(())
}
Find the source code on Github here.
Layers
Example: hyper_http_layer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example hyper_http_layer
//! This example demonstrates how to write a custom layer for the [`hyper`] HTTP client that can
//! modify the underlying HTTP request before it is sent.
use alloy::{
node_bindings::Anvil,
providers::{Provider, ProviderBuilder},
rpc::client::RpcClient,
transports::http::{
hyper,
hyper_util::{
client::legacy::{Client, Error},
rt::TokioExecutor,
},
Http, HyperClient, HyperResponse, HyperResponseFut,
},
};
use eyre::Result;
use http_body_util::Full;
use tower::{Layer, Service};
#[tokio::main]
async fn main() -> Result<()> {
// Start an Anvil node.
let anvil = Anvil::new().spawn();
// Create a new Hyper client.
let hyper_client =
Client::builder(TokioExecutor::new()).build_http::<Full<hyper::body::Bytes>>();
// Use tower::ServiceBuilder to stack layers on top of the Hyper client.
let service = tower::ServiceBuilder::new().layer(RequestModifyingLayer).service(hyper_client);
// Instantiate the HyperClient with the stacked layers.
let layer_transport = HyperClient::<Full<hyper::body::Bytes>, _>::with_service(service);
let http = Http::with_client(layer_transport, anvil.endpoint_url());
// Create a new RPC client with the Hyper transport.
let rpc_client = RpcClient::new(http, true);
let provider = ProviderBuilder::new().on_client(rpc_client);
let num = provider.get_block_number().await.unwrap();
assert_eq!(num, 0);
Ok(())
}
// Layer that will be stacked on top of the Hyper client.
struct RequestModifyingLayer;
// Implement the `Layer` trait for the custom layer.
impl<S> Layer<S> for RequestModifyingLayer {
type Service = RequestModifyingService<S>;
fn layer(&self, inner: S) -> Self::Service {
RequestModifyingService { inner }
}
}
// Service that will modify the request before it is sent.
#[derive(Clone)] // Service must be Cloneable.
struct RequestModifyingService<S> {
inner: S,
}
impl<S, B> Service<hyper::Request<B>> for RequestModifyingService<S>
where
S: Service<hyper::Request<B>, Response = HyperResponse, Error = Error>
+ Clone
+ Send
+ Sync
+ 'static,
S::Future: Send,
S::Error: std::error::Error + Send + Sync + 'static,
B: From<Vec<u8>> + Send + 'static + Clone + Sync + std::fmt::Debug,
{
type Error = Error;
type Future = HyperResponseFut;
type Response = HyperResponse;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: hyper::Request<B>) -> Self::Future {
// Modify the request here.
// Example: Add a custom header to the request.
let header = req.headers_mut();
header.insert("x-alloy", "hyper".parse().unwrap());
println!("Request: {:?}", req);
let fut = self.inner.call(req);
Box::pin(fut)
}
}
Find the source code on Github here.
Example: logging_layer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example logging_layer
//! This examples demonstrates how to implement your own custom transport layer.
//! As a demonstration we implement a simple request / response logging layer.
use alloy::{
node_bindings::Anvil,
providers::{Provider, ProviderBuilder},
rpc::{
client::ClientBuilder,
json_rpc::{RequestPacket, ResponsePacket},
},
transports::TransportError,
};
use eyre::Result;
use std::{
fmt::Debug,
future::{Future, IntoFuture},
pin::Pin,
task::{Context, Poll},
};
use tower::{Layer, Service};
struct LoggingLayer;
// Implement tower::Layer for LoggingLayer.
impl<S> Layer<S> for LoggingLayer {
type Service = LoggingService<S>;
fn layer(&self, inner: S) -> Self::Service {
LoggingService { inner }
}
}
// A logging service that wraps an inner service.
#[derive(Debug, Clone)]
struct LoggingService<S> {
inner: S,
}
// Implement tower::Service for LoggingService.
impl<S> Service<RequestPacket> for LoggingService<S>
where
// Constraints on the service.
S: Service<RequestPacket, Response = ResponsePacket, Error = TransportError>,
S::Future: Send + 'static,
S::Response: Send + 'static + Debug,
S::Error: Send + 'static + Debug,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: RequestPacket) -> Self::Future {
println!("Request: {req:?}");
let fut = self.inner.call(req);
Box::pin(async move {
let res = fut.await;
println!("Response: {res:?}");
res
})
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().spawn();
// Create a new client with the logging layer.
let rpc_url = anvil.endpoint_url();
let client = ClientBuilder::default().layer(LoggingLayer).http(rpc_url);
// Create a new provider with the client.
let provider = ProviderBuilder::new().on_client(client);
for _ in 0..10 {
let _block_number = provider.get_block_number().into_future().await?;
}
Ok(())
}
Find the source code on Github here.
Example: retry_layer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example retry_layer
//! This example demonstrates how to use the [`RetryBackoffLayer`] in the provider.
use alloy::{
node_bindings::Anvil,
providers::{Provider, ProviderBuilder},
rpc::client::RpcClient,
transports::layers::RetryBackoffLayer,
};
#[tokio::main]
async fn main() -> eyre::Result<()> {
let anvil = Anvil::new().spawn();
// The maximum number of retries for rate limit errors
let max_retry = 10;
// The initial backoff in milliseconds
let backoff = 1000;
// The number of compute units per second for this provider
let cups = 100;
// Instantiate the RetryBackoffLayer with the configuration
let retry_layer = RetryBackoffLayer::new(max_retry, backoff, cups);
// Add the layer to the transport client.
// The layer will retry all requests that return a rate limit error (eg. 429) until max_retries
// have been reached.
let client = RpcClient::builder().layer(retry_layer).http(anvil.endpoint_url());
let provider = ProviderBuilder::new().on_client(client);
let latest_block = provider.get_block_number().await?;
assert_eq!(latest_block, 0);
Ok(())
}
Find the source code on Github here.
Node bindings
- Deploy contract on local Anvil instance
- Fork instance on Anvil
- Fork provider on Anvil
- Local instance on Anvil
- Local provider on Anvil
- Local provider on Geth
- Local provider on Reth
- Mock WETH balance with Anvil
Example: anvil_deploy_contract
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example anvil_deploy_contract
//! Example of deploying a contract to a local Anvil node using the [`ProviderBuilder`].
use alloy::{primitives::U256, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
sol! {
#[allow(missing_docs)]
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
#[sol(rpc, bytecode="6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract.
let contract = Counter::deploy(&provider).await?;
println!("Deployed contract at address: {}", contract.address());
// Set the number to 42.
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
let number = builder.call().await?.number.to_string();
println!("Retrieved number: {number}");
Ok(())
}
Find the source code on Github here.
Example: anvil_fork_instance
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example anvil_fork_instance
//! Example of spinning up a forked Anvil instance and connecting it with a provider.
use alloy::{
node_bindings::Anvil,
providers::{ext::AnvilApi, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let anvil = Anvil::new().fork(rpc_url).try_spawn()?;
let provider = ProviderBuilder::new().with_recommended_fillers().on_http(anvil.endpoint_url());
// Get node info using the Anvil API.
let info = provider.anvil_node_info().await?;
println!("Node info: {:#?}", info);
assert_eq!(info.environment.chain_id, 1);
assert_eq!(info.fork_config.fork_url, Some(rpc_url.to_string()));
Ok(())
}
Find the source code on Github here.
Example: anvil_fork_provider
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example anvil_fork_provider
//! Example of spinning up a forked Anvil node using the [`ProviderBuilder`].
use alloy::providers::{ext::AnvilApi, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.on_anvil_with_config(|anvil| anvil.fork(rpc_url));
// Get node info using the Anvil API.
let info = provider.anvil_node_info().await?;
println!("Node info: {:#?}", info);
assert_eq!(info.environment.chain_id, 1);
assert_eq!(info.fork_config.fork_url, Some(rpc_url.to_string()));
Ok(())
}
Find the source code on Github here.
Example: anvil_local_instance
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example anvil_local_instance
//! Example of spinning up a local Anvil instance and connecting it with a provider.
use alloy::{
node_bindings::Anvil,
providers::{ext::AnvilApi, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).chain_id(1337).try_spawn()?;
let provider = ProviderBuilder::new().with_recommended_fillers().on_http(anvil.endpoint_url());
// Get node info using the Anvil API.
let info = provider.anvil_node_info().await?;
println!("Node info: {:#?}", info);
assert_eq!(info.environment.chain_id, 1337);
assert_eq!(info.fork_config.fork_url, None);
Ok(())
}
Find the source code on Github here.
Example: anvil_local_provider
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example anvil_local_provider
//! Example of spinning up a local Anvil node using the [`ProviderBuilder`].
use alloy::providers::{ext::AnvilApi, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.on_anvil_with_config(|anvil| anvil.block_time(1).chain_id(1337));
// Get node info using the Anvil API.
let info = provider.anvil_node_info().await?;
println!("Node info: {:#?}", info);
assert_eq!(info.environment.chain_id, 1337);
assert_eq!(info.fork_config.fork_url, None);
Ok(())
}
Find the source code on Github here.
Example: geth_local_instance
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example geth_local_instance
//! Example of spinning up a local Geth node instance and connecting it with a provider.
use alloy::{
node_bindings::Geth,
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Geth node.
// Ensure `geth` is available in $PATH.
let geth = Geth::new().chain_id(1337).port(8545_u16).authrpc_port(8551).spawn();
let provider = ProviderBuilder::new().on_http(geth.endpoint().parse()?);
let chain_id = provider.get_chain_id().await?;
println!("Geth running at: {} with chain id: {chain_id}", geth.endpoint());
assert_eq!(chain_id, 1337);
assert_eq!(geth.port(), 8545);
assert_eq!(geth.auth_port(), Some(8551));
assert_eq!(geth.p2p_port(), None);
Ok(())
}
Find the source code on Github here.
Example: reth_local_instance
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example reth_local_instance
//! Example of spinning up a local Reth node instance and connecting it with a provider.
use alloy::{
node_bindings::Reth,
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Reth node.
// Ensure `reth` is available in $PATH.
let reth = Reth::new().dev().disable_discovery().instance(1).spawn();
let provider = ProviderBuilder::new().on_http(reth.endpoint().parse()?);
let chain_id = provider.get_chain_id().await?;
println!("Reth running at: {} with chain id: {chain_id}", reth.endpoint());
assert_eq!(chain_id, 1337);
assert_eq!(reth.http_port(), 8545);
assert_eq!(reth.ws_port(), 8546);
assert_eq!(reth.auth_port(), Some(8551));
assert_eq!(reth.p2p_port(), None);
Ok(())
}
Find the source code on Github here.
Example: anvil_set_storage_at
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example anvil_set_storage_at
//! Example of mocking WETH balance of a target account using [`AnvilApi::anvil_set_storage_at`].
use alloy::{
primitives::{address, keccak256, utils::parse_units, Address, U256},
providers::{ext::AnvilApi, ProviderBuilder},
sol,
sol_types::SolValue,
};
use eyre::Result;
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
contract IERC20 {
function balanceOf(address target) returns (uint256);
}
);
static WETH_ADDR: Address = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let provider = ProviderBuilder::new().on_anvil_with_config(|anvil| anvil.fork(rpc_url));
// Create an instance of the WETH contract.
let iweth = IERC20::new(WETH_ADDR, provider.clone());
// Random empty account.
let account = address!("F605F9d1cB055E87E30bcAEe4CB9389a35aBe8Ff");
// Get the WETH balance of the target account before mocking.
let balance_before = iweth.balanceOf(account).call().await?._0;
println!("WETH balance before: {}", balance_before);
assert_eq!(balance_before, U256::ZERO);
// Mock WETH balance using the Anvil API.
let hashed_slot = keccak256((account, U256::from(3)).abi_encode());
let mocked_balance: U256 = parse_units("1.0", "ether")?.into();
provider.anvil_set_storage_at(WETH_ADDR, hashed_slot.into(), mocked_balance.into()).await?;
// Get the WETH balance of the target account after mocking.
let balance_after = iweth.balanceOf(account).call().await?._0;
println!("WETH balance after: {}", balance_after);
assert_eq!(balance_after, mocked_balance);
Ok(())
}
Find the source code on Github here.
Primitives
Example: bytes_and_address_types
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example bytes_and_address_types
//! Example of basic usage of bytes and address types and macros.
use alloy::primitives::{
address, b128, b256, b512, b64, bytes, fixed_bytes, Address, Bytes, FixedBytes,
};
use eyre::Result;
fn main() -> Result<()> {
// Bytes type
let a = bytes!("0123abcd");
assert_eq!(a, Bytes::from(&[0x01, 0x23, 0xab, 0xcd]));
assert_eq!(a.len(), 4);
// Address type
let b = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
assert_eq!(
b,
Address::from(&[
0xf3, 0x9f, 0xd6, 0xe5, 0x1a, 0xad, 0x88, 0xf6, 0xf4, 0xce, 0x6a, 0xb8, 0x82, 0x72,
0x79, 0xcf, 0xff, 0xb9, 0x22, 0x66
])
);
assert_eq!(b.len(), 20);
// FixedBytes<8> type
let c = b64!("0102030405060708");
assert_eq!(c, FixedBytes::from(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]));
assert_eq!(c.len(), 8);
// FixedBytes<16> type
let d = b128!("0102030405060708090a0b0c0d0e0f10");
assert_eq!(
d,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
])
);
assert_eq!(d.len(), 16);
// FixedBytes<32> type
let e = b256!("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20");
assert_eq!(
e,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20,
]),
);
assert_eq!(e.len(), 32);
// FixedBytes<64> type
let f = b512!("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40");
assert_eq!(
f,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
]),
);
assert_eq!(f.len(), 64);
// FixedBytes<20> type, determined by the length of the input
let g = fixed_bytes!("0102030405060708090a0b0c0d0e0f1011121314");
assert_eq!(
g,
FixedBytes::from(&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
]),
);
assert_eq!(g.len(), 20);
Ok(())
}
Find the source code on Github here.
Example: hashing_functions
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example hashing_functions
//! Example of basic usage of hashing functions.
use alloy::primitives::{eip191_hash_message, keccak256};
use eyre::{Ok, Result};
fn main() -> Result<()> {
// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3
let hash = keccak256(b"hello world");
assert_eq!(
hash.to_string(),
"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"
);
assert_eq!(hash.len(), 32);
// Hash a message according to [EIP-191] (version `0x01`).
//
// The final message is a UTF-8 string, encoded as follows:
// `"\x19Ethereum Signed Message:\n" + message.length + message`
//
// This message is then hashed using [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3.
//
// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
let eip191_hash = eip191_hash_message(b"hello_world");
assert_eq!(
eip191_hash.to_string(),
"0xd52de6e039c023a7c77752126e4d9d99e2a7dacea3d19e97e9c2ebcb3ecf1c00"
);
assert_eq!(eip191_hash.len(), 32);
Ok(())
}
Find the source code on Github here.
Providers
Example: builder
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example builder
//! Example of using the `ProviderBuilder` to create a provider with a signer and network.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
node_bindings::Anvil,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::local::PrivateKeySigner,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).try_spawn()?;
// Set up signer from the first default Anvil account (Alice).
let signer: PrivateKeySigner = anvil.keys()[0].clone().into();
let wallet = EthereumWallet::from(signer.clone());
// Create two users, Alice and Bob.
let alice = signer.address();
let bob = anvil.addresses()[1];
// Set up the HTTP provider with the `reqwest` crate.
let rpc_url = anvil.endpoint_url();
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Create a transaction.
let tx = TransactionRequest::default().with_to(bob).with_value(U256::from(100));
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
Find the source code on Github here.
Example: builtin
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example builtin
//! Example of using the `on_builtin` method in the provider.
use alloy::{
node_bindings::Anvil,
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).try_spawn()?;
// Instantiate a HTTP transport provider by passing the HTTP endpoint url.
let http_rpc_url = anvil.endpoint();
let http_provider = ProviderBuilder::new().on_builtin(&http_rpc_url).await?;
// Get latest block number.
let block_number = http_provider.get_block_number().await?;
println!("Latest block number: {block_number:?}");
// This requires the `pubsub` and `ws` features to be enabled.
let ws_rpc_url = anvil.ws_endpoint();
let ws_provider = ProviderBuilder::new().on_builtin(&ws_rpc_url).await?;
let sub = ws_provider.subscribe_blocks().await?;
let mut stream = sub.into_stream().take(2);
println!("Awaiting block headers...");
let handle = tokio::spawn(async move {
while let Some(header) = stream.next().await {
println!("{}", header.number);
}
});
handle.await?;
// This requires the `pubsub` and `ipc` features to be enabled.
// This would throw a runtime error if the ipc does not exist.
let ipc_path = "/tmp/reth.ipc";
let ipc_provider = ProviderBuilder::new().on_builtin(ipc_path).await?;
let _block_number = ipc_provider.get_block_number().await?;
Ok(())
}
Find the source code on Github here.
Example: http
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example http
//! Example of using the HTTP provider with the `reqwest` crate to get the latest block number.
use alloy::providers::{Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider with the HTTP transport using the `reqwest` crate.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get latest block number.
let latest_block = provider.get_block_number().await?;
println!("Latest block number: {latest_block}");
Ok(())
}
Find the source code on Github here.
Example: http_with_auth
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example http_with_auth
//! Example of using the reqwest HTTP client with an `Authorization` header to get the latest block
//! number.
use alloy::{
providers::{Provider, ProviderBuilder},
rpc::client::RpcClient,
transports::http::{
reqwest::{
header::{HeaderMap, HeaderValue, AUTHORIZATION},
Client,
},
Http,
},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Set the Authorization header.
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_static("deadbeef"));
// Create the reqwest::Client with the AUTHORIZATION header.
let client_with_auth = Client::builder().default_headers(headers).build()?;
// Create the HTTP transport.
let rpc_url = "https://eth.merkle.io".parse()?;
let http = Http::with_client(client_with_auth, rpc_url);
let rpc_client = RpcClient::new(http, false);
// Create a provider with the HTTP transport.
let provider = ProviderBuilder::new().on_client(rpc_client);
// Get latest block number.
let latest_block = provider.get_block_number().await?;
println!("Latest block number: {latest_block}");
Ok(())
}
Find the source code on Github here.
Example: ws
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ws
//! Example of using the WS provider to subscribe to new blocks.
use alloy::providers::{Provider, ProviderBuilder, WsConnect};
use eyre::Result;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Create the provider.
let rpc_url = "wss://eth-mainnet.g.alchemy.com/v2/your-api-key";
let ws = WsConnect::new(rpc_url);
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Subscribe to new blocks.
let sub = provider.subscribe_blocks().await?;
// Wait and take the next 4 blocks.
let mut stream = sub.into_stream().take(4);
println!("Awaiting block headers...");
// Take the stream and print the block number upon receiving a new block.
let handle = tokio::spawn(async move {
while let Some(header) = stream.next().await {
println!("Latest block number: {}", header.number);
}
});
handle.await?;
Ok(())
}
Find the source code on Github here.
Example: ws_with_auth
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ws_with_auth
//! Example of using the WS provider with auth to subscribe to new blocks.
use alloy::{
providers::{Provider, ProviderBuilder, WsConnect},
transports::Authorization,
};
use eyre::Result;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Create authorization methods.
let auth = Authorization::basic("username", "password");
let auth_bearer = Authorization::bearer("bearer-token");
// Create the WS connection object with authentication.
let rpc_url = "wss://your-ws-endpoint.com/";
let ws_basic = WsConnect::new(rpc_url).with_auth(auth);
let ws_bearer = WsConnect::new(rpc_url).with_auth(auth_bearer);
// Create the provider.
let provider_basic = ProviderBuilder::new().on_ws(ws_basic).await?;
let provider_bearer = ProviderBuilder::new().on_ws(ws_bearer).await?;
// Subscribe to new block headers.
let sub_basic = provider_basic.subscribe_blocks();
let sub_bearer = provider_bearer.subscribe_blocks();
// Wait and take the next 4 block headers
let mut stream_basic = sub_basic.await?.into_stream().take(4);
let mut stream_bearer = sub_bearer.await?.into_stream().take(4);
println!("Awaiting block headers...");
// Take the basic stream and print the block number upon receiving a new block header.
let basic_handle = tokio::spawn(async move {
while let Some(header) = stream_basic.next().await {
println!("Latest block number (basic): {}", header.number);
}
});
// Take the bearer stream and print the block number upon receiving a new block header.
let bearer_handle = tokio::spawn(async move {
while let Some(header) = stream_bearer.next().await {
println!("Latest block number (bearer): {}", header.number);
}
});
// Wait for both tasks to complete.
let _ = tokio::try_join!(basic_handle, bearer_handle)?;
Ok(())
}
Find the source code on Github here.
Example: ipc
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ipc
//! Example of using the IPC provider to get the latest block number.
use alloy::providers::{IpcConnect, Provider, ProviderBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Set up the IPC transport which is consumed by the RPC client.
let ipc_path = "/tmp/reth.ipc";
// Create the provider.
let ipc = IpcConnect::new(ipc_path.to_string());
let provider = ProviderBuilder::new().on_ipc(ipc).await?;
let latest_block = provider.get_block_number().await?;
println!("Latest block: {latest_block}");
Ok(())
}
Find the source code on Github here.
Queries
Example: query_contract_storage
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example query_contract_storage
//! Example of querying contract storage from the Ethereum network.
use alloy::{
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get storage slot 0 from the Uniswap V3 USDC-ETH pool on Ethereum mainnet.
let pool_address = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640");
let storage_slot = U256::from(0);
// The provider calls the RPC at the latest block by default. A block can exlpicitly be set
// using `.block()`.
let storage = provider.get_storage_at(pool_address, storage_slot).await?;
println!("Slot 0: {storage:?}");
Ok(())
}
Find the source code on Github here.
Example: query_deployed_bytecode
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example query_deployed_bytecode
//! Example of querying deployed bytecode of a contract on the Ethereum network.
use alloy::{
primitives::address,
providers::{Provider, ProviderBuilder},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get the bytecode of the Uniswap V3 USDC-ETH pool on Ethereum mainnet.
let pool_address = address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640");
let bytecode = provider.get_code_at(pool_address).await?;
println!("Bytecode: {bytecode:?}");
Ok(())
}
Find the source code on Github here.
Example: query_logs
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example query_logs
//! Example of querying logs from the Ethereum network.
use alloy::{
primitives::{address, b256},
providers::{Provider, ProviderBuilder},
rpc::types::Filter,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Get logs from the latest block
let latest_block = provider.get_block_number().await?;
// Create a filter to get all logs from the latest block.
let filter = Filter::new().from_block(latest_block);
// Get all logs from the latest block that match the filter.
let logs = provider.get_logs(&filter).await?;
for log in logs {
println!("{log:?}");
}
// Get all logs from the latest block that match the transfer event signature/topic.
let transfer_event_signature =
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
let filter = Filter::new().event_signature(transfer_event_signature).from_block(latest_block);
// You could also use the event name instead of the event signature like so:
// .event("Transfer(address,address,uint256)")
// Get all logs from the latest block that match the filter.
let logs = provider.get_logs(&filter).await?;
for log in logs {
println!("Transfer event: {log:?}");
}
// Get all logs from the latest block emitted by the UNI token address.
let uniswap_token_address = address!("1f9840a85d5aF5bf1D1762F925BDADdC4201F984");
let filter = Filter::new().address(uniswap_token_address).from_block(latest_block);
// Get all logs from the latest block that match the filter.
let logs = provider.get_logs(&filter).await?;
for log in logs {
println!("Uniswap token logs: {log:?}");
}
Ok(())
}
Find the source code on Github here.
The sol!
macro
Example: deploy_from_contract
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example deploy_from_contract
//! Example of deploying a contract from Solidity code using the `sol!` macro to Anvil and
//! interacting with it.
use alloy::{primitives::U256, providers::ProviderBuilder, sol};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
sol! {
#[allow(missing_docs)]
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
#[sol(rpc, bytecode="6080806040523460135760df908160198239f35b600080fdfe6080806040526004361015601257600080fd5b60003560e01c9081633fb5c1cb1460925781638381f58a146079575063d09de08a14603c57600080fd5b3460745760003660031901126074576000546000198114605e57600101600055005b634e487b7160e01b600052601160045260246000fd5b600080fd5b3460745760003660031901126074576020906000548152f35b34607457602036600319011260745760043560005500fea2646970667358221220e978270883b7baed10810c4079c941512e93a7ba1cd1108c781d4bc738d9090564736f6c634300081a0033")]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Deploy the `Counter` contract.
let contract = Counter::deploy(&provider).await?;
println!("Deployed contract at address: {}", contract.address());
let builder = contract.setNumber(U256::from(42));
let tx_hash = builder.send().await?.watch().await?;
println!("Set number to 42: {tx_hash}");
// Increment the number to 43.
let builder = contract.increment();
let tx_hash = builder.send().await?.watch().await?;
println!("Incremented number: {tx_hash}");
// Retrieve the number, which should be 43.
let builder = contract.number();
let number = builder.call().await?.number.to_string();
println!("Retrieved number: {number}");
Ok(())
}
Find the source code on Github here.
Example: decode_returns
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example decode_returns
//! Example showing how to decode return values from a call to a contract using the `sol!` macro.
use alloy::{
hex,
primitives::{Uint, I256, U256},
sol,
sol_types::SolCall,
};
use eyre::Result;
// Codegen from excerpt of Chainlink Aggregator interface.
// See: https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419#code
sol!(
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Eq)]
function getRoundData(uint80 _roundId) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
);
fn main() -> Result<()> {
let result = getRoundDataCall::abi_decode_returns(
&hex!(
"0000000000000000000000000000000000000000000000060000000000004716
00000000000000000000000000000000000000000000000000000051faad1c80
000000000000000000000000000000000000000000000000000000006669627b
000000000000000000000000000000000000000000000000000000006669627b
0000000000000000000000000000000000000000000000060000000000004716"
),
true,
);
assert_eq!(
result,
Ok(getRoundDataReturn {
roundId: Uint::<80, 2>::from(110680464442257327894_u128),
answer: I256::from_dec_str("352098000000")?,
startedAt: U256::from(1718182523),
updatedAt: U256::from(1718182523),
answeredInRound: Uint::<80, 2>::from(110680464442257327894_u128),
})
);
Ok(())
}
Find the source code on Github here.
Example: events_errors
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example events_errors
//! Example showing how to decode events and errors from a contract using the `sol!` macro.
use alloy::{providers::ProviderBuilder, sol};
use eyre::Result;
use futures_util::StreamExt;
// Generate a contract instance from Solidity.
sol!(
#[allow(missing_docs)]
#[sol(rpc, bytecode = "608060405260008055348015601357600080fd5b506103e9806100236000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632baeceb71461005c5780632ccbdbca1461006657806361bc221a14610070578063c3e8b5ca1461008e578063d09de08a14610098575b600080fd5b6100646100a2565b005b61006e610103565b005b61007861013e565b60405161008591906101f9565b60405180910390f35b610096610144565b005b6100a061017f565b005b60016000808282546100b49190610243565b925050819055506000543373ffffffffffffffffffffffffffffffffffffffff167fdc69c403b972fc566a14058b3b18e1513da476de6ac475716e489fae0cbe4a2660405160405180910390a3565b6040517f23b0db14000000000000000000000000000000000000000000000000000000008152600401610135906102e3565b60405180910390fd5b60005481565b6040517fa5f9ec670000000000000000000000000000000000000000000000000000000081526004016101769061034f565b60405180910390fd5b6001600080828254610191919061036f565b925050819055506000543373ffffffffffffffffffffffffffffffffffffffff167ff6d1d8d205b41f9fb9549900a8dba5d669d68117a3a2b88c1ebc61163e8117ba60405160405180910390a3565b6000819050919050565b6101f3816101e0565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e826101e0565b9150610259836101e0565b92508282039050818112600084121682821360008512151617156102805761027f610214565b5b92915050565b600082825260208201905092915050565b7f4572726f72204100000000000000000000000000000000000000000000000000600082015250565b60006102cd600783610286565b91506102d882610297565b602082019050919050565b600060208201905081810360008301526102fc816102c0565b9050919050565b7f4572726f72204200000000000000000000000000000000000000000000000000600082015250565b6000610339600783610286565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600061037a826101e0565b9150610385836101e0565b9250828201905082811215600083121683821260008412151617156103ad576103ac610214565b5b9291505056fea2646970667358221220a878a3c1da1a1170e4496cdbc63bd5ed1587374bcd6cf6d4f1d5b88fa981795d64736f6c63430008190033")]
contract CounterWithError {
int256 public counter = 0;
// Events - using `Debug` to print the events
#[derive(Debug)]
event Increment(address indexed by, int256 indexed value);
#[derive(Debug)]
event Decrement(address indexed by, int256 indexed value);
// Custom Error
error ErrorA(string message);
error ErrorB(string message);
// Functions
function increment() public {
counter += 1;
emit Increment(msg.sender, counter);
}
function decrement() public {
counter -= 1;
emit Decrement(msg.sender, counter);
}
function revertA() public pure {
revert ErrorA("Error A");
}
function revertB() public pure {
revert ErrorB("Error B");
}
}
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_gas_estimation().on_anvil();
// Deploy the `Counter` contract.
let contract = CounterWithError::deploy(provider.clone()).await?;
// Setup a filter for the Increment and Decrement events.
let increment_filter = contract.Increment_filter().watch().await?;
let decrement_filter = contract.Decrement_filter().watch().await?;
// Convert to streams.
let mut increment_stream = increment_filter.into_stream();
let mut decrement_stream = decrement_filter.into_stream();
// Call the increment and decrement functions.
let increment_call = contract.increment();
let decrement_call = contract.decrement();
// Wait for the calls to be included.
let _increment_res = increment_call.send().await?;
let _decrement_res = decrement_call.send().await?;
// Catch the events.
for _ in 0..2 {
let log = tokio::select! {
Some(Ok((incr, log))) = increment_stream.next() => {
println!("Increment: {incr:#?}");
// Return raw log
log
}
Some(Ok((decr, log))) = decrement_stream.next() => {
println!("Decrement: {decr:#?}");
// Return raw log
log
}
};
println!("Log: {log:#?}");
}
// Call the `revertA` function.
let err_call = contract.revertA();
let err_result = err_call.send().await;
if let Err(err) = err_result {
println!("Error A: {err:#?}");
}
// Call the `revertB` function.
let err_call = contract.revertB();
let err_result = err_call.send().await;
if let Err(err) = err_result {
println!("Error B: {err:#?}");
}
Ok(())
}
Find the source code on Github here.
Example: structs_enums
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example structs_enums
//! Example showing how to use the `sol!` macro to generate Rust bindings for Solidity structs and
//! enums.
use alloy::{primitives::U256, sol};
use eyre::Result;
// Generates Rust bindings for Solidity structs, enums and type aliases.
sol! {
#[allow(missing_docs)]
#[derive(Debug)]
/// Foo
struct Foo {
uint256 a;
uint64 b;
Bar greater;
}
#[allow(missing_docs)]
#[derive(Debug)]
/// Bar
enum Bar {
A,
B,
}
}
fn main() -> Result<()> {
// Create an instance of the struct.
let foo = Foo { a: U256::from(1), b: 2_u64, greater: Bar::A };
println!("{foo:?}");
Ok(())
}
Find the source code on Github here.
Example: user_defined_types
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example user_defined_types
//! Example showing defining user defined value types and type aliases using the `sol!` macro.
use alloy::{
primitives::{Address, U256},
sol,
sol_types::SolType,
};
use eyre::Result;
// Type definition: generates a new struct that implements `SolType`
sol! {
/// Equivalent to `struct CustomType(U256)` in Rust
type CustomType is uint256;
}
// Type aliases
type Bytes32 = sol! { bytes32 };
// This is equivalent to the following:
// type B32 = alloy_sol_types::sol_data::FixedBytes<32>;
// User defined types
type CustomArrayOf<T> = sol! { T[] };
type CustomTuple = sol! { tuple(address, bytes, string) };
fn main() -> Result<()> {
let _b32_type = Bytes32::abi_encode(&[0; 32]);
let _custom_type = CustomType(U256::from(1));
let _custom_array_of_type = CustomArrayOf::<sol!(bool)>::abi_encode(&vec![true, false]);
let _custom_tuple_type =
CustomTuple::abi_encode(&(Address::ZERO, vec![0; 32], "hello".to_string()));
Ok(())
}
Find the source code on Github here.
Subscriptions
- Watch and poll for contract event logs
- Subscribe and watch blocks
- Subscribe and listen for specific contract event logs
- Subscribe and listen for all contract event logs
- Subscribe and listen to pending transactions in the public mempool
Example: poll_logs
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example poll_logs
//! Example of watching and polling for contract events by `WebSocket` subscription.
use alloy::{
node_bindings::Anvil,
providers::{ProviderBuilder, WsConnect},
sol,
};
use eyre::Result;
use futures_util::StreamExt;
// Codegen from embedded Solidity code and precompiled bytecode.
// solc v0.8.26; solc Counter.sol --via-ir --optimize --bin
sol!(
#[allow(missing_docs)]
#[sol(rpc, bytecode = "6080806040523460195760008055610155908161001f8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c80632baeceb7146100d057806361bc221a146100b25763d09de08a1461003d57600080fd5b346100ad5760003660031901126100ad57600054600181019060006001831291129080158216911516176100975780600055337ff6d1d8d205b41f9fb9549900a8dba5d669d68117a3a2b88c1ebc61163e8117ba600080a3005b634e487b7160e01b600052601160045260246000fd5b600080fd5b346100ad5760003660031901126100ad576020600054604051908152f35b346100ad5760003660031901126100ad5760005460001981019081136001166100975780600055337fdc69c403b972fc566a14058b3b18e1513da476de6ac475716e489fae0cbe4a26600080a300fea26469706673582212200d333e08e1230b0b9919825888e587a45c68e2aa2f7f58752712491e2201da9c64736f6c634300081a0033")]
contract Counter {
int256 public counter = 0;
event Increment(address indexed by, int256 indexed value);
event Decrement(address indexed by, int256 indexed value);
function increment() public {
counter += 1;
emit Increment(msg.sender, counter);
}
function decrement() public {
counter -= 1;
emit Decrement(msg.sender, counter);
}
}
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).try_spawn()?;
// Create a WebSocket provider.
let ws = WsConnect::new(anvil.ws_endpoint());
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Deploy the `Counter` contract.
let contract = Counter::deploy(provider.clone()).await?;
println!("Deployed contract at: {}", contract.address());
// Create filters for each event.
let increment_filter = contract.Increment_filter().watch().await?;
let decrement_filter = contract.Decrement_filter().watch().await?;
// Build a call to increment the counter.
let increment_call = contract.increment();
// Build a call to decrement the counter.
let decrement_call = contract.decrement();
// Send the transaction call twice for each event.
for _ in 0..2 {
let _ = increment_call.send().await?;
let _ = decrement_call.send().await?;
}
// Poll for logs.
increment_filter
.into_stream()
.take(2)
.for_each(|log| async {
match log {
Ok((_event, log)) => {
println!("Received Increment: {log:?}");
}
Err(e) => {
println!("Error: {e:?}");
}
}
})
.await;
decrement_filter
.into_stream()
.take(2)
.for_each(|log| async {
match log {
Ok((_event, log)) => {
println!("Received Decrement: {log:?}");
}
Err(e) => {
println!("Error: {e:?}");
}
}
})
.await;
Ok(())
}
Find the source code on Github here.
Example: subscribe_blocks
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example subscribe_blocks
//! Example of subscribing to blocks and watching block headers by polling.
use alloy::{
node_bindings::Anvil,
providers::{Provider, ProviderBuilder, WsConnect},
};
use eyre::Result;
use futures_util::{stream, StreamExt};
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).try_spawn()?;
// Create a provider.
let ws = WsConnect::new(anvil.ws_endpoint());
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Subscribe to block headers.
let subscription = provider.subscribe_blocks().await?;
let mut stream = subscription.into_stream().take(2);
while let Some(header) = stream.next().await {
println!("Received block number: {}", header.number);
}
// Poll for block headers.
let poller = provider.watch_blocks().await?;
let mut stream = poller.into_stream().flat_map(stream::iter).take(2);
while let Some(block_hash) = stream.next().await {
println!("Polled for block header: {block_hash:?}");
}
Ok(())
}
Find the source code on Github here.
Example: subscribe_logs
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example subscribe_logs
//! Example of subscribing and listening for specific contract events by `WebSocket` subscription.
use alloy::{
primitives::address,
providers::{Provider, ProviderBuilder, WsConnect},
rpc::types::{BlockNumberOrTag, Filter},
};
use eyre::Result;
use futures_util::stream::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Create the provider.
let rpc_url = "wss://eth-mainnet.g.alchemy.com/v2/your-api-key";
let ws = WsConnect::new(rpc_url);
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Create a filter to watch for UNI token transfers.
let uniswap_token_address = address!("1f9840a85d5aF5bf1D1762F925BDADdC4201F984");
let filter = Filter::new()
.address(uniswap_token_address)
// By specifying an `event` or `event_signature` we listen for a specific event of the
// contract. In this case the `Transfer(address,address,uint256)` event.
.event("Transfer(address,address,uint256)")
.from_block(BlockNumberOrTag::Latest);
// Subscribe to logs.
let sub = provider.subscribe_logs(&filter).await?;
let mut stream = sub.into_stream();
while let Some(log) = stream.next().await {
println!("Uniswap token logs: {log:?}");
}
Ok(())
}
Find the source code on Github here.
Example: subscribe_all_logs
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example subscribe_all_logs
//! Example of subscribing and listening for all contract events by `WebSocket` subscription.
use alloy::{
primitives::address,
providers::{Provider, ProviderBuilder, WsConnect},
rpc::types::{BlockNumberOrTag, Filter},
sol,
sol_types::SolEvent,
};
use eyre::Result;
use futures_util::stream::StreamExt;
// Codegen from ABI file to interact with the contract.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
IWETH9,
"examples/abi/IWETH9.json"
);
#[tokio::main]
async fn main() -> Result<()> {
// Create the provider.
let rpc_url = "wss://eth-mainnet.g.alchemy.com/v2/your-api-key";
let ws = WsConnect::new(rpc_url);
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Create a filter to watch for all WETH9 events.
let weth9_token_address = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let filter = Filter::new()
// By NOT specifying an `event` or `event_signature` we listen to ALL events of the
// contract.
.address(weth9_token_address)
.from_block(BlockNumberOrTag::Latest);
// Subscribe to logs.
let sub = provider.subscribe_logs(&filter).await?;
let mut stream = sub.into_stream();
while let Some(log) = stream.next().await {
// Match on topic 0, the hash of the signature of the event.
match log.topic0() {
// Match the `Approval(address,address,uint256)` event.
Some(&IWETH9::Approval::SIGNATURE_HASH) => {
let IWETH9::Approval { src, guy, wad } = log.log_decode()?.inner.data;
println!("Approval from {src} to {guy} of value {wad}");
}
// Match the `Transfer(address,address,uint256)` event.
Some(&IWETH9::Transfer::SIGNATURE_HASH) => {
let IWETH9::Transfer { src, dst, wad } = log.log_decode()?.inner.data;
println!("Transfer from {src} to {dst} of value {wad}");
}
// WETH9's `Deposit(address,uint256)` and `Withdrawal(address,uint256)` events are not
// handled here.
_ => (),
}
}
Ok(())
}
Find the source code on Github here.
Example: subscribe_pending_transactions
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example subscribe_pending_transactions
//! Example of subscribing and listening for pending transactions in the public mempool by
//! `WebSocket` subscription.
use alloy::providers::{Provider, ProviderBuilder, WsConnect};
use eyre::Result;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<()> {
// Create the provider.
let rpc_url = "wss://eth-mainnet.g.alchemy.com/v2/your-api-key";
let ws = WsConnect::new(rpc_url);
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Subscribe to pending transactions.
// Alteratively use `subscribe_full_pending_transactions` to get the full transaction details
// directly if supported by the RPC provider.
let sub = provider.subscribe_pending_transactions().await?;
// Wait and take the next 3 transactions.
let mut stream = sub.into_stream().take(3);
println!("Awaiting pending transactions...");
// Take the stream and print the pending transaction.
let handle = tokio::spawn(async move {
while let Some(tx_hash) = stream.next().await {
// Get the transaction details.
if let Ok(tx) = provider.get_transaction_by_hash(tx_hash).await {
println!("Transaction details: {tx:#?}");
}
}
});
handle.await?;
Ok(())
}
Find the source code on Github here.
Example: event_multiplexer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example event_multiplexer
//! Example of multiplexing the watching of event logs.
use alloy::{
node_bindings::Anvil,
primitives::I256,
providers::{ProviderBuilder, WsConnect},
sol,
sol_types::SolEvent,
};
use eyre::Result;
use futures_util::StreamExt;
use std::str::FromStr;
// Codegen from embedded Solidity code and precompiled bytecode.
// solc v0.8.26; solc EventMultiplexer.sol --via-ir --optimize --bin
sol!(
#[allow(missing_docs)]
#[sol(rpc, bytecode = "60808060405234601557610207908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c80634350913814610156578063a5f3c23b14610108578063adefc37b146100ba5763bbe93d911461004857600080fd5b346100b557610056366101bb565b818102919060008212600160ff1b82141661009f57818305149015171561009f57337fd7a123d4c8e44db3186e04b9c96c102287276929c930f2e8abcaa555ef5dcacc600080a3005b634e487b7160e01b600052601160045260246000fd5b600080fd5b346100b5576100c8366101bb565b906000828203921281831281169183139015161761009f57337f32e913bf2ad35da1e845597618bb9f3f80642a68dd39f30a093a7838aa61fb27600080a3005b346100b557610116366101bb565b906000828201928312911290801582169115161761009f57337f6da406ea462447ed7804b4a4dc69c67b53d3d45a50381ae3e9cf878c9d7c23df600080a3005b346100b557610164366101bb565b9081156101a557600160ff1b811460001983141661009f5705337f1c1e8bbe327890ea8d3f5b22370a56c3fcef7ff82f306161f64647fe5d285881600080a3005b634e487b7160e01b600052601260045260246000fd5b60409060031901126100b557600435906024359056fea2646970667358221220d876fbacf1e90fc174532f3525420c446351b467f788f9d7a726a7d55045909664736f6c634300081a0033")]
contract EventMultiplexer {
event Add(address indexed sender, int256 indexed value);
event Sub(address indexed sender, int256 indexed value);
event Mul(address indexed sender, int256 indexed value);
event Div(address indexed sender, int256 indexed value);
function add(int256 a, int256 b) public {
emit Add(msg.sender, a + b);
}
function sub(int256 a, int256 b) public {
emit Sub(msg.sender, a - b);
}
function mul(int256 a, int256 b) public {
emit Mul(msg.sender, a * b);
}
function div(int256 a, int256 b) public {
emit Div(msg.sender, a / b);
}
}
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).try_spawn()?;
// Create a provider.
let ws = WsConnect::new(anvil.ws_endpoint());
let provider = ProviderBuilder::new().on_ws(ws).await?;
// Deploy the `EventExample` contract.
let contract = EventMultiplexer::deploy(provider).await?;
println!("Deployed contract at: {}", contract.address());
// Create filters for each event.
let add_filter = contract.Add_filter().watch().await?;
let sub_filter = contract.Sub_filter().watch().await?;
let mul_filter = contract.Mul_filter().watch().await?;
let div_filter = contract.Div_filter().watch().await?;
let a = I256::from_str("1")?;
let b = I256::from_str("1")?;
// Build the transaction calls.
let add_call = contract.add(a, b);
let sub_call = contract.sub(a, b);
let mul_call = contract.mul(a, b);
let div_call = contract.div(a, b);
// Send the transaction calls.
let _ = add_call.send().await?;
let _ = sub_call.send().await?;
let _ = mul_call.send().await?;
let _ = div_call.send().await?;
// Convert the filters into streams.
let mut add_stream = add_filter.into_stream();
let mut sub_stream = sub_filter.into_stream();
let mut mul_stream = mul_filter.into_stream();
let mut div_stream = div_filter.into_stream();
let add_log = &EventMultiplexer::Add::SIGNATURE_HASH;
let sub_log = &EventMultiplexer::Sub::SIGNATURE_HASH;
let mul_log = &EventMultiplexer::Mul::SIGNATURE_HASH;
let div_log = &EventMultiplexer::Div::SIGNATURE_HASH;
// Use tokio::select! to multiplex the streams and capture the log
// tokio::select! will return the first event that arrives from any of the streams
// The for loop helps capture all the logs.
for _ in 0..4 {
let log = tokio::select! {
Some(log) = add_stream.next() => {
log?.1
}
Some(log) = sub_stream.next() => {
log?.1
}
Some(log) = mul_stream.next() => {
log?.1
}
Some(log) = div_stream.next() => {
log?.1
}
};
let topic = &log.topics()[0];
if topic == add_log {
println!("Received Add: {log:?}");
} else if topic == sub_log {
println!("Received Sub: {log:?}");
} else if topic == mul_log {
println!("Received Mul: {log:?}");
} else if topic == div_log {
println!("Received Div: {log:?}");
}
}
Ok(())
}
Find the source code on Github here.
Transactions
- Decode input
- Encode and decode EIP-1559 transaction
- Get gas price in USD
- Send EIP-1559 transaction
- Send EIP-4844 transaction
- Send EIP-7702 transaction
- Send legacy transaction
- Send private transaction using Flashbots Protect
- Sign and send a raw transaction
- Trace call
- Trace transaction
- Transfer ERC20 token
- Transfer ETH
- Send transaction with access list
Example: decode_input
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example decode_input
//! Example of how to decode the input of a transaction.
use alloy::{primitives::hex, sol, sol_types::SolCall};
use eyre::Result;
// Codegen from excerpt of Uniswap V2 Router interface.
// See: https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02
sol!(
#[allow(missing_docs)]
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
);
#[tokio::main]
async fn main() -> Result<()> {
println!("Decoding https://etherscan.io/tx/0xd1b449d8b1552156957309bffb988924569de34fbf21b51e7af31070cc80fe9a");
let input = "0x38ed173900000000000000000000000000000000000000000001a717cc0a3e4f84c00000000000000000000000000000000000000000000000000000000000000283568400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000201f129111c60401630932d9f9811bd5b5fff34e000000000000000000000000000000000000000000000000000000006227723d000000000000000000000000000000000000000000000000000000000000000200000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7";
let input = hex::decode(input)?;
// Decode the input using the generated `swapExactTokensForTokens` bindings.
let decoded = swapExactTokensForTokensCall::abi_decode(&input, false);
match decoded {
Ok(decoded) => {
let path = decoded.path;
println!(
"Swap {} of token {} to {} of token {}",
decoded.amountIn,
path.first().expect("Path is empty"),
decoded.amountOutMin,
path.last().expect("Path is empty")
);
}
Err(e) => {
println!("Error decoding input: {e:?}");
}
}
Ok(())
}
Find the source code on Github here.
Example: encode_decode_eip1559
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example encode_decode_eip1559
//! Example showing how to encode and decode an [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) transaction.
use alloy::{
consensus::{SignableTransaction, TxEip1559},
eips::eip2930::AccessList,
primitives::{address, b256, hex, PrimitiveSignature as Signature, TxKind, U256},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// EIP1559 transaction: <https://etherscan.io/tx/0x0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0>
let tx_hash = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0");
// Signer of the transaction.
let signer = address!("DD6B8b3dC6B7AD97db52F08a275FF4483e024CEa");
// Construct the EIP-1559 transaction.
let tx = TxEip1559 {
chain_id: 1,
nonce: 0x42,
gas_limit: 44386,
to: TxKind::Call( address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6")),
value: U256::from(0_u64),
input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(),
max_fee_per_gas: 0x4a817c800,
max_priority_fee_per_gas: 0x3b9aca00,
access_list: AccessList::default(),
};
// Construct the signature of the transaction.
let signature = Signature::from_scalars_and_parity(
b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
false,
);
// Convert the transaction into a signed transaction.
let signed_tx = tx.into_signed(signature);
assert_eq!(*signed_tx.hash(), tx_hash);
// Recover the signer from the signed transaction to ensure it matches the expected signer.
let recovered_signer = signed_tx.recover_signer()?;
assert_eq!(recovered_signer, signer);
Ok(())
}
Find the source code on Github here.
Example: gas_price_usd
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example gas_price_usd
//! Example of how to get the gas price in USD using the Chainlink ETH/USD feed.
use alloy::{
network::TransactionBuilder,
primitives::{address, utils::format_units, Address, Bytes, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
sol,
sol_types::SolCall,
};
use eyre::Result;
use std::str::FromStr;
const ETH_USD_FEED: Address = address!("5f4eC3Df9cbd43714FE2740f5E3616155c5b8419");
const ETH_USD_FEED_DECIMALS: u8 = 8;
const ETH_DECIMALS: u32 = 18;
// Codegen from excerpt of Chainlink Aggregator interface.
// See: https://etherscan.io/address/0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419#code
sol!(
#[allow(missing_docs)]
function latestAnswer() external view returns (int256);
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let provider = ProviderBuilder::new().on_anvil_with_config(|anvil| anvil.fork(rpc_url));
// Create a call to get the latest answer from the Chainlink ETH/USD feed.
let call = latestAnswerCall {}.abi_encode();
let input = Bytes::from(call);
// Call the Chainlink ETH/USD feed contract.
let tx = TransactionRequest::default().with_to(ETH_USD_FEED).with_input(input);
let response = provider.call(&tx).await?;
let result = U256::from_str(&response.to_string())?;
// Get the gas price of the network.
let wei_per_gas = provider.get_gas_price().await?;
// Convert the gas price to Gwei and USD.
let gwei = format_units(wei_per_gas, "gwei")?.parse::<f64>()?;
let usd = get_usd_value(wei_per_gas, result)?;
println!("Gas price in Gwei: {gwei}");
println!("Gas price in USD: {usd}");
Ok(())
}
fn get_usd_value(amount: u128, price_usd: U256) -> Result<f64> {
let base = U256::from(10).pow(U256::from(ETH_DECIMALS));
let value = U256::from(amount) * price_usd / base;
let formatted = format_units(value, ETH_USD_FEED_DECIMALS)?.parse::<f64>()?;
Ok(formatted)
}
Find the source code on Github here.
Example: send_raw_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_raw_transaction
//! Example of signing, encoding and sending a raw transaction using a wallet.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder, WalletProvider},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil_with_wallet();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_chain_id(provider.get_chain_id().await?)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000);
// Build and sign the transaction using the `EthereumWallet` with the provided wallet.
let tx_envelope = tx.build(&provider.wallet()).await?;
// Send the raw transaction and retrieve the transaction receipt.
// [Provider::send_tx_envelope] is a convenience method that encodes the transaction using
// EIP-2718 encoding and broadcasts it to the network using [Provider::send_raw_transaction].
let receipt = provider.send_tx_envelope(tx_envelope).await?.get_receipt().await?;
println!("Sent transaction: {}", receipt.transaction_hash);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
Find the source code on Github here.
Example: with_access_list
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example with_access_list
//! Example of sending a EIP-1559 transaction with access list.
use alloy::{
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
sol,
};
use eyre::Result;
// Codegen from artifact.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
SimpleStorage,
"examples/artifacts/SimpleStorage.json"
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Deploy the `SimpleStorage` contract.
let contract_address = SimpleStorage::deploy_builder(provider.clone(), "initial".to_string())
.from(alice)
.deploy()
.await?;
let contract = SimpleStorage::new(contract_address, provider.clone());
// Build a transaction to set the values of the contract.
// The `from` field is automatically filled to the first signer's address (Alice).
let set_value_call = contract.setValues("hello".to_string(), "world".to_string());
let calldata = set_value_call.calldata().to_owned();
let tx = TransactionRequest::default().from(bob).to(contract_address).input(calldata.into());
// Create an access list for the transaction.
let access_list_with_gas_used = provider.create_access_list(&tx).await?;
// Add the access list to the transaction.
let tx_with_access_list = tx.access_list(access_list_with_gas_used.access_list);
// Send the transaction with the access list.
let tx_hash = provider.send_transaction(tx_with_access_list).await?.watch().await?;
println!("Transaction hash: {tx_hash}");
// Check the value of the contract.
let value = contract.getValue().call().await?._0;
assert_eq!(value, "hello");
Ok(())
}
Find the source code on Github here.
Example: send_eip1559_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_eip1559_transaction
//! Example showing how to send an [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) transaction.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_chain_id(provider.get_chain_id().await?)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
Find the source code on Github here.
Example: send_eip4844_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_eip4844_transaction
//! Example showing how to send an [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) transaction.
use alloy::{
consensus::{SidecarBuilder, SimpleCoder},
eips::eip4844::DATA_GAS_PER_BLOB,
network::{TransactionBuilder, TransactionBuilder4844},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node with the Cancun hardfork enabled.
// Ensure `anvil` is available in $PATH.
let provider =
ProviderBuilder::new().on_anvil_with_config(|anvil| anvil.args(["--hardfork", "cancun"]));
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Create a sidecar with some data.
let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(b"Blobs are fun!");
let sidecar = sidecar.build()?;
// Build a transaction to send the sidecar from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let gas_price = provider.get_gas_price().await?;
let eip1559_est = provider.estimate_eip1559_fees(None).await?;
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_max_fee_per_blob_gas(gas_price)
.with_max_fee_per_gas(eip1559_est.max_fee_per_gas)
.with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas)
.with_blob_sidecar(sidecar);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
assert_eq!(
receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"),
DATA_GAS_PER_BLOB as u128
);
Ok(())
}
Find the source code on Github here.
Example: send_eip7702_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_eip7702_transaction
//! Example showing how to send an [EIP-7702](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md) transaction.
use alloy::{
eips::eip7702::Authorization,
network::{EthereumWallet, TransactionBuilder, TransactionBuilder7702},
node_bindings::Anvil,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::{local::PrivateKeySigner, SignerSync},
sol,
};
use eyre::Result;
// Codegen from embedded Solidity code and precompiled bytecode.
// solc v0.8.25 Log.sol --via-ir --optimize --bin
sol!(
#[allow(missing_docs)]
#[sol(rpc, bytecode = "6080806040523460135760c9908160188239f35b5f80fdfe6004361015600b575f80fd5b5f3560e01c80637b3ab2d014605f57639ee1a440146027575f80fd5b34605b575f366003190112605b577f2d67bb91f17bca05af6764ab411e86f4ddf757adb89fcec59a7d21c525d417125f80a1005b5f80fd5b34605b575f366003190112605b577fbcdfe0d5b27dd186282e187525415c57ea3077c34efb39148111e4d342e7ab0e5f80a100fea2646970667358221220f6b42b522bc9fb2b4c7d7e611c7c3e995d057ecab7fd7be4179712804c886b4f64736f6c63430008190033")]
contract Log {
#[derive(Debug)]
event Hello();
event World();
function emitHello() public {
emit Hello();
}
function emitWorld() public {
emit World();
}
}
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node with the Prague hardfork enabled.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().arg("--hardfork").arg("prague").try_spawn()?;
// Create two users, Alice and Bob.
// Alice will sign the authorization and Bob will send the transaction.
let alice: PrivateKeySigner = anvil.keys()[0].clone().into();
let bob: PrivateKeySigner = anvil.keys()[1].clone().into();
// Create a provider with the wallet for only Bob (not Alice).
let rpc_url = anvil.endpoint_url();
let wallet = EthereumWallet::from(bob.clone());
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Deploy the contract Alice will authorize.
let contract = Log::deploy(&provider).await?;
// Create an authorization object for Alice to sign.
let authorization = Authorization {
chain_id: anvil.chain_id(),
// Reference to the contract that will be set as code for the authority.
address: *contract.address(),
nonce: provider.get_transaction_count(alice.address()).await?,
};
// Alice signs the authorization.
let signature = alice.sign_hash_sync(&authorization.signature_hash())?;
let signed_authorization = authorization.into_signed(signature);
// Collect the calldata required for the transaction.
let call = contract.emitHello();
let emit_hello_calldata = call.calldata().to_owned();
// Build the transaction.
let tx = TransactionRequest::default()
.with_to(alice.address())
.with_authorization_list(vec![signed_authorization])
.with_input(emit_hello_calldata);
// 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")
);
assert!(receipt.status());
assert_eq!(receipt.from, bob.address());
assert_eq!(receipt.to, Some(alice.address()));
assert_eq!(receipt.inner.logs().len(), 1);
assert_eq!(receipt.inner.logs()[0].address(), alice.address());
Ok(())
}
Find the source code on Github here.
Example: send_legacy_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_legacy_transaction
//! Example showing how to send a legacy transaction.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// // Spin up a local Anvil node.
// // Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_value(U256::from(100))
.with_gas_price(20_000_000_000)
.with_gas_limit(21_000);
// 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")
);
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));
Ok(())
}
Find the source code on Github here.
Example: send_private_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example send_private_transaction
//! Example of sending a private transaction using Flashbots Protect.
use alloy::{
network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder},
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::local::PrivateKeySigner,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Set up the HTTP transport which is consumed by the RPC client.
//
// By default, Flashbots Protect transactions are only shared with the Flashbots Builder, which
// builds only a subset of all Ethereum blocks. In `fast` mode, transactions are shared with
// all registered builders no less than one block after they are received to increase the
// number of blocks the user's transaction can be included in.
//
// Fast mode has 2 key differences from the default Protect experience:
// - Shared with all builders: By default, Flashbots Protect transactions are only shared with
// the Flashbots Builder, which builds only a subset of all Ethereum blocks. In fast mode,
// transactions are shared with all registered builders no less than one block after they are
// received to increase the number of blocks the user's transaction can be included in.
// - Larger refund paid to validator: By default, only 10% of MEV-Share refunds are paid to
// validators. In fast mode, validators receive 50% of refunds which makes it more likely that
// the user’s transactions will be chosen in a given block.
//
// For more information, see the [Flashbots documentation](https://docs.flashbots.net/flashbots-protect/overview).
//
// To use `fast` mode change the URL to `https://rpc.flashbots.net/fast`.
let flashbots_url = "https://rpc.flashbots.net".parse()?;
// Create a provider.
let provider = ProviderBuilder::new().on_http(flashbots_url);
// Create a signer from a random private key.
let signer = PrivateKeySigner::random();
let wallet = EthereumWallet::from(signer);
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let bob = PrivateKeySigner::random().address();
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_chain_id(1)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000);
// Build the transaction with the provided wallet. Flashbots Protect requires the transaction to
// be signed locally and send using `eth_sendRawTransaction`.
let tx_envelope = tx.build(&wallet).await?;
// Encode the transaction using EIP-2718 encoding.
let tx_encoded = tx_envelope.encoded_2718();
// Send the raw transaction. The transaction is sent to the Flashbots relay and, if valid, will
// be included in a block by a Flashbots builder. Note that the transaction request, as defined,
// is invalid and will not be included in the blockchain.
let pending = provider.send_raw_transaction(&tx_encoded).await?.register().await?;
println!("Sent transaction: {}", pending.tx_hash());
Ok(())
}
Find the source code on Github here.
Example: trace_call
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example trace_call
//! Example of how to trace a transaction using `trace_call`.
use alloy::{
network::TransactionBuilder,
primitives::{address, U256},
providers::{ext::TraceApi, ProviderBuilder},
rpc::types::{trace::parity::TraceType, TransactionRequest},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Create a provider.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider = ProviderBuilder::new().on_http(rpc_url);
// Build a transaction to send 100 wei from Alice to Vitalik.
let alice = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx =
TransactionRequest::default().with_from(alice).with_to(vitalik).with_value(U256::from(100));
// Trace the transaction on top of the latest block.
let trace_type = [TraceType::Trace];
let result = provider.trace_call(&tx, &trace_type).await?;
println!("{:?}", result.trace);
Ok(())
}
Find the source code on Github here.
Example: trace_transaction
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example trace_transaction
//! Example of how to trace a transaction using `trace_transaction`.
use alloy::{
primitives::b256,
providers::{ext::DebugApi, ProviderBuilder},
rpc::types::trace::geth::{
GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions,
GethDefaultTracingOptions,
},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let provider = ProviderBuilder::new().on_anvil_with_config(|anvil| anvil.fork(rpc_url));
// Hash of the tx we want to trace.
let hash = b256!("97a02abf405d36939e5b232a5d4ef5206980c5a6661845436058f30600c52df7");
// Trace with the default tracer.
let default_options = GethDebugTracingOptions::default();
let result = provider.debug_trace_transaction(hash, default_options).await?;
println!("DEFAULT_TRACE: {result:?}");
// Trace with built-in call tracer.
let call_options = GethDebugTracingOptions {
config: GethDefaultTracingOptions {
disable_storage: Some(true),
enable_memory: Some(false),
..Default::default()
},
tracer: Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)),
..Default::default()
};
let result = provider.debug_trace_transaction(hash, call_options).await?;
println!("CALL_TRACE: {result:?}");
// Trace using a custom JavaScript tracer.
let js_options = GethDebugTracingOptions {
tracer: Some(GethDebugTracerType::JsTracer("{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"DELEGATECALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}".into())),
..Default::default()
};
let result = provider.debug_trace_transaction(hash, js_options).await?;
println!("JS_TRACER: {result:?}");
Ok(())
}
Find the source code on Github here.
Example: transfer_erc20
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example transfer_erc20
//! Example of how to transfer ERC20 tokens from one account to another.
use alloy::{
primitives::U256,
providers::{Provider, ProviderBuilder},
sol,
};
use eyre::Result;
// Codegen from artifact.
sol!(
#[allow(missing_docs)]
#[sol(rpc)]
ERC20Example,
"examples/artifacts/ERC20Example.json"
);
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a forked Anvil node.
// Ensure `anvil` is available in $PATH.
let rpc_url = "https://eth.merkle.io";
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.on_anvil_with_wallet_and_config(|anvil| anvil.fork(rpc_url));
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Deploy the `ERC20Example` contract.
let contract = ERC20Example::deploy(provider).await?;
// Register the balances of Alice and Bob before the transfer.
let alice_before_balance = contract.balanceOf(alice).call().await?._0;
let bob_before_balance = contract.balanceOf(bob).call().await?._0;
// Transfer and wait for inclusion.
let amount = U256::from(100);
let tx_hash = contract.transfer(bob, amount).send().await?.watch().await?;
println!("Sent transaction: {tx_hash}");
// Register the balances of Alice and Bob after the transfer.
let alice_after_balance = contract.balanceOf(alice).call().await?._0;
let bob_after_balance = contract.balanceOf(bob).call().await?._0;
// Check the balances of Alice and Bob after the transfer.
assert_eq!(alice_before_balance - alice_after_balance, amount);
assert_eq!(bob_after_balance - bob_before_balance, amount);
Ok(())
}
Find the source code on Github here.
Example: transfer_eth
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example transfer_eth
//! Example of how to transfer ETH from one account to another.
use alloy::{
network::TransactionBuilder,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().with_recommended_fillers().on_anvil_with_wallet();
// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];
// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx =
TransactionRequest::default().with_from(alice).with_to(bob).with_value(U256::from(100));
// Send the transaction and listen for the transaction to be included.
let tx_hash = provider.send_transaction(tx).await?.watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Find the source code on Github here.
Wallets
- AWS signer
- Ledger signer
- Private key signer
- Mnemonic signer
- Sign message
- Verify message
- Sign permit hash
- Trezor signer
- Yubi signer
- Keystore signer
- Create keystore
Example: aws_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example aws_signer
//! Example showing how to use the AWS KMS signer.
use alloy::signers::{aws::AwsSigner, Signer};
use aws_config::BehaviorVersion;
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
let Ok(key_id) = std::env::var("AWS_KEY_ID") else {
return Ok(());
};
let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
let client = aws_sdk_kms::Client::new(&config);
let signer = AwsSigner::new(client, key_id, Some(1)).await?;
let message = "Hello, world!";
let signature = signer.sign_message(message.as_bytes()).await?;
assert_eq!(signature.recover_address_from_msg(message)?, signer.address());
Ok(())
}
Find the source code on Github here.
Example: ledger_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example ledger_signer
//! Example of signing and sending a transaction using a Ledger device.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::ledger::{HDPath, LedgerSigner},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Instantiate the application by acquiring a lock on the Ledger device.
let signer = LedgerSigner::new(HDPath::LedgerLive(0), Some(1)).await?;
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Build a transaction to send 100 wei from Alice to Vitalik.
// The `from` field is automatically filled to the first signer's address (Alice).
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction and wait for inclusion with 3 confirmations.
let tx_hash =
provider.send_transaction(tx).await?.with_required_confirmations(3).watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Find the source code on Github here.
Example: private_key_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example private_key_signer
//! Example of using a local wallet to sign and send a transaction.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
node_bindings::Anvil,
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::local::PrivateKeySigner,
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let anvil = Anvil::new().block_time(1).try_spawn()?;
// Set up signer from the first default Anvil account (Alice).
// [RISK WARNING! Writing a private key in the code file is insecure behavior.]
// The following code is for testing only. Set up signer from private key, be aware of danger.
// let signer: PrivateKeySigner = "<PRIVATE_KEY>".parse().expect("should parse private key");
let signer: PrivateKeySigner = anvil.keys()[0].clone().into();
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let rpc_url = anvil.endpoint_url();
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Build a transaction to send 100 wei from Alice to Vitalik.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"))
.with_value(U256::from(100));
// Send the transaction and wait for inclusion.
let tx_hash = provider.send_transaction(tx).await?.watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Find the source code on Github here.
Example: mnemonic_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example mnemonic_signer
//! Example of using `MnemonicBuilder` to access a wallet from a mnemonic phrase.
use alloy::signers::local::{coins_bip39::English, MnemonicBuilder};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
let phrase = "work man father plunge mystery proud hollow address reunion sauce theory bonus";
let index = 0u32;
let password = "TREZOR123";
// Access mnemonic phrase with password.
// Child key at derivation path: m/44'/60'/0'/0/{index}.
let wallet = MnemonicBuilder::<English>::default()
.phrase(phrase)
.index(index)?
// Use this if your mnemonic is encrypted.
.password(password)
.build()?;
println!("Wallet: {}", wallet.address());
// Generate a random wallet (24 word phrase) at custom derivation path.
let wallet = MnemonicBuilder::<English>::default()
.word_count(24)
.derivation_path("m/44'/60'/0'/2/1")?
// Optionally add this if you want the generated mnemonic to be written
// to a file `.write_to(path)`.
.build_random()?;
println!("Random wallet: {}", wallet.address());
Ok(())
}
Find the source code on Github here.
Example: sign_message
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example sign_message
//! Example of signing a message with a signer.
use alloy::signers::{local::PrivateKeySigner, Signer};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Set up a random signer.
let signer = PrivateKeySigner::random();
// Optionally, the wallet's chain id can be set, in order to use EIP-155
// replay protection with different chains.
let signer = signer.with_chain_id(Some(1337));
// The message to sign.
let message = b"hello";
// Sign the message asynchronously with the signer.
let signature = signer.sign_message(message).await?;
println!("Signature produced by {}: {:?}", signer.address(), signature);
println!("Signature recovered address: {}", signature.recover_address_from_msg(&message[..])?);
Ok(())
}
Find the source code on Github here.
Example: verify_message
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example verify_message
//! Example of verifying that a message was signed by the provided address.
use alloy::signers::{local::PrivateKeySigner, SignerSync};
use eyre::{Ok, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Instantiate a signer.
let signer = PrivateKeySigner::random();
// Sign a message.
let message = "Some data";
let signature = signer.sign_message_sync(message.as_bytes())?;
// Recover the signer from the message.
let recovered = signature.recover_address_from_msg(message)?;
assert_eq!(recovered, signer.address());
Ok(())
}
Find the source code on Github here.
Example: sign_permit_hash
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example sign_permit_hash
//! Example of signing a permit hash using a wallet.
use alloy::{
primitives::{address, keccak256, U256},
signers::{local::PrivateKeySigner, Signer},
sol,
sol_types::{eip712_domain, SolStruct},
};
use eyre::Result;
use serde::Serialize;
sol! {
#[allow(missing_docs)]
#[derive(Serialize)]
struct Permit {
address owner;
address spender;
uint256 value;
uint256 nonce;
uint256 deadline;
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Set up a random signer.
let signer = PrivateKeySigner::random();
let domain = eip712_domain! {
name: "Uniswap V2",
version: "1",
chain_id: 1,
verifying_contract: address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
salt: keccak256("test"),
};
let permit = Permit {
owner: signer.address(),
spender: address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"),
value: U256::from(100),
nonce: U256::from(0),
deadline: U256::from(0),
};
// Derive the EIP-712 signing hash.
let hash = permit.eip712_signing_hash(&domain);
// Sign the hash asynchronously with the wallet.
let signature = signer.sign_hash(&hash).await?;
println!(
"Recovered address matches wallet address: {}",
signature.recover_address_from_prehash(&hash)? == signer.address()
);
println!("Wallet signature matches: {}", signer.sign_hash(&hash).await? == signature);
Ok(())
}
Find the source code on Github here.
Example: trezor_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example trezor_signer
//! Example of signing and sending a transaction using a Trezor device.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::trezor::{HDPath, TrezorSigner},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Instantiate the application by acquiring a lock on the Trezor device.
let signer = TrezorSigner::new(HDPath::TrezorLive(0), Some(1)).await?;
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Build a transaction to send 100 wei from Alice to Vitalik.
// The `from` field is automatically filled to the first signer's address (Alice).
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction and wait for inclusion with 3 confirmations.
let tx_hash =
provider.send_transaction(tx).await?.with_required_confirmations(3).watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Find the source code on Github here.
Example: yubi_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example yubi_signer
//! Example of signing and sending a transaction using a Yubi device.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::local::{
yubihsm::{Connector, Credentials, UsbConfig},
YubiSigner,
},
};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
// We use USB for the example, but you can connect over HTTP as well. Refer
// to the [YubiHSM](https://docs.rs/yubihsm/0.34.0/yubihsm/) docs for more information.
let connector = Connector::usb(&UsbConfig::default());
// Instantiate the connection to the YubiKey. Alternatively, use the
// `from_key` method to upload a key you already have, or the `new` method
// to generate a new keypair.
let signer = YubiSigner::connect(connector, Credentials::default(), 0);
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let rpc_url = "https://eth.merkle.io".parse()?;
let provider =
ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
// Build a transaction to send 100 wei from Alice to Vitalik.
// The `from` field is automatically filled to the first signer's address (Alice).
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction and wait for inclusion with 3 confirmations.
let tx_hash =
provider.send_transaction(tx).await?.with_required_confirmations(3).watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Find the source code on Github here.
Example: keystore_signer
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example keystore_signer
//! Example of using a keystore wallet to sign and send a transaction.
use alloy::{
network::{EthereumWallet, TransactionBuilder},
primitives::{address, U256},
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
signers::local::LocalSigner,
};
use eyre::Result;
use std::{env, path::PathBuf};
#[tokio::main]
async fn main() -> Result<()> {
// Password to decrypt the keystore file with.
let password = "test";
// Set up signer using Alice's keystore file.
// The private key belongs to Alice, the first default Anvil account.
let keystore_file_path =
PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("examples/keystore/alice.json");
let signer = LocalSigner::decrypt_keystore(keystore_file_path, password)?;
let wallet = EthereumWallet::from(signer);
// Create a provider with the wallet.
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_anvil_with_config(|anvil| anvil.block_time(1));
// Build a transaction to send 100 wei from Alice to Vitalik.
// The `from` field is automatically filled to the first signer's address (Alice).
let vitalik = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let tx = TransactionRequest::default().with_to(vitalik).with_value(U256::from(100));
// Send the transaction and wait for inclusion.
let tx_hash = provider.send_transaction(tx).await?.watch().await?;
println!("Sent transaction: {tx_hash}");
Ok(())
}
Find the source code on Github here.
Example: create_keystore
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example create_keystore
//! Example of creating a keystore file from a private key and password, and then reading it back.
use alloy::{primitives::hex, signers::local::LocalSigner};
use eyre::Result;
use rand::thread_rng;
use std::fs::read_to_string;
use tempfile::tempdir;
#[tokio::main]
async fn main() -> Result<()> {
let dir = tempdir()?;
let mut rng = thread_rng();
// Private key of Alice, the first default Anvil account.
let private_key = hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
// Password to encrypt the keystore file with.
let password = "test";
// Create a keystore file from the private key of Alice, returning a [Wallet] instance.
let (wallet, file_path) =
LocalSigner::encrypt_keystore(&dir, &mut rng, private_key, password, None)?;
let keystore_file_path = dir.path().join(file_path);
println!("Wrote keystore for {} to {:?}", wallet.address(), keystore_file_path);
// Read the keystore file back.
let recovered_wallet = LocalSigner::decrypt_keystore(keystore_file_path.clone(), password)?;
println!(
"Read keystore from {:?}, recovered address: {}",
keystore_file_path,
recovered_wallet.address()
);
// Assert that the address of the original key and the recovered key are the same.
assert_eq!(wallet.address(), recovered_wallet.address());
// Display the contents of the keystore file.
let keystore_contents = read_to_string(keystore_file_path)?;
println!("Keystore file contents: {keystore_contents:?}");
Ok(())
}
Find the source code on Github here.
Advanced
- Using
AnyNetwork
- Decoding with
json_abi
- Encoding with
dyn_abi
- Static encoding with
sol!
- Using
foundry-fork-db
- Wrapping
Provider
trait overreth-db
Example: any_network
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example any_network
//! Example of using `AnyNetwork` to get a type-safe representation of
//! network-specific data.
//!
//! In this example, we extract the `gasUsedForL1` and `l1BlockNumber` fields
//! of Arbitrum's transaction receipts.
use alloy::{
network::{AnyNetwork, EthereumWallet},
primitives::{address, Address, U128, U256, U64},
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
sol,
};
use eyre::Result;
// The address of the contract below deployed to Arbitrum Sepolia.
const COUNTER_CONTRACT_ADDRESS: Address = address!("d62FC4aB418580919F22E2aC3A0D93F832A95E70");
sol! {
#[allow(missing_docs)]
#[sol(rpc)]
contract Counter {
uint256 public number;
function setNumber(uint256 newNumber) public {
number = newNumber;
}
function increment() public {
number++;
}
}
}
#[derive(Debug, serde::Deserialize)]
struct ArbOtherFields {
#[serde(rename = "gasUsedForL1")]
gas_used_for_l1: U128,
#[serde(rename = "l1BlockNumber")]
l1_block_number: U64,
}
#[tokio::main]
async fn main() -> Result<()> {
// [RISK WARNING! Writing a private key in the code file is insecure behavior.]
// The following code is for testing only. Set up signer from private key, be aware of danger.
let signer: PrivateKeySigner = "<PRIVATE_KEY>".parse().expect("should parse private key");
let wallet = EthereumWallet::from(signer);
// Create a provider with the Arbitrum Sepolia network and the wallet.
let rpc_url = "https://sepolia-rollup.arbitrum.io/rpc".parse()?;
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.network::<AnyNetwork>()
.wallet(wallet)
.on_http(rpc_url);
// Create a contract instance.
let contract = Counter::new(COUNTER_CONTRACT_ADDRESS, &provider);
// Set the number to 42.
let builder = contract.setNumber(U256::from(42));
let receipt = builder.send().await?.get_receipt().await?;
// Fetch the `gasUsedForL1` and `l1BlockNumber` fields from the receipt.
let arb_fields: ArbOtherFields = receipt.other.deserialize_into()?;
let l1_gas = arb_fields.gas_used_for_l1.to::<u128>();
let l1_block_number = arb_fields.l1_block_number.to::<u64>();
println!("Gas used for L1: {}", l1_gas);
println!("L1 block number: {}", l1_block_number);
Ok(())
}
Find the source code on Github here.
Example: decoding_json_abi
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example decoding_json_abi
//! Example for deserializing ABI using `json_abi`.
use alloy::json_abi::JsonAbi;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get the contract abi.
let path = std::env::current_dir()?.join("examples/advanced/examples/abi/SimpleLending.json");
let contents = std::fs::read(path)?;
let abi: JsonAbi = serde_json::from_slice(&contents)?;
// Print deserialized ABI components
println!("Deserialized ABI:");
// Constructor
if let Some(constructor) = &abi.constructor {
println!("\n>> Constructor:");
println!(" Inputs: {:?}", constructor.inputs);
println!(" State mutability: {:?}", constructor.state_mutability);
}
println!("\n=========\n");
// Functions
println!("Functions:");
for (name, functions) in &abi.functions {
println!("\n>> {}:", name);
for function in functions {
println!(" Inputs: {:?}", function.inputs);
println!(" Outputs: {:?}", function.outputs);
println!(" State mutability: {:?}", function.state_mutability);
}
}
println!("\n=========\n");
// Events
println!("Events:");
for (name, events) in &abi.events {
println!("\n>> {}:", name);
for event in events {
println!(" Inputs: {:?}", event.inputs);
println!(" Anonymous: {}", event.anonymous);
}
}
println!("\n=========\n");
// Errors
println!("Errors:");
for (name, errors) in &abi.errors {
println!(">> {}:", name);
for error in errors {
println!(" Inputs: {:?}", error.inputs);
}
}
println!("\n=========\n");
// Example of working with a specific function
if let Some(add_collateral) = abi.functions.get("addCollateral").and_then(|f| f.first()) {
println!("Example: addCollateral() function exists!");
println!("Inputs:");
for input in &add_collateral.inputs {
println!(" Name: {}, Type: {}", input.name, input.ty);
}
}
Ok(())
}
Find the source code on Github here.
Example: encoding_dyn_abi
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example encoding_dyn_abi
//! Example of [EIP712](https://eips.ethereum.org/EIPS/eip-712) encoding and decoding via `dyn_abi`.
use alloy::{
dyn_abi::{DynSolType, DynSolValue},
hex,
primitives::{keccak256, Address, U256},
signers::{local::PrivateKeySigner, Signer},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// EIP-712 domain
let domain_type = DynSolType::Tuple(vec![
DynSolType::String, // name
DynSolType::String, // version
DynSolType::Uint(256), // chainId
DynSolType::Address, // verifyingContract
]);
let domain_value = DynSolValue::Tuple(vec![
DynSolValue::String("Alloy".to_string()),
DynSolValue::String("1.0.1".to_string()),
DynSolValue::Uint(U256::from(1), 256),
DynSolValue::Address(Address::from([0x42; 20])),
]);
// Message type (sample message)
let message_type = DynSolType::Tuple(vec![
DynSolType::Address, // from
DynSolType::Address, // to
DynSolType::String, // contents
]);
// Random values
let message_value = DynSolValue::Tuple(vec![
DynSolValue::Address(Address::from([0x11; 20])),
DynSolValue::Address(Address::from([0x22; 20])),
DynSolValue::String("EIP-712 encoding".to_string()),
]);
// Encode the domain and message
let encoded_domain = domain_value.abi_encode();
let encoded_message = message_value.abi_encode();
println!("Encoded domain: 0x{}", hex::encode(&encoded_domain));
println!("Encoded message: 0x{}", hex::encode(&encoded_message));
// Decode the domain and message
let decoded_domain = domain_type.abi_decode(&encoded_domain)?;
let decoded_message = message_type.abi_decode(&encoded_message)?;
println!("\nDecoded domain:");
print_tuple(&decoded_domain, &["name", "version", "chainId", "verifyingContract"]);
println!("\nDecoded message:");
print_tuple(&decoded_message, &["from", "to", "contents"]);
// Calculate EIP-712 hash
let domain_separator = keccak256(&encoded_domain);
let message_hash = keccak256(&encoded_message);
let eip712_hash = keccak256([&[0x19, 0x01], &domain_separator[..], &message_hash[..]].concat());
println!("\nEIP-712 hash: 0x{}", hex::encode(eip712_hash));
// Signing the hash via random signer
// Ref: examples/wallets/examples/sign_message.rs
// Create a signer
let wallet = PrivateKeySigner::random();
println!("\nSigner address: {}", wallet.address());
// Sign the EIP-712 hash
let signature = wallet.sign_hash(&eip712_hash).await?;
println!("Signature: 0x{}", hex::encode(signature.as_bytes()));
// Verify the signature
let recovered_address = signature.recover_address_from_prehash(&eip712_hash)?;
println!("Recovered address: {}", recovered_address);
assert_eq!(recovered_address, wallet.address(), "Signature verification failed");
println!("Signature verified successfully!");
Ok(())
}
/// Utility function to print the decoded data.
fn print_tuple(value: &DynSolValue, field_names: &[&str]) {
if let DynSolValue::Tuple(values) = value {
for (value, name) in values.iter().zip(field_names.iter()) {
println!(" {}: {:?}", name, value);
}
}
}
Find the source code on Github here.
Example: encoding_sol_static
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example encoding_sol_static
//! Example for static encoding calldata via `sol!`.
use std::str::FromStr;
use alloy::{
hex,
primitives::{Address, U256},
sol,
sol_types::SolCall,
};
// Using UniswapV2 `swapExactTokensForTokens()` method for this example.
// See: https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02#swapexacttokensfortokens
sol!(
#[allow(missing_docs)]
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
);
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Swap 1 DAI for 1 USDC with a slippage tolerance of 1%.
let amount_in = U256::from(1000000000000000000u128); // 1 token
let amount_out_min = U256::from(9900000000000000000u128); // 0.99 tokens (1% slippage)
// Construct path DAI --> WETH --> USDC.
let token_in = Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F")?; // DAI
let weth = Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")?; // WETH
let token_out = Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")?; // USDC
let path = vec![token_in, weth, token_out];
// Recipient of the output tokens.
let to = Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e")?;
// Unix timestamp after which the transaction will revert.
let deadline = U256::from(1690000000u64); // Random timestamp
let swap_data =
swapExactTokensForTokensCall::new((amount_in, amount_out_min, path, to, deadline));
let encoded = hex::encode(swapExactTokensForTokensCall::abi_encode(&swap_data));
println!("Encoded: 0x{}", encoded);
Ok(())
}
Find the source code on Github here.
Example: foundry_fork_db
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example foundry_fork_db
//! This example demonstrates how to use `foundry_fork_db` to build a minimal fork with a db that
//! caches responses from the RPC provider.
//!
//! `foundry_fork_db` is designed out-of-the-box to smartly cache and deduplicate requests to the
//! rpc provider, while fetching data that is missing from it's db instance.
//!
//! `foundry_fork_db` serves as the backend for Foundry's forking functionality in Anvil and Forge.
use std::sync::Arc;
use alloy::{
consensus::BlockHeader,
eips::BlockId,
network::{AnyNetwork, TransactionBuilder, TransactionResponse},
node_bindings::Anvil,
primitives::U256,
providers::{Provider, ProviderBuilder},
rpc::types::{
serde_helpers::WithOtherFields, Block, BlockTransactionsKind, TransactionRequest,
},
};
use eyre::Result;
use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend};
use revm::{db::CacheDB, DatabaseRef, Evm};
use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, TxEnv};
#[tokio::main]
async fn main() -> Result<()> {
let anvil = Anvil::new().spawn();
let provider = ProviderBuilder::new().network::<AnyNetwork>().on_http(anvil.endpoint_url());
let block =
provider.get_block(BlockId::latest(), BlockTransactionsKind::Hashes).await?.unwrap();
// The `BlockchainDbMeta` is used a identifier when the db is flushed to the disk.
// This aids in cases where the disk contains data from multiple forks.
let meta = BlockchainDbMeta::default()
.with_chain_id(31337)
.with_block(&block.inner)
.with_url(&anvil.endpoint());
let db = BlockchainDb::new(meta, None);
// Spawn the backend with the db instance.
// `SharedBackend` is used to send request to the `BackendHandler` which is responsible for
// filling missing data in the db, and also deduplicate requests that are being sent to the
// RPC provider.
//
// For example, if we send two requests to get_full_block(0) simultaneously, the
// `BackendHandler` is smart enough to only send one request to the RPC provider, and queue the
// other request until the response is received.
// Once the response from RPC provider is received it relays the response to both the requests
// over their respective channels.
//
// The `SharedBackend` and `BackendHandler` communicate over an unbounded channel.
let shared = SharedBackend::spawn_backend(Arc::new(provider.clone()), db, None).await;
let start_t = std::time::Instant::now();
let block_rpc = shared.get_full_block(0).unwrap();
let time_rpc = start_t.elapsed();
// `SharedBackend` is cloneable and holds the channel to the same `BackendHandler`.
#[allow(clippy::redundant_clone)]
let cloned_backend = shared.clone();
// Block gets cached in the db
let start_t = std::time::Instant::now();
let block_cache = cloned_backend.get_full_block(0).unwrap();
let time_cache = start_t.elapsed();
assert_eq!(block_rpc, block_cache);
println!("-------get_full_block--------");
// The backend handle falls back to the RPC provider if the block is not in the cache.
println!("1st request (via rpc): {:?}", time_rpc);
// The block is cached due to the previous request and can be fetched from db.
println!("2nd request (via fork db): {:?}\n", time_cache);
let alice = anvil.addresses()[0];
let bob = anvil.addresses()[1];
let basefee = block.header.base_fee_per_gas.unwrap();
let tx_req = TransactionRequest::default()
.with_from(alice)
.with_to(bob)
.with_value(U256::from(100))
.with_max_fee_per_gas(basefee as u128)
.with_max_priority_fee_per_gas(basefee as u128 + 1)
.with_gas_limit(21000)
.with_nonce(0);
let mut evm = configure_evm_env(block, shared.clone(), configure_tx_env(tx_req));
// Fetches accounts from the RPC
let start_t = std::time::Instant::now();
let alice_bal = shared.basic_ref(alice)?.unwrap().balance;
let bob_bal = shared.basic_ref(bob)?.unwrap().balance;
let time_rpc = start_t.elapsed();
let res = evm.transact().unwrap();
let total_spent = U256::from(res.result.gas_used()) * U256::from(basefee) + U256::from(100);
shared.data().do_commit(res.state);
// Fetches accounts from the cache
let start_t = std::time::Instant::now();
let alice_bal_after = shared.basic_ref(alice)?.unwrap().balance;
let bob_bal_after = shared.basic_ref(bob)?.unwrap().balance;
let time_cache = start_t.elapsed();
println!("-------get_account--------");
println!("1st request (via rpc): {:?}", time_rpc);
println!("2nd request (via fork db): {:?}\n", time_cache);
assert_eq!(alice_bal_after, alice_bal - total_spent);
assert_eq!(bob_bal_after, bob_bal + U256::from(100));
Ok(())
}
fn configure_evm_env<T: TransactionResponse, H: BlockHeader>(
block: WithOtherFields<Block<T, H>>,
shared: SharedBackend,
tx_env: TxEnv,
) -> Evm<'static, (), CacheDB<SharedBackend>> {
let basefee = block.header.base_fee_per_gas().map(U256::from).unwrap_or_default();
let block_env = BlockEnv {
number: U256::from(block.header.number()),
coinbase: block.header.beneficiary(),
timestamp: U256::from(block.header.timestamp()),
gas_limit: U256::from(block.header.gas_limit()),
basefee,
prevrandao: block.header.mix_hash(),
difficulty: block.header.difficulty(),
blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new(
block.header.excess_blob_gas().unwrap_or_default(),
)),
};
let db = CacheDB::new(shared);
let evm = Evm::builder().with_block_env(block_env).with_db(db).with_tx_env(tx_env).build();
evm
}
fn configure_tx_env(tx_req: TransactionRequest) -> TxEnv {
TxEnv {
caller: tx_req.from.unwrap(),
transact_to: tx_req.to.unwrap(),
value: tx_req.value.unwrap(),
gas_price: U256::from(tx_req.max_fee_per_gas.unwrap()),
gas_limit: tx_req.gas.unwrap_or_default(),
..Default::default()
}
}
Find the source code on Github here.
Example: reth_db_provider
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example reth_db_provider
//! In this example, we demonstrate how we wrap the `Provider` trait over reth-db by
//! leveraging `ProviderCall`.
//!
//! `ProviderCall` enables the alloy-provider to fetch results of a rpc request from arbitrary
//! sources. These arbitray sources could be a RPC call over the network, a local database, or even
//! a synchronous function call.
//!
//! `ProviderCall` is the final future in the flow of an rpc request and is used by the
//! `RpcWithBlock` and `EthCall` types under the hood to give flexibility to the user to use
//! their own implementation of the `Provider` trait and fetch results from any source.
//!
//! Learn more about `ProviderCall` [here](https://github.com/alloy-rs/alloy/pull/788).
use std::{marker::PhantomData, path::PathBuf, sync::Arc};
use alloy::{
eips::{BlockId, BlockNumberOrTag},
node_bindings::{utils::run_with_tempdir, Reth},
primitives::{address, Address, U64},
providers::{
Provider, ProviderBuilder, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock,
},
rpc::client::NoParams,
transports::{Transport, TransportErrorKind},
};
use eyre::Result;
use reth_chainspec::ChainSpecBuilder;
use reth_db::{open_db_read_only, DatabaseEnv};
use reth_node_ethereum::EthereumNode;
use reth_node_types::NodeTypesWithDBAdapter;
use reth_provider::{
providers::StaticFileProvider, BlockNumReader, DatabaseProviderFactory, ProviderError,
ProviderFactory, StateProvider, TryIntoHistoricalStateProvider,
};
mod reth_db_layer;
use reth_db_layer::RethDbLayer;
#[tokio::main]
async fn main() -> Result<()> {
run_with_tempdir("provider-call-reth-db", |data_dir| async move {
// Initializing reth with a tmp data directory.
// We use a tmp directory for the purposes of this example.
// This would actually use an existing reth datadir specified by `--datadir` when starting
// your reth node.
let reth = Reth::new()
.dev()
.disable_discovery()
.block_time("1s")
.data_dir(data_dir.clone())
.spawn();
let db_path = data_dir.join("db");
// Initialize the provider with the reth-db layer. The reth-db layer intercepts the rpc
// requests and returns the results from the reth-db database.
// Any RPC method that is not implemented in the RethDbProvider gracefully falls back to the
// RPC provider specified in the `on_http` method.
let provider =
ProviderBuilder::new().layer(RethDbLayer::new(db_path)).on_http(reth.endpoint_url());
// Initialize the RPC provider to compare the time taken to fetch the results.
let rpc_provider = ProviderBuilder::new().on_http(reth.endpoint_url());
println!("--------get_block_number---------");
let start_t = std::time::Instant::now();
let latest_block_db = provider.get_block_number().await.unwrap();
println!("via reth-db: {:?}", start_t.elapsed());
let start_t = std::time::Instant::now();
let latest_block_rpc = rpc_provider.get_block_number().await.unwrap();
println!("via rpc: {:?}\n", start_t.elapsed());
assert_eq!(latest_block_db, latest_block_rpc);
println!("------get_transaction_count------");
let alice = address!("14dC79964da2C08b23698B3D3cc7Ca32193d9955");
let start_t = std::time::Instant::now();
let nonce_db =
provider.get_transaction_count(alice).block_id(BlockId::latest()).await.unwrap();
println!("via reth-db: {:?}", start_t.elapsed());
let start_t = std::time::Instant::now();
let nonce_rpc =
rpc_provider.get_transaction_count(alice).block_id(BlockId::latest()).await.unwrap();
println!("via rpc: {:?}\n", start_t.elapsed());
assert_eq!(nonce_db, nonce_rpc);
})
.await;
Ok(())
}
/// Implement the `ProviderLayer` trait for the `RethDBLayer` struct.
impl<P, T> ProviderLayer<P, T> for RethDbLayer
where
P: Provider<T>,
T: Transport + Clone,
{
type Provider = RethDbProvider<P, T>;
fn layer(&self, inner: P) -> Self::Provider {
RethDbProvider::new(inner, self.db_path().clone())
}
}
/// A provider that overrides the vanilla `Provider` trait to get results from the reth-db.
///
/// It holds the `reth_provider::ProviderFactory` that enables read-only access to the database
/// tables and static files.
#[derive(Clone, Debug)]
pub struct RethDbProvider<P, T> {
inner: P,
db_path: PathBuf,
provider_factory: DbAccessor,
_pd: PhantomData<T>,
}
impl<P, T> RethDbProvider<P, T> {
/// Create a new `RethDbProvider` instance.
pub fn new(inner: P, db_path: PathBuf) -> Self {
let db = open_db_read_only(&db_path, Default::default()).unwrap();
let chain_spec = ChainSpecBuilder::mainnet().build();
let static_file_provider =
StaticFileProvider::read_only(db_path.join("static_files"), false).unwrap();
let provider_factory =
ProviderFactory::new(db.into(), chain_spec.into(), static_file_provider);
let db_accessor: DbAccessor<
ProviderFactory<NodeTypesWithDBAdapter<EthereumNode, Arc<DatabaseEnv>>>,
> = DbAccessor::new(provider_factory);
Self { inner, db_path, provider_factory: db_accessor, _pd: PhantomData }
}
const fn factory(&self) -> &DbAccessor {
&self.provider_factory
}
/// Get the DB Path
pub fn db_path(&self) -> PathBuf {
self.db_path.clone()
}
}
/// Implement the `Provider` trait for the `RethDbProvider` struct.
///
/// This is where we override specific RPC methods to fetch from the reth-db.
impl<P, T> Provider<T> for RethDbProvider<P, T>
where
P: Provider<T>,
T: Transport + Clone,
{
fn root(&self) -> &RootProvider<T> {
self.inner.root()
}
/// Override the `get_block_number` method to fetch the latest block number from the reth-db.
fn get_block_number(&self) -> ProviderCall<T, NoParams, U64, u64> {
let provider = self.factory().provider().map_err(TransportErrorKind::custom).unwrap();
let best = provider.best_block_number().map_err(TransportErrorKind::custom);
ProviderCall::ready(best)
}
/// Override the `get_transaction_count` method to fetch the transaction count of an address.
///
/// `RpcWithBlock` uses `ProviderCall` under the hood.
fn get_transaction_count(&self, address: Address) -> RpcWithBlock<T, Address, U64, u64> {
let this = self.factory().clone();
RpcWithBlock::new_provider(move |block_id| {
let provider = this.provider_at(block_id).map_err(TransportErrorKind::custom).unwrap();
let maybe_acc =
provider.basic_account(address).map_err(TransportErrorKind::custom).unwrap();
let nonce = maybe_acc.map(|acc| acc.nonce).unwrap_or_default();
ProviderCall::ready(Ok(nonce))
})
}
}
/// A helper type to get the appropriate DB provider.
#[derive(Debug, Clone)]
struct DbAccessor<DB = ProviderFactory<NodeTypesWithDBAdapter<EthereumNode, Arc<DatabaseEnv>>>>
where
DB: DatabaseProviderFactory<Provider: TryIntoHistoricalStateProvider + BlockNumReader>,
{
inner: DB,
}
impl<DB> DbAccessor<DB>
where
DB: DatabaseProviderFactory<Provider: TryIntoHistoricalStateProvider + BlockNumReader>,
{
const fn new(inner: DB) -> Self {
Self { inner }
}
fn provider(&self) -> Result<DB::Provider, ProviderError> {
self.inner.database_provider_ro()
}
fn provider_at(&self, block_id: BlockId) -> Result<Box<dyn StateProvider>, ProviderError> {
let provider = self.inner.database_provider_ro()?;
let block_number = match block_id {
BlockId::Hash(hash) => {
if let Some(num) = provider.block_number(hash.into())? {
num
} else {
return Err(ProviderError::BlockHashNotFound(hash.into()));
}
}
BlockId::Number(BlockNumberOrTag::Number(num)) => num,
_ => provider.best_block_number()?,
};
provider.try_into_history_at_block(block_number)
}
}
Find the source code on Github here.
Comparison
Example: compare_new_heads
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example compare_new_heads
//! Example of comparing new block headers from multiple providers.
use alloy::{
network::AnyNetwork,
providers::{Provider, ProviderBuilder},
};
use chrono::Utc;
use clap::Parser;
use eyre::Result;
use futures_util::StreamExt;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::mpsc;
#[derive(Debug, Parser)]
struct Cli {
#[clap(short = 'r', help = "rpcs to connect to, usage: -r <name>:<url> -r <name>:<url> ...")]
rpcs: Vec<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let rpcs: Vec<(&str, &str)> = cli.rpcs.iter().filter_map(|s| s.split_once(':')).collect();
let mut total_streams = 0;
let mut tasks = vec![];
let (sx, mut rx) = mpsc::unbounded_channel();
for (name, url) in rpcs {
let sx = sx.clone();
let name = Arc::new(name.to_string());
let url = url.to_string();
let provider = match ProviderBuilder::new().network::<AnyNetwork>().on_builtin(&url).await {
Ok(provider) => provider,
Err(e) => {
eprintln!("skipping {} at {} because of error: {}", name, url, e);
continue;
}
};
let mut stream = match provider.subscribe_blocks().await {
Ok(stream) => stream.into_stream().take(10),
Err(e) => {
eprintln!("skipping {} at {} because of error: {}", name, url, e);
continue;
}
};
total_streams += 1;
tasks.push(tokio::spawn(async move {
let _p = provider; // keep provider alive
while let Some(header) = stream.next().await {
if let Err(e) = sx.send((name.clone(), header, Utc::now())) {
eprintln!("sending to channel failed: {}", e);
}
}
}));
}
tokio::spawn(async move {
#[derive(Debug)]
struct TxTrack {
first_seen: chrono::DateTime<Utc>,
seen_by: Vec<(Arc<String>, chrono::DateTime<Utc>)>,
}
let mut tracker = HashMap::new();
while let Some((name, header, timestamp)) = rx.recv().await {
let block_number = header.number;
let track = tracker
.entry(block_number)
.and_modify(|t: &mut TxTrack| {
t.seen_by.push((name.clone(), timestamp));
})
.or_insert_with(|| TxTrack {
first_seen: timestamp,
seen_by: vec![(name.clone(), timestamp)],
});
if track.seen_by.len() == total_streams {
let mut msg = String::new();
for (name, timestamp) in &track.seen_by {
msg.push_str(&format!(
"{} +{}ms ",
name,
(*timestamp - track.first_seen).num_milliseconds()
));
}
println!(
"block #{} at {} - {}",
block_number,
track.first_seen.timestamp_millis(),
msg
);
tracker.remove(&block_number);
}
}
});
for task in tasks {
task.await?;
}
Ok(())
}
Find the source code on Github here.
Example: compare_pending_txs
Example
To run this example:
- Clone the examples repository:
git clone git@github.com:alloy-rs/examples.git
- Run:
cargo run --example compare_pending_txs
//! Example of comparing pending transactions from multiple providers.
use alloy::{
network::AnyNetwork,
providers::{Provider, ProviderBuilder},
};
use chrono::Utc;
use clap::Parser;
use eyre::Result;
use futures_util::StreamExt;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::mpsc;
#[derive(Debug, Parser)]
struct Cli {
#[clap(short = 'r', help = "rpcs to connect to, usage: -r <name>:<url> -r <name>:<url> ...")]
rpcs: Vec<String>,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let tmp: Vec<(&str, &str)> = cli.rpcs.iter().filter_map(|s| s.split_once(':')).collect();
let mut rpcs = vec![];
for (name, url) in tmp {
if url.starts_with("http") {
eprintln!("skipping {} at {} because it is not a websocket/ipc endpoint", name, url);
continue;
}
rpcs.push((name, url));
}
let mut total_streams = 0;
let mut tasks = vec![];
let (sx, mut rx) = mpsc::unbounded_channel();
for (name, url) in &rpcs {
let sx = sx.clone();
let name = Arc::new(name.to_string());
let url = url.to_string();
let provider = match ProviderBuilder::new().network::<AnyNetwork>().on_builtin(&url).await {
Ok(provider) => provider,
Err(e) => {
eprintln!("skipping {} at {} because of error: {}", name, url, e);
continue;
}
};
let mut stream = match provider.subscribe_pending_transactions().await {
Ok(stream) => stream.into_stream().take(50),
Err(e) => {
eprintln!("skipping {} at {} because of error: {}", name, url, e);
continue;
}
};
total_streams += 1;
tasks.push(tokio::spawn(async move {
let _p = provider; // keep provider alive
while let Some(tx_hash) = stream.next().await {
if let Err(e) = sx.send((name.clone(), tx_hash, Utc::now())) {
eprintln!("sending to channel failed: {}", e);
}
}
}));
}
tokio::spawn(async move {
#[derive(Debug)]
struct TxTrack {
first_seen: chrono::DateTime<Utc>,
seen_by: Vec<(Arc<String>, chrono::DateTime<Utc>)>,
}
let mut tracker = HashMap::new();
while let Some((name, tx_hash, timestamp)) = rx.recv().await {
let track = tracker
.entry(tx_hash)
.and_modify(|t: &mut TxTrack| {
t.seen_by.push((name.clone(), timestamp));
})
.or_insert_with(|| TxTrack {
first_seen: timestamp,
seen_by: vec![(name.clone(), timestamp)],
});
if track.seen_by.len() == total_streams {
let mut msg = String::new();
for (name, timestamp) in &track.seen_by {
msg.push_str(&format!(
"{} +{}ms ",
name,
(*timestamp - track.first_seen).num_milliseconds()
));
}
println!(
"pending tx #{} at {} - {}",
tx_hash,
track.first_seen.timestamp_millis(),
msg
);
tracker.remove(&tx_hash);
}
}
});
for task in tasks {
task.await?;
}
Ok(())
}
Find the source code on Github here.
Contributing to Alloy
🎈 Thanks for your help improving the project! We are so happy to have you!
There are opportunities to contribute to Alloy at any level. It doesn't matter if you are just getting started with Rust or are the most weathered expert, we can use your help.
No contribution is too small and all contributions are valued.
This guide will help you get started. Do not let this guide intimidate you. It should be considered a map to help you navigate the process.
The dev channel is available for any concerns not covered in this guide, please join us!
Conduct
The Alloy project adheres to the Rust Code of Conduct. This describes the minimum behavior expected from all contributors. Instances of violations of the Code of Conduct can be reported by contacting the project team at james@prestwich.
Contributing in Issues
For any issue, there are fundamentally three ways an individual can contribute:
-
By opening the issue for discussion: For instance, if you believe that you have uncovered a bug in Alloy, creating a new issue in the Alloy issue tracker is the way to report it.
-
By helping to triage the issue: This can be done by providing supporting details (a test case that demonstrates a bug), providing suggestions on how to address the issue, or ensuring that the issue is tagged correctly.
-
By helping to resolve the issue: Typically this is done either in the form of demonstrating that the issue reported is not a problem after all, or more often, by opening a Pull Request that changes some bit of something in Alloy in a concrete and reviewable manner.
Anybody can participate in any stage of contribution. We urge you to participate in the discussion around bugs and participate in reviewing PRs.
Contributions Related to Spelling and Grammar
At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or elsewhere.
Asking for General Help
If you have reviewed existing documentation and still have questions or are having problems, you can open an issue asking for help.
In exchange for receiving help, we ask that you contribute back a documentation PR that helps others avoid the problems that you encountered.
Pull Requests
Pull Requests are the way concrete changes are made to the code, documentation, and dependencies in the Alloy repository.
Before making a large change, it is usually a good idea to first open an issue describing the change to solicit feedback and guidance. This will increase the likelihood of the PR getting merged.
When opening a PR please select the "Allow Edits From Maintainers" option. Alloy maintains strict standards for code quality and style, as well as commit signing. This option allows us to make small changes to your PR to bring it in line with these standards. It helps us get your PR in faster, and with less work from you.
Commits
It is a recommended best practice to keep your changes as logically grouped as possible within individual commits. There is no limit to the number of commits any single Pull Request may have, and many contributors find it easier to review changes that are split across multiple commits.
That said, if you have a number of commits that are "checkpoints" and don't represent a single logical change, please squash those together.
Note that multiple commits often get squashed when they are landed (see the notes about commit squashing).
Commit message guidelines
Commit messages should follow the Conventional Commits specification.
Usage
This section lists some commonly needed commands.
First create a fork.
Next clone your fork with the --recurse-submodules
flag:
git clone --recurse-submodules $FORK_URL
The book is build with mdbook
, which you can get by running cargo install mdbook
.
The book requires mdbook-external-links
: cargo install mdbook-external-links
and mdbook-linkcheck
: cargo install mdbook-linkcheck
.
To see the book change live run:
mdbook serve
To run the book with docker, run:
docker run -p 3000:3000 -v `pwd`:/book peaceiris/mdbook serve
To perform an update of generated output inspect and run ./scripts/update.sh
.
To add a new section (file) to the book, add it to ./src/SUMMARY.md
.
Discuss and update
You will probably get feedback or requests for changes to your Pull Request. This is a big part of the submission process so don't be discouraged! Some contributors may sign off on the Pull Request right away, others may have more detailed comments or feedback. This is a necessary part of the process in order to evaluate whether the changes are correct and necessary.
Any community member can review a PR and you might get conflicting feedback. Keep an eye out for comments from code owners to provide guidance on conflicting feedback.
Once the PR is open, do not rebase the commits. See Commit Squashing for more details.
Commit Squashing
In most cases, do not squash commits that you add to your Pull Request during the review process. When the commits in your Pull Request land, they may be squashed into one commit per logical change. Metadata will be added to the commit message (including links to the Pull Request, links to relevant issues, and the names of the reviewers). The commit history of your Pull Request, however, will stay intact on the Pull Request page.
Reviewing Pull Requests
Any Alloy community member is welcome to review any pull request.
All Alloy contributors who choose to review and provide feedback on Pull Requests have a responsibility to both the project and the individual making the contribution. Reviews and feedback must be helpful, insightful, and geared towards improving the contribution as opposed to simply blocking it. If there are reasons why you feel the PR should not land, explain what those are. Do not expect to be able to block a Pull Request from advancing simply because you say "No" without giving an explanation. Be open to having your mind changed. Be open to working with the contributor to make the Pull Request better.
Reviews that are dismissive or disrespectful of the contributor or any other reviewers are strictly counter to the Code of Conduct.
When reviewing a Pull Request, the primary goals are for the codebase to improve and for the person submitting the request to succeed. Even if a Pull Request does not land, the submitters should come away from the experience feeling like their effort was not wasted or unappreciated. Every Pull Request from a new contributor is an opportunity to grow the community.
Review a bit at a time.
Do not overwhelm new contributors.
It is tempting to micro-optimize and make everything about relative performance, perfect grammar, or exact style matches. Do not succumb to that temptation.
Focus first on the most significant aspects of the change:
- Does this change make sense for Alloy?
- Does this change make Alloy better, even if only incrementally?
- Are there clear bugs or larger scale issues that need attending to?
- Is the commit message readable and correct? If it contains a breaking change is it clear enough?
Note that only incremental improvement is needed to land a PR. This means that the PR does not need to be perfect, only better than the status quo. Follow up PRs may be opened to continue iterating.
When changes are necessary, request them, do not demand them, and do not assume that the submitter already knows how to add a test or run a benchmark.
Specific performance optimization techniques, coding styles and conventions change over time. The first impression you give to a new contributor never does.
Nits (requests for small changes that are not essential) are fine, but try to avoid stalling the Pull Request. Most nits can typically be fixed by the Alloy Collaborator landing the Pull Request but they can also be an opportunity for the contributor to learn a bit more about the project.
It is always good to clearly indicate nits when you comment: e.g.
Nit: change foo() to bar(). But this is not blocking.
If your comments were addressed but were not folded automatically after new commits or if they proved to be mistaken, please, hide them with the appropriate reason to keep the conversation flow concise and relevant.
Be aware of the person behind the code
Be aware that how you communicate requests and reviews in your feedback can have a significant impact on the success of the Pull Request. Yes, we may land a particular change that makes Alloy better, but the individual might just not want to have anything to do with Alloy ever again. The goal is not just having good code.
Abandoned or Stalled Pull Requests
If a Pull Request appears to be abandoned or stalled, it is polite to first
check with the contributor to see if they intend to continue the work before
checking if they would mind if you took it over (especially if it just has nits
left). When doing so, it is courteous to give the original contributor credit
for the work they started (either by preserving their name and email address in
the commit log, or by using an Author:
meta-data tag in the commit.
Adapted from the Tokio contributing guide.