Every example below uses the real sdkt CLI. Commands that touch a live
network (a contract id, account, or RPC) are marked (network) and require
an RPC endpoint configured in .sdkt.toml or via the default testnet/public
RPC. Offline commands work anywhere.
The repository ships a self-contained, offline-runnable example so you can
reproduce the core workflow without creating /tmp files:
examples/
sample_token/src/lib.rs # minimal Soroban contract (intentionally has an AUTH-001 finding)
sample_scval.b64 # a valid base64-encoded ScVal for `sdkt decode`
Build sdkt first, then run the deterministic smoke test (no network, no
secrets):
cargo build --bin sdkt
bash scripts/smoke_examples.shThe script verifies, against the actual binary:
sdkt --versionreports2.7.0.sdkt wasm inspect crates/sdkt-cli/tests/fixtures/us_old.wasmshows a contract spec withfn transfer.sdkt audit examples/sample_token/src/lib.rsreportsAUTH-001onadmin_action(the example's deliberate, unguarded privileged function).sdkt decodeonexamples/sample_scval.b64returns{"bool": false}.
All four checks must pass for the smoke test to exit 0.
A real, copy-paste example (offline — no network):
sdkt decode AAAAAAAAAAIAAAAAAAAABHRlc3Q= --type ScVal
# → { "bool": false }
sdkt decode AAAAAAAAAAIAAAAAAAAABHRlc3Q= --type ScVal --format json
sdkt decode --file payload.b64 --type TransactionEnvelopeThe decoder also handles TransactionEnvelope and ContractEvent payloads the
same way.
sdkt diff --old-wasm deployed.wasm --new-wasm candidate.wasm --upgrade-safety
sdkt diff --old-wasm deployed.wasm --new-wasm candidate.wasm --format jsonsdkt audit runs on contract Rust source. Write a tiny throwaway contract to
a temp file, then point the auditor at it — no project scaffold required:
cat > /tmp/token.rs <<'EOF'
use soroban_sdk::{contract, contractimpl, Address};
#[contract]
pub struct Token;
#[contractimpl]
impl Token {
pub fn transfer(_from: Address, _to: Address) {}
// NOTE: admin_action is privileged but missing require_auth() — audit flags it
pub fn admin_action(_admin: Address) {}
}
EOF
sdkt audit /tmp/token.rs
sdkt audit /tmp/token.rs --format json
sdkt audit /tmp/token.rs --disable MOVE-001sdkt wasm cache info
sdkt wasm cache clearThese need a configured network. Set it once:
sdkt init my-project --minimal # scaffolds a project + .sdkt.tomlsdkt inspect <CONTRACT_ID> --abi contract.wasm
sdkt storage check <CONTRACT_ID> --abi contract.wasm
sdkt storage analyze <CONTRACT_ID>
# Estimate storage rent offline for a WASM contract (per-class breakdown and total)
sdkt storage estimate contract.wasm
# Read a contract storage entry.
# Option A — raw LedgerKey (base64 or hex XDR), the advanced escape hatch:
sdkt storage read --contract <CONTRACT_ID> --key-xdr <BASE64_LEDGER_KEY>
# Option B — typed key built from the contract's own types (same TYPE:VALUE
# syntax as `sdkt call --args`). This builds ScVec[symbol, args...]:
# persistent map entry keyed by a symbol + address:
sdkt storage read --contract <CONTRACT_ID> --map-key balances --key-arg address:G...
# composite key (symbol + multiple typed args):
sdkt storage read --contract <CONTRACT_ID> --map-key allowance --key-arg address:G... --key-arg address:G...
# temporary durability (defaults to persistent):
sdkt storage read --contract <CONTRACT_ID> --map-key session --key-arg u32:100 --durability temporary
# the contract's instance-storage entry:
sdkt storage read --contract <CONTRACT_ID> --instance
# Extend TTL of the contract instance (and optional extra keys)
# --ledgers is relative: entries will live at least N ledgers past the current ledger.
# Example: 17280 ledgers ≈ 1 day at 5s/ledger.
sdkt storage extend --contract <CONTRACT_ID> --ledgers 17280 --identity my-deployercall and invoke accept repeated --args-json values. Each value is a JSON
array whose elements are encoded as positional Soroban arguments, in order;
the encoded values are appended after any --args values.
| JSON value | Soroban value |
|---|---|
null |
Void |
true / false |
Bool |
| JSON string | String |
| integer in signed 32-bit range | I32 |
| non-negative integer up to unsigned 32-bit range | U32 |
| larger signed integer | I64 |
| larger non-negative integer | U64 |
| JSON array | Vec |
| JSON object | Map<String, ScVal> |
For example:
sdkt call C... transfer --args-json '["alice", {"amount": 100}, null]'
sdkt invoke C... transfer --args-json '["alice", {"amount": 100}]'Malformed JSON, non-array roots, unsupported number representations, and values that exceed Soroban collection limits fail before any RPC request is sent.
Build, validate, simulate, sign, and submit a Soroban transaction:
# 1. Create a local signing identity (offline)
sdkt identity generate alice
# 2. Build an unsigned envelope (offline; fee defaults to 100 stroops,
# override with --fee <STROPS>)
sdkt tx build \
--source <SOURCE_ACCOUNT> --sequence <SEQ> \
--contract <CONTRACT_ID> --function hello \
--output unsigned.xdr
# 3. Validate the envelope offline
sdkt tx validate --envelope unsigned.xdr
# 4. Simulate against the network to catch failures early (RPC)
sdkt tx simulate --envelope unsigned.xdr
# 4b. Simulate with ABI-aware result decoding (requires contractspecv0 WASM)
sdkt tx simulate --envelope unsigned.xdr --abi /path/to/contract.wasm
# 4c. Simulate with on-chain ABI from a deployed contract (no local WASM needed)
sdkt tx simulate --envelope unsigned.xdr --abi-contract C...CONTRACT_ID...
# 5. Sign with the local identity (offline)
sdkt tx sign --input unsigned.xdr --output signed.xdr --identity alice --network testnet
# 6. Submit the signed envelope (RPC)
sdkt tx submit --envelope signed.xdrtx sign is fully offline — it signs with a local ED25519 keystore
identity, so no RPC or secret exposure is involved. The --network flag
(testnet | mainnet | futurenet | custom:<passphrase>) only selects the
signature hash; signing never touches the network. tx submit / tx simulate
read the network from .sdkt.toml or default to testnet RPC.
sdkt events <CONTRACT_ID> --abi contract.wasm
sdkt events <CONTRACT_ID> --topic transfer # only events whose first topic is `transfer`
sdkt account <ADDRESS>
sdkt fee estimatesdkt identity generate alice
sdkt identity list
sdkt identity show alice
sdkt identity default alice
sdkt identity delete alice# Save a named network profile (referenced by other commands instead of full URLs)
sdkt network add testnet \
--rpc-url https://soroban-testnet.stellar.org \
--passphrase "Test SDF Network ; September 2015" \
--friendbot https://friendbot.stellar.org \
--description "Stellar testnet"
# List / inspect / remove
sdkt network list
sdkt network show testnet
sdkt network remove testnet
# Machine-readable output for scripting / CI
sdkt network show testnet --format jsonFriendbot is a Testnet-only faucet — it does not exist on Mainnet. The network
profile you pass must carry an explicit friendbot_url. sdkt identity fund
removes the manual step of copying your public key and pasting it into a Friendbot
UI or making a raw curl call.
# Generate a local signing identity (offline)
sdkt identity generate my-deployer
# Fund it via Friendbot using a profile that has --friendbot set
sdkt identity fund my-deployer --network-profile testnet
# JSON output for scripting
sdkt identity fund my-deployer --network-profile testnet --format json- Requires
--network-profile <NAME>to be passed explicitly. - The profile must have a friendbot URL (set via
sdkt network add --friendbot <url>). - If the profile has no Friendbot URL, the command fails with a clear error.
- HTTP 429 (rate limit) is reported explicitly — wait and retry.
- HTTP 5xx and malformed responses surface as errors.
Once a profile exists, reference it from any RPC command instead of repeating the full endpoint:
# Inspect / explore using the saved profile
sdkt inspect <CONTRACT_ID> --network-profile testnet
sdkt account <ADDRESS> --network-profile testnet
sdkt events <CONTRACT_ID> --network-profile testnet
# Override the profile inline when needed
sdkt inspect <CONTRACT_ID> --network-profile testnet \
--rpc-url https://my-custom-rpc.example \
--network-passphrase "Custom Network ; 2024"Precedence (highest wins): explicit --rpc-url / --network-passphrase >
--network-profile > .sdkt.toml [network] > built-in testnet default.
Commands without these flags behave exactly as before.
# Auto-generate salt (simplest)
sdkt deploy --wasm contract.wasm
# Explicit salt for deterministic address
sdkt deploy --wasm contract.wasm --salt <40-HEX-CHARS>
# Abort if the upgrade is not backwards-compatible:
sdkt deploy --wasm new.wasm --salt <40-HEX-CHARS> --deny-breaking --old-wasm deployed.wasm--salt is optional. When omitted, a random 20-byte salt is generated automatically.
Invoke a contract function without signing or submitting a transaction. Useful for querying state, checking return values, and debugging.
# Simple call (no args)
sdkt call C... balance
# With typed args
sdkt call C... transfer --args address:G...,u32:100
# JSON output for scripting
sdkt call C... balance --format json --network-profile testnet
# ABI-aware result decoding (requires contractspecv0 WASM)
sdkt call C... balance --abi /path/to/contract.wasm --format json --network-profile testnet
# Same, but the ABI comes from the deployed contract (no local WASM needed)
sdkt call C... balance --abi-contract C... --format json --network-profile testnet--argsaccepts typed values:u32:N,u64:N,i32:N,i64:N,timepoint:N,duration:N,bool:true|false,string:text,address:G...--abi <wasm>enables ABI-aware result decoding using the contract spec from a local WASM file. Without it, the raw base64 XDR result is shown.--abi-contract <id>resolves the ABI from the deployed contract over RPC instead (inspect_contract→get_wasm_bytecode→ spec parse) — no local artifact needed. Mutually exclusive with--abi.- When an ABI is supplied and the function is found in the spec, the decoded human-readable label appears in the output. JSON includes both raw and decoded fields.
- If the function is not in the ABI, the raw result is preserved and a warning is emitted to stderr.
- If
--abiWASM is invalid/unparseable, the command fails with a clear error. - No identity required — call is read-only via
simulateTransaction - No transaction is signed or submitted
Run the full transaction lifecycle in one command: sequence fetch → simulation → final envelope build → signing → submission → polling.
# Invoke with a local identity (signs + pays fees)
sdkt invoke C... increment --args u32:1 --identity alice --network-profile testnet
# JSON output for scripting
sdkt invoke C... set_admin --args address:G... --identity alice --format json --network-profile testnet
# Build and sign the envelope, but do not submit it
sdkt invoke C... set_admin --args address:G... --identity alice --network-profile testnet --build-only
# → prints the base64 envelope plus the simulated fee and sequence.
# Feed it straight back into the offline/remote path:
sdkt tx validate --envelope <that-xdr>
sdkt tx sign --input <that-xdr> --identity other --output signed.txt
sdkt tx submit --envelope signed.txt --wait--argsuses the sameTYPE:VALUEsyntax ascall, but is strict: an unknown type or a missingTYPE:prefix is an error (no base64 passthrough).--identitynames a keystore identity (sdkt identity generate); the secret key never touches the command line.- The fee is computed from the simulation (
minResourceFee+ 100 stroops inclusion fee); the footprint and auth entries come from the same simulation. - Output shows the transaction hash, final status, fee, and result XDR. Exit code is non-zero when the transaction fails or is rejected.
--build-onlyruns sequence fetch → simulation → envelope build → signing and stops before submission: nosendTransaction, no polling, no state change. It prints the envelope (pretty:Transaction Envelope (NOT submitted):block; JSON:envelopeXdrplusfeeandsequence) and exits 0. The mainnet safety guard still applies, and simulation failures surface exactly as they do on the submit path. The emitted envelope is byte-for-byte whatinvokewould have submitted, so it round-trips throughtx validate/tx sign/tx submit.- Relation to
tx build/sign/submit:invokeis the one-command equivalent oftx build(with a real sequence + simulated fees) →tx sign→tx submit --wait;invoke --build-onlycovers the first two without thetxround-trip. Use thetxsubcommands when you need to inspect or modify the envelope between steps; useinvokefor the common straight path. - Limitations (core implementation): single-operation only, no ABI-aware
result decoding of the return value, no
--feeoverride. A live Testnet smoke test is documented here but NOT exercised in CI.
Gate a PR on the static audit and a release on upgrade-safety. See ci-cd.md for the full workflows.
Ensure privileged functions have authentication barriers:
# .github/workflows/sdkt-audit.yml
on: [pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SaboLabs/soroban-devkit/.github/actions/sdkt@main
with:
command: audit
sdkt-version: v2.7.0
target: contracts/token/src/lib.rs
severity-threshold: criticalEnsure the newly built .wasm is completely backward-compatible with what is currently on-chain:
# .github/workflows/sdkt-upgrade-safety.yml
on:
release:
types: [published]
jobs:
upgrade-safety:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: SaboLabs/soroban-devkit/.github/actions/sdkt@main
with:
command: upgrade-safety
sdkt-version: v2.7.0
old-wasm: builds/current.wasm
new-wasm: builds/candidate.wasm