Skip to content

Latest commit

 

History

History
929 lines (794 loc) · 54.9 KB

cli.md

File metadata and controls

929 lines (794 loc) · 54.9 KB

CLI Command Reference

The sdkt-cli crate uses clap (derive API) for command routing. Every command returns Result<(), Box<dyn std::error::Error>>; library errors (RpcError, DecodeError, WasmError) bubble up and are printed via eprintln! with a non-zero exit.

Command Tree

sdkt
├── decode <xdr>
│   ├── --type <ScVal|TransactionEnvelope|TransactionResult|ContractEvent>
│   ├── --format <json|pretty>
│   └── --file <path>
│
├── encode <TYPE:VALUE>...
│   │   (fully offline: encode typed values to base64 XDR)
│   │   Supports: u32, i32, u64, i64, timepoint, duration, u128, i128, bool,
│   │   string, symbol, bytes, address. Also json:<JSON> for composite values.
│
├── inspect <contract-id>
│   ├── --interface                 (render full interface instead of name-only ABI)
│   ├── --format <json|pretty>
│   ├── --abi <wasm>                (ABI-aware storage decode)
│   └── --abi-contract <id>         (fetch deployed contract's ABI)
│
├── storage
│   ├── check <contract-id>   [--abi <wasm>] [--abi-contract <id>] [--format]
│   ├── analyze <contract-id> [--abi <wasm>] [--abi-contract <id>] [--format]
│   ├── estimate <wasm-path>  [--ledgers <N>] [--format <pretty|json>]
│   ├── storage-diff --old <snapshot-a.json> --new <snapshot-b.json> [--extend-plan] [--format <pretty|json>]
│   ├── read --contract <contract-id> --key-xdr <BASE64_XDR> [--abi <wasm>] [--format]
│   ├── extend --contract <contract-id> --ledgers <N> [--key <xdr>]... [--identity <name>] [--timeout <s>] [--interval <s>] [--format]
│   └── restore --contract <contract-id> --envelope <xdr> [--dry-run] [--identity <name>] [--timeout <s>] [--interval <s>] [--format]
│
│   `estimate` performs an offline calculation of storage rent over a specified
│   ledger horizon (default 17,280 ledgers, ~1 day) using the contract spec ABI.
│   It computes a breakdown across Instance, Persistent, and Temporary classes
│   and a total in stroops and XLM.
│
│   `storage-diff` compares two storage snapshots (`--old` and `--new`, generated by
│   `sdkt storage analyze --format json`) and identifies removed, expiring, and unchanged
│   entries. With `--extend-plan`, it derives a non-mutating remediation plan (target contract,
│   affected keys, and suggested ledger horizon) for operator extension.
│
│   Snapshots recorded by `sdkt storage analyze --format json` include capture context:
│   - `latest_ledger`: the ledger sequence at capture time.
│   - `network`: the network identity (passphrase or network name) against which capture was performed.
│
│   When diffing snapshots:
│   - If both snapshots record a network and they differ, the diff is refused with an error
│     naming both networks (preventing cross-network skew from reading as state drift).
│   - If both snapshots record a ledger, TTLs are compared on a common basis by normalising
│     the old snapshot's TTLs by the ledger delta (`new_ledger - old_ledger`), ensuring normal
│     time passage between snapshots does not falsely classify entries as expiring soon.
│     The ledger delta is surfaced in both pretty and JSON output.
│   - Backward compatibility: legacy snapshot files omitting capture context remain fully
│     supported; their capture context is reported as "unknown".
│   - Value drift detection: `sdkt storage analyze --format json` records each entry's
│     base64 XDR `ScVal` value. When two snapshots both carry values for the same entry,
│     `storage-diff` reports a `value_changed` delta (before/after values) instead of
│     classifying the entry as unchanged. An entry whose value and TTL both match stays
│     unchanged; TTL-only changes still report only TTL/rent deltas. Entries without
│     captured values (legacy snapshots, contract-code entries) are never flagged for
│     value drift.
│
│   `read` fetches a single ledger entry by its complete `LedgerKey` (base64 XDR).
│   The instance key is NOT included automatically — supply the full key via --key-xdr.
│   ABI formatting is optional and applies only to the returned value.
│
│   `extend` submits an `ExtendFootprintTtl` transaction. The contract instance
│   key is always included in the read-only footprint. Additional `--key` values
│   (base64 XDR or hex XDR) are merged and de-duplicated. This does NOT discover
│   all contract storage and does NOT restore archived entries.
│
│   `analyze` (alias `snapshot`) accepts repeatable `--key-xdr` (alias `--key`). Each
│   `getLedgerEntries` request is limited to 200 keys; larger read sets (including
│   the automatically included instance key) are fetched in sequential batches.
│   For batched reads, found entries retain request order and absent entries are
│   omitted. A failed batch or a change of ledger between batches fails the
│   complete read; retry if the ledger changes. This batching applies to reads,
│   not to the transaction footprint limits of `extend` or `restore`.
│
│   `restore` simulates `--envelope` (the invocation that failed on archived
│   state), adopts the `restorePreamble` footprint and minimum resource fee, and
│   submits a `RestoreFootprint` transaction. It fails without submitting when
│   the simulation has no preamble (state is live) or the preamble footprint is
│   empty or malformed. `--dry-run` prints the keys and fee without signing or
│   submitting. It restores only; re-run the invocation afterwards.
│
│   `--abi <wasm>` supplies the ABI from a local WASM; `--abi-contract <id>` fetches
│   the deployed contract's on-chain WASM and uses it as the ABI source
│   for storage decoding. The two flags are mutually exclusive.
│
├── invoke <CONTRACT_ID> <FUNCTION>
│   ├── --args <TYPE:VALUE>...    (same typed-args as `call` / `tx build`;
│   │                            strict: unknown types are rejected)
│   ├── --identity <name>        (signs and pays; default: "default")
│   ├── --timeout <SECONDS>      (wait-for-settlement timeout; default 60)
│   ├── --interval <SECONDS>     (poll interval while waiting; default 1)
│   ├── --format <json|pretty>
│   └── --network-profile <NAME> / --rpc-url <URL> / --network-passphrase <P>
│
│   Polling rules (shared by every command that waits for a transaction):
│   `--interval` must be greater than 0 and `--timeout` must be greater than
│   or equal to `--interval`; invalid pairs are rejected before any network
│   or identity work begins. `--no-wait` returns the transaction hash right
│   after submission and ignores both flags.
│
│   State-changing end-to-end flow in one command:
│     fetch account sequence → simulate → build final envelope (authoritative
│     footprint + fees + auth entries from simulation) → sign with the local
 main
│   Result decoding is limited to the transaction-level `TransactionResult`
│   XDR (auto-detection also recognizes `ScVal`, `TransactionEnvelope`,
│   `TransactionResult`, and `ContractEvent`; there is no XDR
│   `TransactionStatus` type in the pinned `stellar-xdr` 28.0.0 API).
│   Inherits the mainnet safety guard
│   (see below). Live Testnet smoke test is documented but NOT exercised in CI.
│
├── tx
│   ├── inspect <hash>        [--format]
│   ├── validate <xdr>        [--format] (offline parse + structural checks)
│   ├── decode <xdr>          [--format] (offline decode; human-readable transaction envelope)
│   ├── simulate <xdr>        [--format] [--abi <wasm>] [--abi-contract <id>] (RPC; surfaces restore preambles, costs, state changes; ABI-aware result decoding. `--abi-contract` fetches the deployed contract's on-chain WASM for decoding)
│   ├── sign                  [--input <xdr|file>] [--output <file>] [--identity <name>] [--network <testnet|mainnet|futurenet|custom:<p>>] [--format] (offline ED25519 signing)
│   ├── submit <xdr>          [--wait] [--timeout <s>] [--interval <s>] [--format] (RPC)
│   └── build                 [--source --sequence --contract --function --fee* --arg* --output --memo-text <text> --memo-id <id>]
│                             Offline by default: the envelope carries only the
│                             base inclusion fee (100 stroops) and prints a
│                             warning, because a Soroban submission also needs
│                             the resource fee.
│                             Pass the network flags on `tx` (e.g.
│                             `sdkt tx --network-profile testnet build ...`) to
│                             simulate the invocation and adopt the reported
│                             `minResourceFee` and footprint, exactly as
│                             `invoke` does — the resulting envelope submits
│                             without re-simulating.
│                             `--fee <STROOPS>` overrides both and costs no
│                             network round trip.
│                             `--memo-text <TEXT>` (at most 28 bytes) or
│                             `--memo-id <ID>` attaches a Stellar memo to the
│                             built envelope; the two flags are mutually
│                             exclusive. Omit both for no memo (unchanged
│                             default).
│
├── events <contract-id>
│   ├── --format <json|pretty>
│   ├── --start-ledger <START_LEDGER> (start ledger sequence number for event search)
│   ├── --end-ledger <END_LEDGER>     (end ledger sequence number for event search)
│   ├── --topic <SYMBOL>              (filter by first topic/event name; server-side filtering)
│   ├── --abi <wasm>                  (ABI-aware decode from a local WASM)
│   ├── --abi-contract <id>           (ABI-aware decode using deployed contract's on-chain WASM)
│   ├── --network-profile <NAME> / --rpc-url <URL> / --network-passphrase <P>
│   └── [other network resolution flags]
│
├── account <address>         [--format]
│
├── fee
│   └── estimate              (manual value entry, type-prefixed)
│
├── wasm
│   ├── inspect <file.wasm>  Offline inspection of a local WASM file (sections, exports, spec)
│   ├── metadata --contract <contract>  [--network testnet] [--refresh] [--format]
│   └── cache                 (info | remove | clear)
│
├── verify --contract <contract-id>
│   ├── --wasm <file.wasm>    (local artifact to compare; offline hashed)
│   ├── --network <testnet>   (RPC network)
│   ├── --format <json|pretty>
│   └── --upgrade-safety      (compare the live deployed contract's interface
│                               against --wasm candidate; emits UpgradeVerdict.
│                               Requires --wasm. Read-only; inherits mainnet-safety
│                               guard.)
│
├── deployment-verify --contract <contract-id>
│   ├── --wasm <file.wasm>    (local artifact to compare; offline hashed)
│   ├── --network <testnet>   (RPC network; conflicts with --rpc-url)
│   └── --format <json|pretty>
│
│   Read-only. Verdicts: MATCH (exit 0), DRIFT (exit 1), UNKNOWN (exit 1,
│   reason given), NOT_FOUND (exit 1). Probes the raw contract-instance
│   ledger entry — no inspection shortcuts, no bytecode download. A
│   `stellar_asset` executable has no WASM artifact, so it is UNKNOWN, not
│   a match. Never signs, submits, or deploys.
│
├── health --contract <contract-id>
│   ├── --wasm <file.wasm>    (optional local artifact to verify against)
│   ├── --network <testnet>   (RPC network / report label)
│   └── --format <json|pretty>
│
├── diff
│   ├── --old-wasm <A>
│   ├── --new-wasm <B>
│   ├── --format <json|pretty>
│   ├── --upgrade-safety      (emit UpgradeVerdict; exclusive with size policy)
│   ├── --max-growth-pct <N>  (fail when growth over the old artifact exceeds N%)
│   └── --max-size-bytes <N>  (fail when the new artifact exceeds N bytes)
│
├── audit [path.rs]
│   ├── --list-rules          (list available audit rules and exit)
│   ├── --format <json|pretty>
│   ├── --disable <RULE_ID>   (repeatable)
│   ├── --rules <PATH|ID>     (repeatable; compiled plugin artifact or installed plugin ID)
│   ├── --no-plugins          (skip loading installed plugins)
│   ├── --abi <wasm>          (path to contract WASM for spec-correlated analysis)
│   ├── --abi-contract <id>   (contract ID whose deployed WASM provides ABI)
│   ├── --fail-on <SEVERITY>  (exit non-zero if findings at or above this level; critical|warning|info)
│   └── --network-profile <NAME> / --rpc-url <URL> / --network-passphrase <PASS>
├── release-assurance
│   ├── --wasm <WASM>         (candidate artifact; required)
│   ├── --previous-wasm <WASM> (baseline for the offline upgrade diff)
│   ├── --audit <PATH>...     (Rust source path(s)/dir(s); same engine as `audit`)
│   ├── --disable <RULE_ID>   (repeatable)
│   ├── --contract <ID>       (deployed contract for on-chain checks; skipped if omitted)
│   ├── --network <testnet|mainnet|futurenet> (network for the on-chain checks; with --contract)
│   ├── --fuzz-evidence <EVIDENCE_JSON> (attach JSON evidence from sdkt fuzz campaign)
│   ├── --max-size-bytes <N>  (fail when the candidate exceeds N bytes)
│   ├── --max-growth-pct <N>  (fail when growth over --previous-wasm exceeds N%)
│   ├── --format <json|pretty>
│   ├── --network-profile <NAME> / --rpc-url <URL> / --network-passphrase <PASS>
│   └── [other flags inherited from network-profile resolution]
├── identity
│   ├── generate <name>       [--format pretty|json]
│   ├── import <name> <secret>   (required)
│   │   stdin form (recommended): import <name> -
│   │   ⚠ argv secrets are deprecated (issue #64): pass `-` instead
│   ├── list                  [--format pretty|json]
│   ├── show <name>           [--format pretty|json]
│   ├── delete <name>
│   ├── default <name>
│   └── fund <name>           [--network-profile <NAME>] [--format pretty|json]
│
├── init <name>              [--minimal] [--force] [--format]

├── network
│   ├── add <name>           [--rpc-url <URL>] [--passphrase <PASS>] [--friendbot <URL>] [--description <TEXT>]
│   ├── list
│   ├── show <name>          [--format json|pretty]
│   ├── check <name>         [--format json|pretty]
│   ├── diagnose [--network <testnet>] [--rpc-url <URL>] [--format json|pretty]
│   └── remove <name>
│
│   `diagnose` is read-only: getHealth/getNetwork/getLatestLedger plus two
│   Horizon GETs. Reports passphrase identity (match/mismatch/unknown),
│   protocol agreement across RPC and Horizon, the local sdkt version, and
│   the ledger resource limits Horizon actually observed
│   (base_fee_in_stroops, base_reserve_in_stroops, max_tx_set_size,
│   protocol_version) — `unavailable` with a reason when Horizon does not
│   answer. Limits are never hardcoded. Exit 0 only when status is `ok`;
│   identity mismatch, protocol inconsistency or an unreachable endpoint exit
│   1 with a valid report. Never signs, submits, or deploys.

├── build                     Compile Rust contracts in the workspace into WASM artifacts
│   └── --format <json|pretty>   (default: pretty)

├── lock                      Generate or inspect `sdkt.lock`
│   ├── generate              Write `sdkt.lock` from current build artifacts (run `sdkt build` first)
│   ├── verify                Verify `sdkt.lock` against on-disk contract artifacts AND package
│   │                         dependencies (lock matches manifest, git commits, path existence).
│   │                         Advisory and non-fatal: prints `✓ lock file verified` /
│   │                         `✓ package dependencies verified` or lists every drift.
│   └── show                  Print `sdkt.lock` contents

├── package                   Validate local package manifests
│   ├── validate              Offline-validate `[package]` metadata + local `[dependencies]`
│   │                         path graph (no network/registry; git/* sources rejected)
│   ├── fetch                 Fetch deps into `.sdkt-cache`: local path passthrough,
│   │                         git clone/checkout. `--force` updates. Never builds.
│   ├── update                Synchronize deps: refresh git deps to latest available
│   │                         commit and rewrite `sdkt.lock`. `rev` pinned; `tag`/`branch`
│   │                         update on drift. `--check` reports; `--dry-run` previews.
│   ├── pack                  Bundle the resolved project into a portable offline artifact
│   │                         manifest + lock + cached git checkouts. `--out`, `--format`.
│   └── publish               Validate publish readiness (`--dry-run` only, read-only);
│                             detects missing cache, lock drift, integrity mismatch.

### Synchronizing dependencies

`sdkt package update` closes the package loop: `validate → fetch → update → verify`.

It resolves each git dependency's **currently available** commit via `git ls-remote`
against its declared URL (no checkout, no clone in `--check`/`--dry-run`), then:

- **`rev`** — immutable and already pinned; never updated. Reported as `pinned (rev)`.
- **`tag`** — resolves the tag's current commit; if it differs from the lock, the
  cache is refreshed and the lock is rewritten.
- **`branch`** — fetches the latest branch head, updates the cache, and rewrites the
  lock.
- **local `path`** — no remote; reported `unchanged`.

Flags:

- `--check` — report available updates only; **does not** fetch or rewrite the lock;
  exits 0 (non-zero is reserved for hard errors like a missing lock or invalid manifest).
- `--dry-run` — compute everything and preview what would change; the cache and lock
  are left untouched.
- `--format pretty|json` — `json` emits `{"checked","updated","unchanged","changes":[...]}`.

On success the lock records the new `commit_sha`, `resolved_reference`, `cache_location`,
and `integrity` for git deps — contract entries, artifact hashes, and deploy order are
preserved. Clear errors are produced for: missing cache, git unavailable, invalid
manifest, missing lock, detached branch, unknown reference, and network failure.

### Version-constrained dependencies

A git dependency may declare an optional semver `version` constraint instead of a
fixed `tag` / `branch` / `rev`:

```toml
[dependencies.math]
git = "https://github.com/org/math"
version = ">=1.0, <2"

When version is set without an explicit ref, sdkt package fetch / sdkt package update resolve the highest remote tag that satisfies the constraint (via git ls-remote --tags; offline for local remotes) and materialize / lock that exact tag. --check reports constraint unsatisfied when no tag matches. An explicit tag / branch / rev always takes precedence and the constraint is ignored (mirroring how rev / path deps bypass version resolution). The lock records the resolved version for audit. This reuses the existing fetch / cache / lock infrastructure; the only new logic is a single pure VersionResolver.

Offline packaging & publish readiness

sdkt package pack bundles the fully resolved project into a portable, network-free artifact so it can be reconstructed and rebuilt on another machine without contacting any remote:

  • the manifest (.sdkt.toml),
  • the lockfile (sdkt.lock),
  • the cached git dependency checkouts under .sdkt-cache/git/<cache_key>.

Each artifact carries a package.json descriptor (PackageBundle) recording the package name/version, the chosen format, the sdkt.lock sha256, and a per-dependency entry (source, git_url, commit_sha, integrity, cache_key, version) so the bundle can be verified offline.

Flags:

  • --out <DIR> — output directory (default ./dist).
  • --format tar.zst|dir — tar.zst writes a compressed tarball <out>/<name>-<version>.tar.zst; dir writes a directory tree <out>/<name>-<version>/. Any other value is rejected with a clear error.

Pack preconditions: every git dependency declared in the manifest must have a cached checkout under .sdkt-cache/git/<cache_key>. If a declared dependency's checkout is missing, sdkt package pack exits non-zero with a git dependency '<name>' is declared but its checkout is not cached ... error and writes no descriptor — the bundle never claims a dependency it does not contain.

sdkt package publish --dry-run validates publish readiness using the existing manifest/lock/cache/integrity infrastructure. It detects missing cache, lock drift, integrity mismatch, commit mismatch, reference change, and invalid package state — all read-only, no network, nothing is published. --broadcast is explicitly opt-in and is rejected because no registry source is defined; the workflow remains fully offline.

Round-trip: a bundle can be reconstructed (sdkt_core::package::unpack) and the reconstructed tree verified to reproduce the original sdkt.lock sha256 and per-git-dependency integrity exactly (verify_bundle_equivalence) — no hashing or git logic is duplicated; the same compute_dependency_integrity / git_cache_key primitives are reused.

├── project │ └── deploy Deploy all contracts defined in the workspace (.sdkt.toml), │ applying topological dependency sorting │ │ --timeout <SECONDS> (default 60) and --interval <SECONDS> (default 1) │ control how long each contract deployment waits for settlement and how │ often it polls; both are validated before any network or identity work. │ │ Contracts declare dependencies via depends_on (canonical) or the │ legacy deploy_after field in [contracts.<alias>]; both are merged. Build, │ deploy, and sdkt lock generate share one resolver, so order is deterministic. │ Invalid graphs (unknown/self/duplicate dependency, cycle, duplicate name) │ fail fast with a clear error. │ │ Deployment records: every successfully deployed contract is written to │ .sdkt-deployments.json in the project directory, keyed by network scope │ and then alias: │ │ { │ "profiles": { │ "testnet": { │ "token": { │ "contract_id": "C…", │ "wasm_hash": "60cddae6…", │ "network": "testnet", │ "timestamp": 1700000000, │ "salt": "deploy" │ } │ } │ } │ } │ │ The scope key is the explicit --network-profile <NAME> when given, else the │ network derived from the passphrase (testnet/mainnet/futurenet) or a │ custom-<hash> for unknown passphrases — so testnet and mainnet deploys never │ collide. Records are persisted after every contract, so if a deploy fails │ mid-graph the contracts that already landed are never lost; the alias → │ contract ID mapping is retained for the retry. On success the record is written │ too (additive; the stdout JSON/pretty output is unchanged). │ │ --skip-deployed resumes an interrupted deploy: aliases whose recorded │ contract ID is verified to still exist on-chain (via getLedgerEntries) are │ skipped without re-deploying (and re-paying for) them, printed as │ ✓ '<alias>' already deployed at <contract_id>. A record whose contract no │ longer exists on-chain is NOT trusted — sdkt warns │ ⚠ Recorded contract for '<alias>' is no longer on-chain; re-deploying. and │ deploys it fresh, replacing the stale record. The default identity (see │ sdkt identity) is used to sign all deployments. │ │ Constructor arguments: contracts requiring initialization parameters at │ create time declare them in .sdkt.toml via the ctor_args field: │ │ [contracts.token] │ path = "contracts/token" │ ctor_args = ["address:G...", "u32:1000000"] │ │ Each string is a TYPE:VALUE pair (same format as sdkt deploy --arg: u32:100, │ address:G..., string:hello, bool:true, bytes:deadbeef, etc.) or a │ base64-encoded ScVal. Invalid arguments fail before WASM upload (fail-fast │ validation) with the same error messages as the single-contract deploy path. │ Contracts without ctor_args deploy with empty constructor arguments (no │ change from pre-feature behavior). │ ├── call │ ├── <CONTRACT_ID> │ ├── │ ├── --args TYPE:VALUE... (e.g. u32:100, address:G..., string:hello) │ ├── --abi (ABI-aware result decoding from a local WASM; │ │ without it the raw base64 XDR result is shown) │ ├── --abi-contract (ABI-aware result decoding using the deployed │ │ contract's on-chain WASM, fetched via the │ │ inspection path; no local artifact needed. │ │ Mutually exclusive with --abi.) │ ├── --format <json|pretty> │ └── --network-profile └── deploy ├── --wasm (upload WASM, then create the contract) ├── --wasm-hash (create-only from an already-uploaded 64-char │ hex WASM hash; mutually exclusive with --wasm) ├── --salt (optional; 40 hex chars, auto-generated if omitted) ├── --show-address (print the predicted address before submission; │ requires --wasm) ├── --dry-run (predict without submitting; requires --wasm) ├── --format <json|pretty> ├── --deny-breaking (abort if not backwards-compatible; requires --wasm) ├── --old-wasm (baseline, required by --deny-breaking) ├── --identity (signing identity; defaults to default) ├── --timeout (wait-for-settlement timeout for the upload and │ create transactions; default 60) ├── --interval (poll interval while waiting; default 1) ├── --arg TYPE:VALUE... (constructor arguments) └── --network-profile / --rpc-url / --network-passphrase

--wasm-hash skips the upload transaction and submits only contract creation, which is useful to resume after an upload succeeded but creation failed. Prediction-only options (--dry-run, --show-address) and --deny-breaking are rejected with --wasm-hash, because these operations require the local WASM bytes.

├── plugin Manage local audit plugins (install/remove/list/show/update) │ ├── init [--force] [--format] Scaffold a new audit rule crate │ ├── list [--format] List installed plugins │ ├── show [--format] Show metadata for an installed plugin │ ├── install [--id ] [--force] [--public-key ] [--format] │ │ Install from local artifact or .sdktplugin bundle │ ├── remove [--format] Remove an installed plugin (idempotent) │ ├── update [--format] Update from new local artifact │ ├── pack [--output ] [--secret-key ] [--format] │ │ Pack directory into .sdktplugin bundle │ ├── verify-bundle [--public-key ] [--format] │ │ Verify bundle integrity and signature │ └── doctor [<target|--all>] [--format] End-to-end diagnostics on a plugin │ ├── doctor Run diagnostic checks on sdkt environment and project │ ├── --format <json|pretty|--json> Output format (pretty is default) │ └── (offline: no network access required) │ ├── completions Generate shell completion scripts │ └── Supported shells: bash, zsh, fish, powershell, elvish │ ├── fuzz Soroban-aware deterministic fuzzing (offline, read-only) │ ├── campaign Run a deterministic fuzz campaign │ │ ├── --seed Deterministic seed (u64, default: 0) │ │ ├── --cases Number of test cases (default: 100) │ │ ├── --function Restrict to one exported function │ │ ├── --artifact-dir

Directory for artifacts (default: ./sdkt-fuzz-artifacts) │ │ ├── --auth Authorization mode: no-auth | correct-auth | wrong-auth │ │ ├── --expect-success Declare function must succeed (repeatable) │ │ ├── --expect-error FN:TYPE:CODE Declare function must fail (repeatable) │ │ ├── --replay Replay every written artifact to verify determinism │ │ ├── --format <pretty|json|jsonl> Output format (default: pretty) │ │ └── [network flags inherited from network-profile resolution] │ │ │ └── replay Replay a finding artifact to verify reproduction │ ├── --wasm Path to the campaign WASM │ └── (exit code: 0 = REPRODUCED, 1 = MISMATCH, 2 = usage error) │ ├── generate Generate typed clients and other artifacts │ └── client Generate typed Rust client from ContractSpec │ ├── --output Output file (prints to stdout if omitted) │ └── --skip-unsupported Skip unsupported functions instead of aborting


## Network Profiles

### Checking an RPC profile

`sdkt network check <name> [--format json]` performs read-only RPC checks against
the saved profile. It checks ledger reachability and node health, then asks the
endpoint for its network identity with `getNetwork`. The output shows the
profile's configured passphrase beside the endpoint-reported passphrase and
warns when they differ; an available Friendbot URL is shown as well. A mismatch
is advisory and does not change the profile or the command's exit status.

Some RPC providers do not implement `getNetwork`. In that case the command
still reports reachability, health, latest ledger, and protocol version from
the other RPC checks, and labels the identity fields as not reported. JSON
output keeps the existing `profile`, `rpc_url`, `reachable`, `status`,
`latest_ledger`, `protocol_version`, and `error` keys, and adds
`configured_passphrase`, nullable `endpoint_passphrase`, nullable
`friendbot_url`, and nullable `network_info_error`. Only an unreachable or
unhealthy endpoint causes a non-zero exit; failure of the optional identity
query does not.

Every RPC command (`inspect`, `verify`, `health`, `storage`, `events`, `account`,
`tx`, `fee`, `wasm`, `deploy`, `project deploy`) accepts the same three optional
flags for selecting / overriding the network endpoint:

| Flag | Meaning |
|------|---------|
| `--network-profile <NAME>` | Use a saved profile (see `sdkt network`). Loads its RPC URL + passphrase. |
| `--rpc-url <URL>` | Explicit RPC endpoint; overrides the profile and `.sdkt.toml`. |
| `--network-passphrase <PASSPHRASE>` | Explicit network passphrase; overrides the profile and `.sdkt.toml`. |

**Resolution precedence (highest wins):**

explicit --rpc-url / --network-passphrase > --network-profile > .sdkt.toml [network] > NetworkConfig::default() (testnet)


`tx sign` is excluded — it is offline signing and takes only `--network` for the
signature hash. Commands invoked without these flags behave exactly as before.

## Shell completions

`sdkt` can emit shell completion scripts for the major shells. This is the
fastest way to discover subcommands and flags.

```bash
sdkt completions bash       # bash
sdkt completions zsh        # zsh
sdkt completions fish       # fish
sdkt completions powershell # powershell
sdkt completions elvish     # elvish

Pipe the output to a file your shell reads at startup (see the README "Shell completions" section for per-shell install paths). Tab-completion then covers commands, subcommands, and flag names.

Doctor

sdkt doctor runs baseline diagnostic checks on the sdkt environment and the current project. It is fully offline and safe to run anywhere.

sdkt doctor              # human-readable output
sdkt doctor --json      # machine-readable output

Checks included in the core:

Check ID Meaning
sdkt runtime sdkt-runtime confirms the binary is running and reports its version
Rust toolchain rust-toolchain verifies cargo and rustc are on PATH
WASM build target wasm-target verifies a Soroban WASM target is installed via rustup
Project config project-config validates .sdkt.toml and the [contracts] dependency graph when inside a project

Exit codes:

  • 0 — all checks passed, or only warnings (non-fatal)
  • 1 — at least one check failed (e.g. invalid config, missing WASM target)

Warnings are never fatal. The JSON output contains no secret material.

Encode

sdkt encode converts a typed value to its base64 XDR representation — the write-direction counterpart to sdkt decode. It is fully offline and useful for building test fixtures, debugging, and CLI round-trips.

sdkt encode u32:100
# AAAAAwAAAGQ=

# Verify by decoding back
sdkt encode string:hello | xargs sdkt decode --type ScVal
# {"string": "hello"}

sdkt encode symbol:USD | xargs sdkt decode --type ScVal
# {"symbol": "USD"}

sdkt encode u128:340282366920938463463374607431768211455 | xargs sdkt decode --type ScVal
# {"u128": "340282366920938463463374607431768211455"}

sdkt encode i128:-1000 | xargs sdkt decode --type ScVal
# {"i128": "-1000"}

sdkt encode timepoint:1758000000 | xargs sdkt decode --type ScVal
# {"timepoint": "1758000000"}

sdkt encode duration:86400 | xargs sdkt decode --type ScVal
# {"duration": "86400"}

sdkt encode bytes:000aFF | xargs sdkt decode --type ScVal
# {"bytes": "000aff"}

sdkt encode json:'[1,2,3]' | xargs sdkt decode --type ScVal
# {"vec": [{"u32": 1}, {"u32": 2}, {"u32": 3}]}

Supported types (core subset)

u32, i32, u64, i64, timepoint, duration, u128, i128, bool, string, symbol (up to 32 bytes), bytes, address (Stellar G... strkey).

Exactly one value is encoded per invocation; the TYPE:VALUE syntax matches the typed-argument convention used by sdkt call and sdkt invoke.

u128 and i128 accept decimal integers within their full 128-bit ranges. Their decoded JSON values are decimal strings, preserving all digits. bytes accepts hexadecimal pairs without a 0x prefix, with either hex letter case. Surrounding whitespace is trimmed; empty input (bytes:) encodes empty bytes. Leading zero bytes are preserved, and decoded JSON uses lowercase hex. Pair parsing matches the runtime typed-argument parser, including its acceptance of a leading + in a pair (bytes:+f encodes 0f).

Composite values (json:)

json:<JSON> encodes one JSON value, including nested arrays and objects, as a single ScVal: json:[1,2,3] is one Vec, not three values. The mapping is: array to Vec, object to Map with String keys (sorted), null to Void, true/false to Bool, string to String, and an integer to the smallest fitting u32/u64 (i32/i64 when negative). Floats and integers beyond the 64-bit range are rejected. Invalid JSON fails with invalid JSON in 'json:<input>': <reason>.

Unsupported (clear failure)

Other types, such as vec:, map:, Option, Result, and UDTs, are rejected with an error listing the supported types (use json: for Vec and Map). Malformed values (bad or out-of-range numbers, invalid bools, invalid strkeys, odd-length or invalid hex) fail with a message naming the offending value.

Generate client

sdkt generate client produces deterministic, typed Rust call builders from the ContractSpec of a compiled Soroban contract WASM. The command is fully offline — it reads a local .wasm artifact and emits Rust source.

# Print the generated client to stdout
sdkt generate client target/wasm32v1-none/release/my_contract.wasm

# Write it to a file
sdkt generate client contract.wasm --output src/client.rs

# Generate a partial client, skipping unsupported functions
sdkt generate client contract.wasm --skip-unsupported --output src/client.rs

Output

Each contract function becomes a *Call struct with:

  • one typed field per parameter,
  • a NAME constant with the contract function name,
  • an args() method encoding the call as TYPE:VALUE strings — the same typed-argument convention accepted by sdkt call and sdkt invoke,
  • a <Function>Output type alias mirroring the return type.

The output is deterministic: the same input WASM always produces byte-identical source, suitable for checking generated files into a repo.

Supported types (core subset)

Parameters and single return values of these primitive types are supported: u32, i32, u64, i64, bool, address, string, symbol, void (no return).

Unsupported (clear failure)

By default, any other type — UDTs, Option, Result, Vec, Map, Tuple, BytesN, Val, multiple return values — aborts generation with an error naming the function, the type, and the position (parameter or return). Nothing partial is emitted. A WASM without a contractspecv0 section is rejected as "not a Soroban contract".

Partial generation (--skip-unsupported)

Pass --skip-unsupported to generate call builders for the supported subset of functions instead of aborting the entire command:

  • Supported functions are generated in original spec order.
  • Skipped functions and their specific reasons are listed deterministically in the generated file header comment.
  • If all functions in the contract are supported, output is identical to the default (no-flag) output.
  • If all functions in the contract are unsupported, valid non-crashing output is produced with an empty contract_functions() and an explanatory header.

Fuzz

sdkt fuzz provides Soroban-aware deterministic fuzzing for release assurance. It is fully offline and read-only: contracts execute in an isolated in-memory host environment without touching the network.

Campaign

sdkt fuzz campaign <wasm> runs a deterministic fuzz campaign against a compiled contract.

Syntax:

sdkt fuzz campaign contract.wasm \
  --seed 42 \
  --cases 100 \
  --function increment \
  --auth correct-auth \
  --artifact-dir ./sdkt-fuzz-artifacts \
  --expect-success increment \
  --expect-error invalid:Auth:6 \
  --replay \
  --format json

Arguments and flags:

Flag Default Meaning
<WASM> required Path to the compiled contract WASM file
--seed <N> 0 Deterministic seed (u64). Same seed produces identical test case sequence across runs
--cases <N> 100 Number of test cases to execute
--function <NAME> all Restrict the campaign to one exported contract function
--artifact-dir <DIR> ./sdkt-fuzz-artifacts Directory where finding artifacts are written
--auth <MODE> no-auth Authorization mode per test case: no-auth, correct-auth, or wrong-auth. See the sdkt-fuzz docs for exact semantics
--expect-success <FN> none Declare that function <FN> must succeed (repeatable). Violations produce RETURN_MISMATCH or UNEXPECTED_ERROR findings
--expect-error <FN:TYPE:CODE> none Declare that function <FN> must fail with error type <TYPE> and code <CODE>, e.g. --expect-error bump:Auth:6 (repeatable)
--replay false Replay every written artifact to verify determinism and reproduce findings
--format <FORMAT> pretty Output format: pretty, json, or jsonl

Expectation declarations:

  • --expect-success <FN> — declare that a function must always return successfully. If a case returns an error or value mismatch, it produces a finding.
  • --expect-error <FN:TYPE:CODE> — declare that a function must fail with a specific error. Format: FunctionName:ErrorType:ErrorCode. Example: --expect-error transfer:Auth:6 means the transfer function must fail with ContractError{Auth, 6}.
  • A function may have only one expectation; declaring it with both flags is a usage error (exit 2).
  • Malformed declarations are usage errors (exit 2).

Output artifacts:

Finding artifacts are written to --artifact-dir as JSON files following the naming pattern <contract>-<function>-case<NNNN>.json. Each artifact contains:

  • artifact_hash — SHA-256 of the canonical artifact JSON (used for deduplication and replay verification)
  • wasm_hash — SHA-256 of the WASM that produced the finding
  • campaign_seed — seed used to generate the test case sequence
  • case_id — zero-based case index in the campaign
  • input — the typed arguments, seed, and auth mode for this case
  • observed_result — the actual return value or error from contract execution
  • classification — the finding type: Pass, ExpectedError, ReturnMismatch, UnexpectedError, etc.
  • minimized — whether the case was minimized
  • complexity — case complexity before and after minimization
  • observation — structured error/return data for replay
  • auth_per_step — authorization entries for each step in a multi-step sequence (if applicable)

Supported --format values:

Format Purpose
pretty Human-readable report; findings listed with classification and details
json Single JSON document: campaign metadata + findings array
jsonl JSON Lines: one artifact per line for streaming processing

Exit codes:

  • 0 — campaign executed successfully; findings may or may not have been discovered
  • 1 — runtime error (e.g. WASM load failure, host error, invalid arguments)
  • 2 — usage error (e.g. malformed expectation, invalid seed)

Replay

sdkt fuzz replay <artifact> replays a finding artifact against its WASM to verify it reproduces.

Syntax:

sdkt fuzz replay ./sdkt-fuzz-artifacts/contract-increment-case0042.json \
  --wasm contract.wasm

Arguments and flags:

Flag Meaning
<ARTIFACT> Path to the finding artifact (JSON) produced by a campaign
--wasm <WASM> required — Path to the campaign WASM the artifact was produced from

Replay verdict:

Exit code Verdict Meaning
0 REPRODUCED The artifact reproduced: same input, same result, finding verified
1 MISMATCH The artifact did not reproduce: result differs from expected
2 Usage error Invalid artifact or WASM path

Determinism:

If --replay was used during the campaign, every artifact was automatically replayed to verify determinism. A REPRODUCED verdict confirms that the finding can be independently re-executed with the exact same inputs and produce the same result.

Artifact directory layout

By default, artifacts are written to ./sdkt-fuzz-artifacts with this structure:

./sdkt-fuzz-artifacts/
├── <contract-hash>-<function-name>-case0000.json
├── <contract-hash>-<function-name>-case0001.json
├── <contract-hash>-<function-name>-case0042.json
└── ...

Each JSON file is a complete finding artifact, self-contained and ready for replay.

Real-contract evidence

For details on fuzz evidence from real Soroban contracts and multi-step execution, see Real-Contract Fuzz Evidence.

Integration with release assurance

sdkt release-assurance accepts --fuzz-evidence <EVIDENCE_JSON> to attach fuzz evidence to a release report. The release pipeline does not run fuzz campaigns itself; campaigns are run separately (manually or in CI) and the resulting evidence is attached by path.

Plugin management

A local, offline-first plugin store. All operations are local; there is no hosted registry and no remote source. Plugins are referenced by a stable id declared in their plugin.toml.

sdkt plugin list                                   # list installed plugins
sdkt plugin list --format json                     # JSON output; every plugin subcommand accepts --format json
sdkt plugin init ./path/to/my-rule                 # scaffold a new audit rule project
sdkt plugin show <id>                              # show a plugin's metadata
sdkt plugin install ./path/to/artifact.wasm        # install from a local file
sdkt plugin install ./bundle.sdktplugin            # verify, then install a bundle (reports signed: true/false)
sdkt plugin install ./bundle.sdktplugin --public-key ./pubkey.key               # require a signature from this author key (unsigned is refused)
sdkt plugin remove <id>                            # remove (idempotent)
sdkt plugin update <id> ./path/to/artifact.wasm    # local-only update
sdkt plugin pack ./path/to/plugin-dir --output ./myrule.sdktplugin              # pack into .sdktplugin bundle
sdkt plugin pack ./path/to/plugin-dir --secret-key ./secret.key                 # sign bundle with Ed25519 secret key (32 bytes, raw)
sdkt plugin verify-bundle ./bundle.sdktplugin                                                # verify unsigned bundle integrity
sdkt plugin verify-bundle ./bundle.sdktplugin --public-key ./pubkey.key                      # verify signed bundle against a specific public key
sdkt audit contract.rs --rules <id>                # resolve id → stored artifact

sdkt audit --rules <id> resolves an installed plugin ID to its stored artifact; filesystem paths must point to compiled .so, .dylib, .dll, or .wasm plugin artifacts. Passing a directory prints a warning and the audit continues with the built-in rules. Installing a native plugin prints a warning: native plugins run unsandboxed (unmodified behavior). See docs/plugins/plugin-authoring.md for the plugin.toml schema and the install-validation rules.

Store root precedence (lowest → highest): <cwd>/.sdkt/plugins, <config-dir>/sdkt/plugins, $SDKT_PLUGIN_DIR.

  • --format json is supported on all read-style commands, every plugin subcommand, and on diff, audit, deploy, init for scripting / CI.

  • diff --upgrade-safety and deploy --deny-breaking implement the Upgrade Safety Guard (see ROADMAP.md).

  • WASM size policy (opt-in). diff and release-assurance accept --max-size-bytes <N> (absolute ceiling on the candidate artifact) and --max-growth-pct <N> (growth over the baseline). Thresholds are operator-supplied policy — SDKT hardcodes no Stellar network limit, and passing a threshold does not assert network-limit compliance. diff measures growth against --old-wasm; release-assurance requires --previous-wasm for a growth check and fails with an explicit error when it is missing rather than silently skipping. Violations exit non-zero: diff prints the violation on stderr after the report, and release-assurance marks the artifact section FAIL (so release_status becomes FAIL). Boundaries are inclusive — a size exactly at the limit or growth exactly at the cap passes. diff --format json also reports size_delta_bytes (signed) and size_delta_pct (one decimal, null when the old artifact is zero bytes). Size policy is independent of ABI compatibility: it is refused in combination with diff --upgrade-safety, and a size violation never changes an upgrade-safety verdict.

    # Report size change alongside the ABI diff (always on, no flags needed):
    sdkt diff --old-wasm prev.wasm --new-wasm candidate.wasm --format json
    
    # Fail the build on a >10% growth OR a >128 KiB candidate (your numbers):
    sdkt diff --old-wasm prev.wasm --new-wasm candidate.wasm \
        --max-growth-pct 10 --max-size-bytes 131072
    
    # Gate a release on the same policy, inside the full assurance report:
    sdkt release-assurance --wasm candidate.wasm --previous-wasm prev.wasm \
        --max-growth-pct 10 --max-size-bytes 131072
  • audit implements the static-analysis rules (AUTH-001/002/003/004, MOVE-001, MATH-001, CEI-001).

  • AUTH-001/AUTH-004 severity follows observed behavior, not the name. Both rules start from a privileged-name heuristic (initialize/init, set_*/grant_*/revoke_* prefixes, and the substrings admin, mint, burn, pause, unpause, upgrade, withdraw, configure, freeze, ban, drain, set_admin, owner_set, transfer_ownership, change_owner, set_auth), then decide severity from what the function actually does:

    • a method of a #[contractimpl] block that performs a recognized state write or an external call and never calls require_auth() → critical (the unguarded-privilege case);
    • a function that is not a #[contractimpl] method (a plain fn or a method of an un-annotated impl) → info: external callers cannot reach it, so authorization is the responsibility of the entrypoint that calls it;
    • a #[contractimpl] method that returns a value with no recognized state write or external call → info: positive evidence of a read-only accessor (admin_balance, get_paused), which cannot be a missing-authorization defect;
    • a #[contractimpl] method that returns nothing with no recognized side effect → critical: the write may go through a helper the scanner does not recognize, so the finding stays loud.

    Nothing is deleted — a demotion is a severity change, always visible in the report (pretty, --format json, and --format sarif agree). --fail-on/CI gates keyed to critical therefore stop triaging getter noise, while info findings remain auditable.

  • --fail-on <severity> (opt-in CI gate). sdkt audit exits 0 on findings by default. With --fail-on critical|warning|info (case-insensitive) the already-computed severity summary is mapped to the process exit code: exit 1 when any finding is at or above the threshold, after the full report has been printed unchanged — in pretty, JSON, and SARIF output alike. An invalid level is a usage error (exit 2) raised before any analysis runs. The flag is independent of --disable: disabled rules produce no findings and therefore never trip the gate.

  • audit --list-rules discovers all registered built-in rules (with id, severity, and description). Supports --format json and does not require a source path argument.

  • Health verdict exit codes. sdkt health exits non-zero when the verdict is critical (for example, the deployed WASM does not match the file passed with --wasm), so a mismatched artifact cannot pass CI or an agent check that only reads the exit code. at_risk remains non-blocking (exit 0), matching how release-assurance treats it.

  • sdkt-agent. A separate binary that turns a plain-language request into a validated, read-only sdkt invocation: sdkt-agent "check health of contract C… on testnet". It resolves the capability from the registry, pins an explicit testnet endpoint for network-touching checks, refuses mutating capabilities and mainnet targets outright, and never guesses on an ambiguous request. --format json emits a versioned result document; the default output is a six-line summary. Full guide — build, capability surface, ambiguity and refusal codes, artifact roles, mutation boundary, network policy, result semantics, troubleshooting: sdkt-agent.

  • Agent capability registry (sdkt_core::registry). The CLI's capabilities are described in one machine-readable table — command path, required/optional arguments, output formats, exit semantics, network requirement, safety class, and an evidence level — exportable as a versioned JSON manifest (sdkt_core::registry::to_json()). It is metadata about existing commands only: nothing is executed and no new CLI surface is added. Mutating capabilities (deploy, invoke, tx sign/submit, storage extend/restore, project deploy, identity fund) are marked requires_confirmation and are never marked verified by a read-only audit. Adding a capability means appending one Capability entry and keeping the parity test green; see the module docs for the rules the tests enforce.

  • sdkt build --format json. Emits a single JSON document on stdout: {"success": true, "artifacts": [{"alias", "path", "wasm_artifact"}]} on success, or {"success": false, "error": "..."} with a non-zero exit on failure. The advisory sdkt.lock report is written to stderr so stdout stays parseable; the default pretty output is unchanged.

  • Contract metadata (contractmetav0). sdkt wasm inspect <file.wasm> decodes the contractmetav0 custom section and prints a Contract Metadata block of key = value entries (for example the rsver, rssdkver, and cliver provenance keys) in section order, with duplicate keys preserved. A WASM without the section prints Contract Metadata (0): followed by (none). With --format json the entries appear as the additive metadata.contract_meta array of { "key": ..., "value": ... } objects — an empty array when the section is absent — alongside the existing metadata keys, which are unchanged. A malformed contractmetav0 payload is reported as a classified error rather than a panic.

  • sdkt wasm metadata --contract <id> reports the same entries for the deployed contract, from the fetched (or cached) WASM bytes: pretty output gains the identical Contract Metadata block, and --format json gains an additive top-level contract_meta array alongside the existing inspection keys, which are unchanged. It is empty when the deployed WASM carries no contractmetav0.

  • Mainnet safety. Mutating commands (tx submit, invoke, deploy, project deploy) refuse to target mainnet unless you explicitly select the network — via --network-profile, --rpc-url, or --network-passphrase. A testnet-default passphrase pointed at a mainnet endpoint is rejected before any request is sent, protecting against signing for the wrong network.

Fuzz expectation declarations

sdkt fuzz campaign accepts repeatable --expect-success <FN> and --expect-error <FN:TYPE:CODE> declarations. A function may have one expectation; declaring it with both flags is a usage error that exits 2 and names the conflicting function. A single valid declaration keeps its existing campaign behavior. Malformed --expect-error declarations also remain usage errors.

Error Handling

  1. All subcommands return Result<(), Box<dyn std::error::Error>>.
  2. Library-level errors (RpcError, DecodeError, WasmError) are bubbled up to the CLI.
  3. The CLI uses eprintln! to print human-readable errors and exits non-zero on fatal errors to ensure correct bash piping behavior.