Simulating Transactions
Simulating transactions allows you to preview the cost and effect of submitting a transaction before paying fees. You can use this to estimate gas costs, validate that a transaction will succeed, and inspect the resulting state changes and events.
How to Simulate a Transaction
Section titled “How to Simulate a Transaction”-
Build the transaction payload.
Create the entry function payload for the transaction you want to simulate.
use aptos_sdk::types::EntryFunctionPayload;let payload = EntryFunctionPayload::new("0x1::aptos_account::transfer".parse()?,vec![],vec![bob.address().into(), 10_000_000u64.into()],); -
Simulate with the
simulatemethod.Pass the sender account and the payload to the
simulatemethod. This submits the transaction to the fullnode for simulation without executing it on-chain.let result = aptos.simulate(&alice, payload).await?; -
Inspect the simulation result.
The result provides several methods for examining what would happen if the transaction were submitted.
println!("Success: {}", result.success());println!("Gas used: {}", result.gas_used());println!("Gas unit price: {}", result.gas_unit_price());println!("VM status: {}", result.vm_status());println!("Events: {:?}", result.events());println!("Changes: {:?}", result.changes());
Simulating with Custom Gas Parameters
Section titled “Simulating with Custom Gas Parameters”If you want to test how a transaction behaves with specific gas settings, build a signed transaction with custom parameters using TransactionBuilder and simulate it with simulate_signed.
use aptos_sdk::transaction_builder::TransactionBuilder;
// Build a raw transaction with a low max gas amountlet raw_txn = TransactionBuilder::new(payload.clone(), aptos.get_chain_id().await?) .sender(alice.address()) .max_gas_amount(1_000) // Set a low gas limit for testing .gas_unit_price(100) .sequence_number(aptos.get_sequence_number(alice.address()).await?) .expiration_timestamp_secs(aptos.get_latest_ledger_info().await?.timestamp() + 60) .build();
// Sign the transactionlet signed_txn = aptos.sign_transaction(&alice, raw_txn)?;
// Simulate the signed transactionlet result = aptos.simulate_signed(signed_txn).await?;println!("Success: {}", result.success());println!("Gas used: {}", result.gas_used());Gas Estimation Shortcut
Section titled “Gas Estimation Shortcut”The SDK provides an estimate_gas convenience method that simulates the transaction and returns a gas estimate with a built-in 20% buffer to account for minor state changes between simulation and actual submission.
let estimated_gas = aptos.estimate_gas(&alice, payload.clone()).await?;println!("Estimated gas (with 20% buffer): {}", estimated_gas);This is the recommended approach when you need a reliable gas estimate for setting max_gas_amount on production transactions.
Pre-flight Checks
Section titled “Pre-flight Checks”Simulation is especially valuable for detecting errors before spending gas. Common errors you can catch include:
RESOURCE_ALREADY_EXISTS— Attempting to create an account or resource that already exists on-chain.RESOURCE_NOT_FOUND— Referencing a resource that does not exist at the expected address.INSUFFICIENT_BALANCE— The sender does not have enough funds to complete the transfer.SEQUENCE_NUMBER_TOO_OLD— The transaction uses a sequence number that has already been consumed.
let result = aptos.simulate(&alice, payload.clone()).await?;
if !result.success() { eprintln!("Transaction would fail: {}", result.vm_status()); // Handle the error before submitting on-chain} else { println!("Transaction would succeed, gas cost: {}", result.gas_used());}Full Working Example
Section titled “Full Working Example”/// This example demonstrates how to simulate a transaction to validate/// it and estimate gas costs before submitting on-chain.use aptos_sdk::{Aptos, AptosConfig};use aptos_sdk::account::Ed25519Account;
#[tokio::main]async fn main() -> anyhow::Result<()> { // Connect to testnet let aptos = Aptos::new(AptosConfig::testnet())?;
// Generate and fund accounts let alice = Ed25519Account::generate(); let bob = Ed25519Account::generate(); aptos.fund_account(alice.address(), 100_000_000).await?; aptos.fund_account(bob.address(), 100_000_000).await?;
println!("Alice: {}", alice.address()); println!("Bob: {}", bob.address());
// Build the transaction payload use aptos_sdk::types::EntryFunctionPayload;
let payload = EntryFunctionPayload::new( "0x1::aptos_account::transfer".parse()?, vec![], vec![bob.address().into(), 10_000_000u64.into()], );
// 1. Basic simulation println!("\n=== Basic Simulation ===\n"); let result = aptos.simulate(&alice, payload.clone()).await?; println!("Success: {}", result.success()); println!("Gas used: {}", result.gas_used()); println!("Gas unit price: {}", result.gas_unit_price()); println!("VM status: {}", result.vm_status());
// 2. Gas estimation with buffer println!("\n=== Gas Estimation ===\n"); let estimated_gas = aptos.estimate_gas(&alice, payload.clone()).await?; println!("Estimated gas (with 20% buffer): {}", estimated_gas);
// 3. Pre-flight validation println!("\n=== Pre-flight Check ===\n"); if result.success() { println!("Transaction is valid. Proceeding to submit..."); let committed = aptos.sign_submit_and_wait(&alice, payload).await?; let success = committed .data .get("success") .and_then(|v| v.as_bool()) .unwrap_or(false); println!("Transaction committed. Success: {}", success); } else { eprintln!("Transaction would fail: {}", result.vm_status()); }
Ok(())}