Skip to content

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.

  1. 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()],
    );
  2. Simulate with the simulate method.

    Pass the sender account and the payload to the simulate method. This submits the transaction to the fullnode for simulation without executing it on-chain.

    let result = aptos.simulate(&alice, payload).await?;
  3. 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());

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 amount
let 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 transaction
let signed_txn = aptos.sign_transaction(&alice, raw_txn)?;
// Simulate the signed transaction
let result = aptos.simulate_signed(signed_txn).await?;
println!("Success: {}", result.success());
println!("Gas used: {}", result.gas_used());

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.

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());
}
/// 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(())
}