Simulate Transaction
Simulate a transaction to estimate gas costs and validate execution without submitting to the blockchain.
When to Use This Method​
POST /transactions/simulate
is essential for:
- Gas Estimation - Calculate gas costs before submission
- Validation - Test if a transaction will succeed
- Error Detection - Identify issues before spending gas
- UI Preview - Show users expected transaction outcomes
Request Format​
Headers​
Content-Type: application/json
Accept: application/json
Body Structure​
{
"sender": "0x1",
"sequence_number": "0",
"max_gas_amount": "0",
"gas_unit_price": "0",
"expiration_timestamp_secs": "1234567890",
"payload": {
"type": "entry_function_payload",
"function": "0x1::aptos_account::transfer",
"type_arguments": [],
"arguments": ["0x2", "100000"]
},
"signature": {
"type": "ed25519_signature",
"public_key": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"signature": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
}
Note: For simulation, you can use dummy signature values (all zeros).
Implementation Examples​
- TypeScript
- Python
- Rust
import { Aptos, AptosConfig, Network, Account } from "@aptos-labs/ts-sdk";
const config = new AptosConfig({
network: Network.MAINNET,
fullnode: "https://api-aptos-mainnet.n.dwellir.com/v1/YOUR_API_KEY",
});
const aptos = new Aptos(config);
// Basic transaction simulation
async function simulateTransaction(
sender: Account,
payload: any
) {
const transaction = await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
});
const [simulationResult] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction,
});
return {
success: simulationResult.success,
gasUsed: simulationResult.gas_used,
gasUnitPrice: simulationResult.gas_unit_price,
maxGasAmount: simulationResult.max_gas_amount,
vmStatus: simulationResult.vm_status,
events: simulationResult.events,
changes: simulationResult.changes,
};
}
// Simulate with gas estimation
async function simulateWithGasEstimation(
sender: Account,
recipient: string,
amount: number
) {
const payload = {
function: "0x1::aptos_account::transfer",
functionArguments: [recipient, amount],
};
const transaction = await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
options: {
maxGasAmount: 0, // Let simulation estimate
gasUnitPrice: 0, // Let simulation estimate
},
});
const [simulation] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction,
options: {
estimateGasUnitPrice: true,
estimateMaxGasAmount: true,
estimatePrioritizedGasUnitPrice: true,
},
});
return {
estimatedGasUsed: simulation.gas_used,
estimatedGasUnitPrice: simulation.gas_unit_price,
estimatedMaxGasAmount: simulation.max_gas_amount,
estimatedTotalCost: BigInt(simulation.gas_used) * BigInt(simulation.gas_unit_price),
success: simulation.success,
vmStatus: simulation.vm_status,
};
}
// Batch simulation for multiple transactions
async function batchSimulate(
sender: Account,
payloads: any[]
) {
const simulations = await Promise.all(
payloads.map(async (payload) => {
const transaction = await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
});
const [result] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction,
});
return {
payload,
success: result.success,
gasUsed: result.gas_used,
error: result.success ? null : result.vm_status,
};
})
);
return simulations;
}
from aptos_sdk.client import RestClient
from aptos_sdk.account import Account
from aptos_sdk.account_address import AccountAddress
from aptos_sdk.transactions import EntryFunction, TransactionArgument
client = RestClient("https://api-aptos-mainnet.n.dwellir.com/v1/YOUR_API_KEY")
def simulate_transaction(sender: Account, payload):
"""Simulate a transaction to estimate gas and check validity"""
# Simulate the transaction
simulation_results = client.simulate_transaction(
sender,
payload,
estimate_gas_unit_price=True,
estimate_max_gas_amount=True,
estimate_prioritized_gas_unit_price=False
)
result = simulation_results[0]
return {
"success": result.get("success", False),
"gas_used": result.get("gas_used", "0"),
"gas_unit_price": result.get("gas_unit_price", "0"),
"max_gas_amount": result.get("max_gas_amount", "0"),
"vm_status": result.get("vm_status", ""),
"events": result.get("events", []),
"changes": result.get("changes", [])
}
def simulate_transfer(sender: Account, recipient: str, amount: int):
"""Simulate an APT transfer"""
payload = EntryFunction.natural(
"0x1::aptos_account",
"transfer",
[],
[
TransactionArgument(
AccountAddress.from_str(recipient),
TransactionArgument.ADDRESS
),
TransactionArgument(amount, TransactionArgument.U64),
],
)
return simulate_transaction(sender, payload)
def validate_before_submit(sender: Account, payload):
"""Validate transaction will succeed before submission"""
simulation = simulate_transaction(sender, payload)
if not simulation["success"]:
raise Exception(f"Transaction will fail: {simulation['vm_status']}")
# Check gas requirements
gas_required = int(simulation["gas_used"])
gas_price = int(simulation["gas_unit_price"])
total_cost = gas_required * gas_price
# Get account balance
account_resources = client.account_resources(sender.address())
apt_resource = next(
(r for r in account_resources
if r["type"] == "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"),
None
)
if apt_resource:
balance = int(apt_resource["data"]["coin"]["value"])
if balance < total_cost:
raise Exception(f"Insufficient balance. Need {total_cost}, have {balance}")
return {
"valid": True,
"gas_estimate": gas_required,
"gas_price": gas_price,
"total_cost": total_cost
}
def batch_simulate_transactions(sender: Account, payloads: list):
"""Simulate multiple transactions"""
results = []
total_gas = 0
for i, payload in enumerate(payloads):
try:
simulation = simulate_transaction(sender, payload)
results.append({
"index": i,
"success": simulation["success"],
"gas_used": simulation["gas_used"],
"error": None if simulation["success"] else simulation["vm_status"]
})
total_gas += int(simulation["gas_used"])
except Exception as e:
results.append({
"index": i,
"success": False,
"error": str(e)
})
return {
"simulations": results,
"total_gas_estimate": total_gas,
"all_valid": all(r["success"] for r in results)
}
use aptos_sdk::{
rest_client::Client,
types::{
account_address::AccountAddress,
transaction::{EntryFunction, TransactionPayload},
},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(
"https://api-aptos-mainnet.n.dwellir.com/v1/YOUR_API_KEY"
.parse()?
);
// Simulate transaction
async fn simulate_transaction(
client: &Client,
sender: &Account,
payload: TransactionPayload,
) -> Result<SimulationResult, Box<dyn std::error::Error>> {
let simulation = client.simulate_transaction(
sender,
payload,
SimulationOptions {
estimate_gas_unit_price: true,
estimate_max_gas_amount: true,
..Default::default()
}
).await?;
Ok(SimulationResult {
success: simulation[0].success,
gas_used: simulation[0].gas_used,
gas_unit_price: simulation[0].gas_unit_price,
vm_status: simulation[0].vm_status.clone(),
})
}
Ok(())
}
Common Use Cases​
1. Gas Cost Estimation​
Estimate gas costs before showing to users:
async function estimateTransactionCost(
sender: Account,
payload: any
): Promise<{
gasUnits: string;
gasPrice: string;
totalAPT: number;
totalUSD: number;
}> {
const [simulation] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction: await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
}),
options: {
estimateGasUnitPrice: true,
estimateMaxGasAmount: true,
},
});
const gasUnits = simulation.gas_used;
const gasPrice = simulation.gas_unit_price;
const totalOctas = BigInt(gasUnits) * BigInt(gasPrice);
const totalAPT = Number(totalOctas) / 100_000_000; // 1 APT = 10^8 Octas
// Get APT price (implement your price feed)
const aptPriceUSD = await getAPTPrice();
const totalUSD = totalAPT * aptPriceUSD;
return {
gasUnits,
gasPrice,
totalAPT,
totalUSD,
};
}
2. Transaction Validation​
Validate complex transactions before submission:
async function validateComplexTransaction(
sender: Account,
payload: any
): Promise<{
valid: boolean;
errors: string[];
warnings: string[];
}> {
const errors: string[] = [];
const warnings: string[] = [];
try {
// Simulate transaction
const [simulation] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction: await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
}),
});
if (!simulation.success) {
errors.push(`Transaction will fail: ${simulation.vm_status}`);
}
// Check gas requirements
const gasRequired = BigInt(simulation.gas_used) * BigInt(simulation.gas_unit_price);
// Get account balance
const resources = await aptos.getAccountResources({
accountAddress: sender.accountAddress,
});
const aptResource = resources.find(
r => r.type === "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
);
const balance = BigInt(aptResource?.data?.coin?.value || 0);
if (balance < gasRequired) {
errors.push(`Insufficient balance for gas. Need ${gasRequired}, have ${balance}`);
} else if (balance < gasRequired * 2n) {
warnings.push("Low balance warning: Less than 2x gas cost remaining");
}
// Check for expected state changes
if (simulation.changes.length === 0) {
warnings.push("Transaction produces no state changes");
}
// Check events
const criticalEvents = simulation.events.filter(
e => e.type.includes("failure") || e.type.includes("error")
);
if (criticalEvents.length > 0) {
warnings.push(`Transaction emits ${criticalEvents.length} error events`);
}
} catch (error) {
errors.push(`Simulation failed: ${error.message}`);
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
3. State Change Preview​
Show users what will change:
async function previewStateChanges(
sender: Account,
payload: any
): Promise<{
balanceChanges: any[];
resourceChanges: any[];
events: any[];
}> {
const [simulation] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction: await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
}),
});
const balanceChanges = [];
const resourceChanges = [];
// Parse write set changes
for (const change of simulation.changes) {
if (change.type === "write_resource") {
const resourceType = change.data.type;
if (resourceType.includes("CoinStore")) {
// Extract coin balance change
const oldValue = change.data.old_value?.coin?.value || "0";
const newValue = change.data.data?.coin?.value || "0";
const diff = BigInt(newValue) - BigInt(oldValue);
balanceChanges.push({
address: change.address,
coinType: resourceType,
oldBalance: oldValue,
newBalance: newValue,
change: diff.toString(),
});
} else {
resourceChanges.push({
address: change.address,
type: resourceType,
changeType: change.data.old_value ? "modified" : "created",
});
}
}
}
return {
balanceChanges,
resourceChanges,
events: simulation.events,
};
}
4. Multi-Step Transaction Planning​
Simulate a sequence of dependent transactions:
async function simulateTransactionSequence(
sender: Account,
steps: Array<{name: string; payload: any}>
): Promise<any[]> {
const results = [];
let sequenceNumber = BigInt(
(await aptos.getAccountInfo({
accountAddress: sender.accountAddress,
})).sequence_number
);
for (const step of steps) {
const transaction = await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: step.payload,
options: {
accountSequenceNumber: sequenceNumber,
},
});
const [simulation] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction,
});
results.push({
step: step.name,
success: simulation.success,
gasUsed: simulation.gas_used,
error: simulation.success ? null : simulation.vm_status,
});
if (!simulation.success) {
// Stop simulation if a step fails
break;
}
sequenceNumber++;
}
return results;
}
Error Analysis​
Parse Simulation Errors​
function parseSimulationError(vmStatus: string): {
code: string;
description: string;
suggestion: string;
} {
const errorMap: Record<string, {description: string; suggestion: string}> = {
"SEQUENCE_NUMBER_TOO_OLD": {
description: "Transaction sequence number is outdated",
suggestion: "Refresh account state and retry",
},
"INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE": {
description: "Not enough APT to pay for gas",
suggestion: "Add more APT to your account",
},
"TRANSACTION_EXPIRED": {
description: "Transaction expiration time has passed",
suggestion: "Create a new transaction with updated expiration",
},
"INVALID_SIGNATURE": {
description: "Transaction signature is invalid",
suggestion: "Check your signing key and retry",
},
"FUNCTION_NOT_FOUND": {
description: "The called function does not exist",
suggestion: "Verify the module and function names",
},
};
const errorInfo = errorMap[vmStatus] || {
description: "Unknown error occurred",
suggestion: "Check transaction parameters",
};
return {
code: vmStatus,
...errorInfo,
};
}
Performance Optimization​
Simulation Caching​
class SimulationCache {
private cache = new Map<string, any>();
private ttl = 10000; // 10 seconds
private getKey(sender: string, payload: any): string {
return `${sender}:${JSON.stringify(payload)}`;
}
async simulate(
sender: Account,
payload: any,
forceRefresh = false
) {
const key = this.getKey(sender.accountAddress.toString(), payload);
const cached = this.cache.get(key);
if (!forceRefresh && cached && Date.now() - cached.timestamp < this.ttl) {
return cached.result;
}
const transaction = await aptos.transaction.build.simple({
sender: sender.accountAddress,
data: payload,
});
const [result] = await aptos.transaction.simulate.simple({
signerPublicKey: sender.publicKey,
transaction,
});
this.cache.set(key, {
result,
timestamp: Date.now(),
});
return result;
}
}
Need help? Contact our support team or check the Aptos documentation.