Rust ethers-rs: How to send transactions
Hello rustafarians!
In this support tip we’ll look at how to send both a Legacy and a EIP-1559 transaction in Rust and using the ethers-rs library.
We’ll use Sepolia so make sure to have some test ETH - if you don’t, no worries, head over to https://docs.metamask.io/developer-tools/faucet/sepolia
Before jumping into it, whoever is new to Rust make sure to:
-
Install Rust from Installation - The Cargo Book
-
Create a new project with
cargo new infura_rs
(reference First Steps with Cargo - The Cargo Book) -
Now let’s edit the Cargo.toml file and add these dependencies:
[dependencies]
ethers = "2.0"
eyre = "0.6.8"
hex = "0.4.3"
tokio = { version = "1.28.2", features = ["full"] }
serde_json = "1.0.96"
- Edit the src/main.rs file and add the following code:
use ethers::{
core::{types::TransactionRequest},
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
utils,
prelude::*
};
use eyre::Result;
use std::convert::TryFrom;
#[tokio::main]
async fn main() -> Result<()> {
// connect to the network
let provider = Provider::<Http>::try_from("https://sepolia.infura.io/v3/INFURA_API_KEY")?;
let chain_id = provider.get_chainid().await?;
// define the signer
// for simplicity replace the private key (without 0x), ofc it always recommended to load it from an .env file or external vault
let wallet: LocalWallet = "SIGNER_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(chain_id.as_u64());
let to_address = "<to_address_goes_here>";
// connect the wallet to the provider
let client = SignerMiddleware::new(provider, wallet);
// craft the transaction
// it knows to figure out the default gas value and determine the next nonce so no need to explicitly add them unless you want to
let tx = TransactionRequest::new()
.to(to_address)
.value(U256::from(utils::parse_ether(0.01)?));
// send it!
let pending_tx = client.send_transaction(tx, None).await?;
// get the mined tx
let receipt = pending_tx.await?.ok_or_else(|| eyre::format_err!("tx dropped from mempool"))?;
let tx = client.get_transaction(receipt.transaction_hash).await?;
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
println!("Tx receipt: {}", serde_json::to_string(&receipt)?);
Ok(())
}
- Compile and run it with cargo run - you should see a similar output:
Sent tx: {"hash":"0xb4...","nonce":"0xa",...,"type":"0x0","chainId":"0xaa36a7"}
Tx receipt: {"transactionHash":"0xb4...",...,"type":"0x0","effectiveGasPrice":"0xcbe0"}
Notice that you’ve just sent a legacy tx ("type":"0x0")
- so how to send an EIP-1559 tx ("type":"0x2")
?
That’s a fairly easy change, the TransactionRequest would become Eip1559TransactionRequest, updated code below:
use ethers::{
core::{types::TransactionRequest},
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
utils,
prelude::*
};
use eyre::Result;
use std::convert::TryFrom;
use types::Eip1559TransactionRequest;
#[tokio::main]
async fn main() -> Result<()> {
// connect to the network
let provider = Provider::<Http>::try_from("https://sepolia.infura.io/v3/INFURA_API_KEY")?;
let chain_id = provider.get_chainid().await?;
// define the signer
// for simplicity replace the private key (without 0x), ofc it always recommended to load it from an .env file or external vault
let wallet: LocalWallet = "SIGNER_PRIVATE_KEY"
.parse::<LocalWallet>()?
.with_chain_id(chain_id.as_u64());
let to_address = "<to_address_goes_here>";
// connect the wallet to the provider
let client = SignerMiddleware::new(provider, wallet);
// craft the transaction
// this also knows to estimate the `max_priority_fee_per_gas` but added it manually just to see how it would look
let tx = Eip1559TransactionRequest::new()
.to(to_address)
.value(U256::from(utils::parse_ether(0.01)?))
.max_priority_fee_per_gas(U256::from(2000000000_u128)); // 2 Gwei
// send it!
let pending_tx = client.send_transaction(tx, None).await?;
// get the mined tx
let receipt = pending_tx.await?.ok_or_else(|| eyre::format_err!("tx dropped from mempool"))?;
let tx = client.get_transaction(receipt.transaction_hash).await?;
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
println!("Tx receipt: {}", serde_json::to_string(&receipt)?);
Ok(())
}