Skip to content

Latest commit

 

History

History
373 lines (284 loc) · 13.8 KB

File metadata and controls

373 lines (284 loc) · 13.8 KB

Simulate, Decode & Replay

Simulate

Simulate a Solana transaction locally using LiteSVM (simulate, alias: sim). sonar simulate --help groups options into Input & RPC, State Preparation, Simulation Controls, and Output & Debug.

# Simulate a transaction with raw Base58/Base64 data
sonar simulate <BASE58_OR_BASE64_STRING> --rpc-url https://api.mainnet-beta.solana.com

# Simulate using transaction signature (auto-detected)
sonar simulate 2gTzNX3zLNhhmJaY44LycEgF8UMadrKeDLHz8rgcQVbXWVU4bs8fLBzWKhvAqKBeo2ttqyXsCeqUW47dfW6775Wu \
  --rpc-url https://api.mainnet-beta.solana.com

# Bundle simulation (multiple transactions)
sonar simulate <TX1> <TX2> <TX3> --rpc-url https://api.mainnet-beta.solana.com

# Instruction input mode (one synthesized transaction)
sonar simulate --payer <PAYER_PUBKEY> \
  --ix 'program=MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr data=0x68656c6c6f'

# Read transaction from stdin (omit TX to read from pipe)
cat ./transaction.txt | sonar simulate --rpc-url <RPC_URL>

# Show balance changes and instruction details
sonar simulate <TX> --rpc-url <RPC_URL> -b -d

# JSON output
sonar simulate <TX> --rpc-url <RPC_URL> --json

Instruction Input

Use --ix to simulate one or more raw instructions without first building a signed transaction. Sonar creates one unsigned legacy transaction and runs the normal simulation pipeline. --payer is optional: when omitted, sonar uses a deterministic placeholder pubkey (sha256("sonar-payer")) auto-funded with 1 SOL for the run; pass --payer <PUBKEY> to override.

--ix auto-detects each value's format, so you can mix DSL, JSON, and files freely across repeated flags (they apply in CLI order):

  • named-field DSL — anything that isn't JSON or a file path
  • inline JSON — values starting with { or [
  • @<path> — read from a file; the file's contents may themselves be either JSON or DSL. ~ is expanded and @/dev/stdin works for piping.
# Single instruction via DSL. `data` is hex and may start with 0x.
sonar simulate --payer <PAYER_PUBKEY> \
  --ix 'program=<PROGRAM_ID> accounts=<ACCOUNT>:sw data=0x01020304'

# Multiple instructions in one atomic transaction.
sonar simulate --payer <PAYER_PUBKEY> \
  --ix 'program=<PROGRAM_A> data=0x01' \
  --ix 'program=<PROGRAM_B> accounts=<ACCOUNT>:w data=0x02'

# Inline JSON when structured input is easier to generate.
sonar simulate --payer <PAYER_PUBKEY> \
  --ix '{"program":"<PROGRAM_ID>","accounts":[{"pubkey":"<ACCOUNT>","is_signer":true,"is_writable":true}],"data":"0x01020304"}'

# Base64 / base58 data via the encoding field (works in DSL and JSON).
# base58 matches how Solana RPC encodes compiled-instruction data.
sonar simulate --payer <PAYER_PUBKEY> \
  --ix 'program=<PROGRAM_ID> data=AQIDBA== encoding=base64'
sonar simulate --payer <PAYER_PUBKEY> \
  --ix '{"program":"<PROGRAM_ID>","data":"<BASE58_DATA>","encoding":"base58"}'

# Read from a file (curl-style `@` prefix). `@/dev/stdin` works for piping.
sonar simulate --payer <PAYER_PUBKEY> --ix @instructions.json

Instruction data is decoded as hex by default in both forms; set the optional encoding field to hex, base64, or base58 to choose otherwise. A leading 0x/0X is accepted (and stripped) for hex; it is not a format switch, so base64/base58 must be selected explicitly via encoding.

DSL fields (the non-JSON, non-@ form):

  • program (or program_id): program pubkey. Required.
  • accounts: optional comma-separated account metas. Account flags are s (signer) and w (writable). Omit the :flags suffix for a read-only non-signer.
  • data: optional instruction data. Decoded per encoding (default hex). Empty data via omitting the field, or data=0x.
  • encoding: optional hex (default), base64, or base58.

JSON fields (inline or from an @<path> file):

  • program (or program_id): program pubkey.
  • accounts: optional ordered account metas. Each account requires pubkey, is_signer, and is_writable — same shape as Solana's AccountMeta so an existing meta object copies over verbatim.
  • data: optional instruction data. Decoded per encoding (default hex). Empty data via omitting the field, "", or "0x".
  • encoding: optional "hex" (default), "base64", or "base58".

Program & Account Override

Override on-chain programs or accounts with local files for testing:

# Override a program with a local .so file
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --override TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA=./custom_token.so

# Override an account with a local .json file
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --override <PUBKEY>=./account.json

Account Funding

Fund system accounts with SOL or token accounts for testing:

# Fund a single account with SOL
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --fund-sol DPLezAkFZ5sFaBXMWt3J2StQwYtcqecUipWSP7YfrLth=10.5sol

# Fund multiple accounts
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --fund-sol <PUBKEY1>=100.0sol \
  --fund-sol <PUBKEY2>=2.75sol

# Fund a token account (raw amount)
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --fund-token <TOKEN_ACCOUNT>=1000000

# Fund a token account with explicit mint (mint auto-detected if account exists on-chain)
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --fund-token <ACCOUNT>:<MINT>=1000000

# Fund a new token account with explicit mint and owner
# Owner is required when the token account does not already exist on-chain
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --fund-token <ACCOUNT>:<MINT>:<OWNER>=1000000

# Fund using decimal amount (uses mint decimals, e.g. 1.5 USDC = 1500000 raw units)
sonar simulate <TX> \
  --rpc-url https://api.mainnet-beta.solana.com \
  --fund-token <TOKEN_ACCOUNT>=1.5

Patching & State Manipulation

# Patch an account inside instruction 2, account 3
# Format: <IX>.<ACCOUNT>=<NEW_PUBKEY>[:w] with 1-based indices
# Account flags follow the same grammar as `--ix accounts=`: append `:w` for
# writable, omit the suffix for read-only (the default). The signer flag `s` is
# rejected here — declare signers when building the instruction with `--ix`.
sonar simulate <TX> --rpc-url <RPC_URL> \
  --patch-ix-account 2.3=<NEW_PUBKEY>:w

# Insert an account at a specific position within instruction 1's account list
# Format: <IX>.<POSITION>=<PUBKEY>[:w] with 1-based indices
# Existing accounts at and after POSITION shift right by one.
# POSITION may equal current_count + 1 to insert at the end (push semantics).
sonar simulate <TX> --rpc-url <RPC_URL> \
  --insert-ix-account 1.3=<PUBKEY>:w

# Remove an account at a specific position from instruction 1's account list
# Format: <IX>.<POSITION> with 1-based indices
# Subsequent accounts shift left by one. The static account_keys table is left
# intact (unreferenced keys remain, mirroring --patch-ix-account behavior).
sonar simulate <TX> --rpc-url <RPC_URL> \
  --remove-ix-account 1.4 \
  --remove-ix-account 1.2

# Ordering note for instruction-account ops:
# Ops apply in flag order (all patches → all inserts → all removes); within each
# flag, CLI argument order is preserved. Positions are interpreted at apply time
# (not against the original list). To express positions relative to the
# pre-mutation list, list ops in descending position order — e.g. above we
# remove position 4 before position 2 so both refer to the original numbering.

#### Whole-instruction insert / remove

```bash
# Remove a whole instruction (1-based index). Subsequent instructions shift
# left by one. The account_keys table is left intact (unreferenced keys remain
# loadable, mirroring --remove-ix-account).
sonar simulate <TX> --rpc-url <RPC_URL> --remove-ix 2

# Insert a whole instruction at a 1-based position. POS=1 prepends;
# POS = current_count + 1 appends. The new instruction becomes the POS-th.
# <SPEC> reuses the --ix grammar: DSL, JSON object/array, or @file.
sonar simulate <TX> --rpc-url <RPC_URL> \
  --insert-ix 1='program=MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr data=0x6869'

# A JSON array spec expands to consecutive positions starting at POS:
sonar simulate <TX> --rpc-url <RPC_URL> \
  --insert-ix 2=@extra_instructions.json

# Signer accounts are supported: new signers are inserted into the message's
# signer section (with a placeholder signature), and the header's signer counts
# are bumped accordingly. An account already present as a non-signer cannot be
# promoted to signer in the same op.
#
# Privilege semantics: Solana message privileges are the union across all
# instructions, so referencing an existing account never weakens it. A key that
# is already writable stays writable even if this instruction lists it
# read-only, and a key already a signer stays a signer. Inserted references can
# only PROMOTE an existing key (e.g. readonly -> writable); they never demote
# it, so existing instructions keep the privileges they need.

# Accounts the inserted instruction introduces that weren't in the original
# transaction are fetched/dependency-resolved automatically before simulation.

# Ordering note for whole-instruction ops:
# They run as a restructuring phase BEFORE account-level ops (--patch-ix-account
# etc.) and data patches (--patch-ix-data), so the latter target the
# post-restructure list. Within the phase, all --insert-ix apply first (in CLI
# order) then all --remove-ix. Positions are interpreted at apply time; to
# express positions relative to the pre-mutation list, list ops in descending
# position order.

Patch instruction data before simulation

Format: =:<HEX_DATA> with a 1-based instruction index

HEX_DATA may optionally start with 0x

sonar simulate --rpc-url <RPC_URL>
--patch-ix-data 1=8:0x01020304

Patch account data before simulation

sonar simulate --rpc-url <RPC_URL>
--patch-account-data =:<HEX_DATA>

Close an account so it does not exist during simulation

sonar simulate --rpc-url <RPC_URL>
--close-account


### Cache & Offline Replay

```bash
# Cache accounts for offline replay
sonar simulate <TX> --rpc-url <RPC_URL> --cache

# Replay from cache (no network; uses ~/.sonar/cache/ when cache is complete)
sonar simulate <TX> --cache

# Force refresh cache
sonar simulate <TX> --rpc-url <RPC_URL> --cache --refresh-cache

Simulation Controls

# Override clock timestamp and slot (Unix or RFC3339)
sonar simulate <TX> --rpc-url <RPC_URL> \
  --timestamp 1700000000 --slot 250000000
sonar simulate <TX> --rpc-url <RPC_URL> \
  --timestamp 2024-01-01T00:00:00Z --slot 250000000

# Verify transaction signatures during simulation
sonar simulate <TX> --rpc-url <RPC_URL> --check-sig

# Load Anchor IDL files from a custom directory
sonar simulate <TX> --rpc-url <RPC_URL> --idl-dir /path/to/idl/files/

Output & Debug

# Always print raw instruction data, even when parser succeeds
sonar simulate <TX> --rpc-url <RPC_URL> --raw-ix-data

# Print raw logs and full instruction details
sonar simulate <TX> --rpc-url <RPC_URL> --raw-log --show-ix-detail

Decode

Decode and display a raw transaction without simulation (decode, alias: dec):

sonar decode <TX> --rpc-url https://api.mainnet-beta.solana.com
sonar decode <TX> --rpc-url <RPC_URL> --json

# Bundle decode (multiple TXs): --json outputs a single JSON array [{...}, {...}]
# Parseable by jq: sonar decode <TX1> <TX2> --json --rpc-url <RPC_URL> | jq .
sonar decode <TX1> <TX2> --rpc-url <RPC_URL> --json

# Read transaction from stdin (omit TX to read from pipe)
cat ./transaction.txt | sonar decode --rpc-url <RPC_URL>

# Always print raw instruction data, even when parser succeeds
sonar decode <TX> --rpc-url <RPC_URL> --raw-ix-data

Decode Cache Controls

# decode also uses cache by default (signature/raw-tx resolution + account loading)
sonar decode <TX_OR_SIGNATURE> --rpc-url <RPC_URL>

# --no-cache disables account cache but still allows cached raw-tx reuse for signatures
sonar decode <TX_OR_SIGNATURE> --rpc-url <RPC_URL> --no-cache

# --refresh-cache bypasses both raw-tx/account cache and forces RPC
sonar decode <TX_OR_SIGNATURE> --rpc-url <RPC_URL> --refresh-cache

sonar decode <TX_OR_SIGNATURE> --rpc-url <RPC_URL> --cache-dir /path/to/cache

Replay

Replay a confirmed transaction. Fetches it by signature from RPC and reconstructs the execution trace from on-chain metadata (no local simulation).

# Replay a confirmed transaction
sonar replay <SIGNATURE> --rpc-url https://api.mainnet-beta.solana.com

# Show balance changes and instruction details
sonar replay <SIGNATURE> --rpc-url <RPC_URL> -b -d

# JSON output
sonar replay <SIGNATURE> --rpc-url <RPC_URL> --json

# Print raw logs instead of structured output
sonar replay <SIGNATURE> --rpc-url <RPC_URL> --raw-log

# Always print raw instruction data, even when parser succeeds
sonar replay <SIGNATURE> --rpc-url <RPC_URL> --raw-ix-data

# Load Anchor IDL files from a custom directory
sonar replay <SIGNATURE> --rpc-url <RPC_URL> --idl-dir /path/to/idls

Cache Management

Manage cached account data for offline simulation:

# Cache accounts for offline replay (writes to ~/.sonar/cache/ by default)
sonar simulate <TX> --rpc-url <RPC_URL> --cache

# Replay from cache (no network access when cache is complete)
sonar simulate <TX> --cache

# Force refresh cache
sonar simulate <TX> --rpc-url <RPC_URL> --refresh-cache

# Custom cache directory (implies --cache)
sonar simulate <TX> --rpc-url <RPC_URL> --cache-dir /path/to/cache

# Manage cache
sonar cache list
sonar cache clean --older-than 7d
sonar cache info <KEY>

Cache metadata schema (_meta.json) uses a transactions array:

{
  "type": "single",
  "transactions": [
    { "input": "<original input>", "raw_tx": "<base64 tx>", "resolved_from": "raw_input|cache|rpc" }
  ]
}