How do I simulate a transaction before executing it?
November 12, 2026
Sui full nodes can execute a transaction against current chain state without committing it. From the CLI, add --dry-run to any transaction command (sui client call, sui client ptb, sui client publish and so on). From TypeScript, call simulateTransaction() on a SuiGrpcClient or SuiGraphQLClient. The result has the same effects, events, balance changes and gas cost you would get from real execution, and nothing is signed, charged or written.
Dry run from the CLI
Every sui client command that builds a transaction takes the same processing flags. --dry-run simulates the transaction instead of executing it:
sui client call \
--package 0x<PACKAGE_ID> \
--module counter \
--function increment \
--args 0x<COUNTER_ID> \
--dry-runThe same flag works for PTBs and for publishing:
sui client ptb \
--split-coins gas "[1000000]" \
--assign coin \
--transfer-objects "[coin]" @0x<RECIPIENT> \
--dry-run
sui client publish --dry-runRelated flags:
--dev-inspectruns the transaction in dev-inspect mode, which is more permissive and is useful for reading return values from functions.sui client ptb --previewonly prints the PTB commands. It doesn't contact the network.--serialize-unsigned-transactionprints base64 transaction bytes without executing. You can feed these tosui client serialized-tx <TX_BYTES> --dry-runlater.
If you leave out --gas-budget, the CLI already runs a dry run first to estimate the budget, then executes. That's why a broken transaction often fails with "Dry run failed, could not automatically determine a budget" before anything is sent.
Simulate from TypeScript
In @mysten/sui v2, every client has simulateTransaction. It accepts a Transaction or built bytes, and returns a discriminated union just like execution does:
import { SuiGrpcClient } from '@mysten/sui/grpc'
import { Transaction } from '@mysten/sui/transactions'
const client = new SuiGrpcClient({
network: 'testnet',
baseUrl: 'https://fullnode.testnet.sui.io:443',
})
const tx = new Transaction()
tx.setSender(myAddress)
tx.moveCall({
target: `${PACKAGE_ID}::counter::increment`,
arguments: [tx.object(COUNTER_ID)],
})
const result = await client.simulateTransaction({
transaction: tx,
include: { effects: true, balanceChanges: true, events: true },
})
if (result.$kind === 'FailedTransaction') {
console.error('Would fail:', result.FailedTransaction.status.error)
} else {
console.log('Gas:', result.Transaction.effects.gasUsed)
console.log('Balance changes:', result.Transaction.balanceChanges)
}Simulation-only options:
include: { commandResults: true }returns each command's return values and mutated references as BCS bytes, so you can read the output of a Move function without executing anything.checksEnabled: falseturns off transaction validation, so you can inspect non-public, non-entry functions. This replaces the old JSON-RPCdevInspectTransactionBlockfor most uses.
The JSON-RPC methods sui_dryRunTransactionBlock and sui_devInspectTransactionBlock belong to the deprecated JSON-RPC API, which public mainnet full nodes no longer serve. Use gRPC or GraphQL (which has a simulateTransaction query) for new code.
Gotchas
- Mocked gas. If the transaction has no gas payment, the node simulates with a mocked gas coin. The simulation still checks the logic and estimates gas, but anything drawn from
tx.gasis paid by the fake coin rather than the sender. The result'sgasPaymentMockedfield tells you which case ran. To simulate with the sender's real coins, set gas payment on the transaction. - State can change. A simulation reflects state at the moment it runs. A shared object can change before you execute, so a passing dry run doesn't guarantee success.
- Building already simulates. When the SDK builds a transaction without an explicit gas budget, it simulates it to set the budget and throws a
SimulationErrorif that simulation fails. You often see a failure atbuild()orsignAndExecuteTransaction()before anything reaches the chain.
To learn how to read a failure, see why did my transaction fail. For budgets, see how gas budgets work. You can also build transactions against testnet in the playground.
Sources
- Executing Transactions: simulateTransaction, Sui TypeScript SDK docs
- Sui Client CLI, Sui documentation
- Sui Client PTB CLI, Sui documentation
client_commands.rs(TxProcessingArgs), MystenLabs/sui on GitHub