suideveloper
All questions
Transactions

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:

Terminal
sui client call \
  --package 0x<PACKAGE_ID> \
  --module counter \
  --function increment \
  --args 0x<COUNTER_ID> \
  --dry-run

The same flag works for PTBs and for publishing:

Terminal
sui client ptb \
  --split-coins gas "[1000000]" \
  --assign coin \
  --transfer-objects "[coin]" @0x<RECIPIENT> \
  --dry-run

sui client publish --dry-run

Related flags:

  • --dev-inspect runs the transaction in dev-inspect mode, which is more permissive and is useful for reading return values from functions.
  • sui client ptb --preview only prints the PTB commands. It doesn't contact the network.
  • --serialize-unsigned-transaction prints base64 transaction bytes without executing. You can feed these to sui client serialized-tx <TX_BYTES> --dry-run later.

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:

TypeScript
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: false turns off transaction validation, so you can inspect non-public, non-entry functions. This replaces the old JSON-RPC devInspectTransactionBlock for 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.gas is paid by the fake coin rather than the sender. The result's gasPaymentMocked field 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 SimulationError if that simulation fails. You often see a failure at build() or signAndExecuteTransaction() 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

More on transactions