Use Rust
In this two-part tutorial we'll use Rust and the ethers-rs library to:
- Send a legacy transaction
("type":"0x0")
- Send an EIP-1559 transaction
("type":"0x2")
This tutorial uses the Sepolia testnet. Also see Transaction types.
Prerequisites
- Make sure that you have test ETH in your MetaMask wallet. You can obtain test ETH for the Sepolia network using the Infura Sepolia faucet.
- Install Rust from The Cargo Book.
Send a legacy transaction
1. Create a new project
Open a terminal and create a new project.
cargo new infura_rs
This creates the infura_rs
directory with the following structure:
infura_rs
├── Cargo.toml
└── src
└── main.rs
Refer to the Cargo documentation for more information about getting started with Cargo.
2. Edit the dependencies
Open Cargo.toml
with your preferred editor and add the following dependencies to it:
[dependencies]
ethers = "2.0"
eyre = "0.6.8"
hex = "0.4.3"
tokio = { version = "1.28.2", features = ["full"] }
serde_json = "1.0.96"
3. Update the main code
Open the Rust source src/main.rs
and replace its contents with 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.
// Replace the SIGNER_PRIVATE_KEY with
// the private key of your Ethereum account (without the 0x prefix).
// However, we recommended that you 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 = "0xAED01C776d98303eE080D25A21f0a42D94a86D9c";
// connect the wallet to the provider
let client = SignerMiddleware::new(provider, wallet);
// Craft the transaction
// The below code knows how to figure out the
// default gas value and determine the next nonce
// so you do not need to explicitly add them.
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(())
}
Next, make the following updates to the above code:
- On line 16 replace the
INFURA_API_KEY
with you API key from the Infura dashboard. - On line 26 replace the
SIGNER_PRIVATE_KEY
with the private key of your Ethereum account. - On line 29, use a test address, such as
0xAED01C776d98303eE080D25A21f0a42D94a86D9c
.
To better secure your keys, follow the recommended approach described in the section Create the .env file.
`
4. Run the code
From the infura_rs
directory, run the code.
cargo run
You will see an output similar to the following.
Use the wrap button on the top right of the below code block window for wrapped display.
Compiling infura_rs v0.1.0 (/Users/rajkaramchedu/onboarding/traian-tutorials/infura_rs)
Finished dev [unoptimized + debuginfo] target(s) in 2.14s
Running `target/debug/infura_rs`
Sent tx: {"hash":"0x3cb5a5fac18e889457905351c9950108873a8f0789fe83e8a733b8367f49a67a","nonce":"0x1","blockHash":"0xa2787f5ec22d491588a8ffc6e7cec3ed97fccac4845e448650d02fce672a657c","blockNumber":"0x3a7608","transactionIndex":"0x3d","from":"0xe33fef60722ba79989aeaa1b6e6daf7f351c0fbb","to":"0xaed01c776d98303ee080d25a21f0a42d94a86d9c","value":"0x2386f26fc10000","gasPrice":"0x3cc","gas":"0x5208","input":"0x","v":"0x1546d71","r":"0x92aa9fe6039946db5ea291a245529a5d67f5531e95d74c483fe8283cca9ec666","s":"0x4a5c0de8e64c79659965fb36f2b0ea1d295ae868f5f65809ef4cf1ef55239e09","type":"0x0","chainId":"0xaa36a7"}
Tx receipt: {"transactionHash":"0x3cb5a5fac18e889457905351c9950108873a8f0789fe83e8a733b8367f49a67a","transactionIndex":"0x3d","blockHash":"0xa2787f5ec22d491588a8ffc6e7cec3ed97fccac4845e448650d02fce672a657c","blockNumber":"0x3a7608","from":"0xe33fef60722ba79989aeaa1b6e6daf7f351c0fbb","to":"0xaed01c776d98303ee080d25a21f0a42d94a86d9c","cumulativeGasUsed":"0x406e87","gasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x0","effectiveGasPrice":"0x3cc"}
In the above transaction receipt, the transaction type shows "type":"0x0"
indicating that this was a legacy transaction. Next, we will send an EIP-1559 transaction, which is of the type "type":"0x2"
.
Send an EIP-1559 transaction
1. Modify the main code
To send an EIP-1559 transaction, i.e., of the "type":"0x2"
you must use Eip1559TransactionRequest
instead of TransactionRequest
in the main.rs
code. Replace the code in main.rs
with 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;
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.
// Replace the SIGNER_PRIVATE_KEY with
// the private key of your Ethereum account (without the 0x prefix).
// However, we recommended that you 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 = "0xAED01C776d98303eE080D25A21f0a42D94a86D9c";
// 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(())
}
2. Run the modified code
From the infura_rs
directory, run the code.
cargo run
You will see an output similar to the following.
Compiling infura_rs v0.1.0 (/Users/rajkaramchedu/onboarding/traian-tutorials/infura_rs)
warning: unused import: `types::TransactionRequest`
--> src/main.rs:2:12
|
2 | core::{types::TransactionRequest},
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: `infura_rs` (bin "infura_rs") generated 1 warning (run `cargo fix --bin "infura_rs"` to apply 1 suggestion)
Finished dev [unoptimized + debuginfo] target(s) in 2.42s
Running `target/debug/infura_rs`
Sent tx: {"hash":"0xbbc036f4dfe00b590c3693b8a2516316dec5748b3e4085ec92dfc040d8b8492b","nonce":"0x4","blockHash":"0xe64a029af23b18738a69c6eab19b85d99dc2844e8ce54a4bedcc1a75fe18dc08","blockNumber":"0x3a7a42","transactionIndex":"0xf","from":"0xe33fef60722ba79989aeaa1b6e6daf7f351c0fbb","to":"0xaed01c776d98303ee080d25a21f0a42d94a86d9c","value":"0x2386f26fc10000","gasPrice":"0x7735940c","gas":"0x5208","input":"0x","v":"0x1","r":"0xa0e4125501b3146910750408adaa255cd3e3a06461e311e1146a0983fcd9b0e0","s":"0x35c5c6cf6650dcff0ed1e25689d0ce17f7f5986342276f11651976c7048172d1","type":"0x2","accessList":[],"maxPriorityFeePerGas":"0x77359400","maxFeePerGas":"0xb2d05e16","chainId":"0xaa36a7"}
Tx receipt: {"transactionHash":"0xbbc036f4dfe00b590c3693b8a2516316dec5748b3e4085ec92dfc040d8b8492b","transactionIndex":"0xf","blockHash":"0xe64a029af23b18738a69c6eab19b85d99dc2844e8ce54a4bedcc1a75fe18dc08","blockNumber":"0x3a7a42","from":"0xe33fef60722ba79989aeaa1b6e6daf7f351c0fbb","to":"0xaed01c776d98303ee080d25a21f0a42d94a86d9c","cumulativeGasUsed":"0x6a7187","gasUsed":"0x5208","contractAddress":null,"logs":[],"status":"0x1","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","type":"0x2","effectiveGasPrice":"0x7735940c"}
Ignore the "warning: unused import: types::TransactionRequest"
above. In the above transaction receipt, the transaction type shows "type":"0x2"
indicating that this was an EIP-1559 transaction.