Skip to content

Latest commit

 

History

History
418 lines (324 loc) · 14.9 KB

examples.md

File metadata and controls

418 lines (324 loc) · 14.9 KB

Examples & Common Workflows

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.

Committed example (recommended starting point)

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.sh

The script verifies, against the actual binary:

  1. sdkt --version reports 2.7.0.
  2. sdkt wasm inspect crates/sdkt-cli/tests/fixtures/us_old.wasm shows a contract spec with fn transfer.
  3. sdkt audit examples/sample_token/src/lib.rs reports AUTH-001 on admin_action (the example's deliberate, unguarded privileged function).
  4. sdkt decode on examples/sample_scval.b64 returns {"bool": false}.

All four checks must pass for the smoke test to exit 0.

Offline (no network)

Decode a base64 XDR ScVal

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 TransactionEnvelope

The decoder also handles TransactionEnvelope and ContractEvent payloads the same way.

Offline WASM diff with upgrade-safety verdict

sdkt diff --old-wasm deployed.wasm --new-wasm candidate.wasm --upgrade-safety
sdkt diff --old-wasm deployed.wasm --new-wasm candidate.wasm --format json

Static security audit of a contract

sdkt 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-001

WASM metadata + cache (offline cache inspection)

sdkt wasm cache info
sdkt wasm cache clear

Network (requires RPC)

These need a configured network. Set it once:

sdkt init my-project --minimal   # scaffolds a project + .sdkt.toml

Inspect a contract's ABI and storage

sdkt 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-deployer

Transaction lifecycle

Composite JSON arguments

call 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.xdr

tx 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.

Events and account

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 estimate

Identity / keystore

sdkt identity generate alice
sdkt identity list
sdkt identity show alice
sdkt identity default alice
sdkt identity delete alice

Network profiles

# 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 json

Fund an identity (Testnet only)

Friendbot 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.

Using a profile with RPC commands

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.

Deploy

# 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.

Read-only contract call

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
  • --args accepts 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 --abi WASM 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

State-changing invoke

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
  • --args uses the same TYPE:VALUE syntax as call, but is strict: an unknown type or a missing TYPE: prefix is an error (no base64 passthrough).
  • --identity names 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-only runs sequence fetch → simulation → envelope build → signing and stops before submission: no sendTransaction, no polling, no state change. It prints the envelope (pretty: Transaction Envelope (NOT submitted): block; JSON: envelopeXdr plus fee and sequence) 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 what invoke would have submitted, so it round-trips through tx validate / tx sign / tx submit.
  • Relation to tx build/sign/submit: invoke is the one-command equivalent of tx build (with a real sequence + simulated fees) → tx sign → tx submit --wait; invoke --build-only covers the first two without the tx round-trip. Use the tx subcommands when you need to inspect or modify the envelope between steps; use invoke for the common straight path.
  • Limitations (core implementation): single-operation only, no ABI-aware result decoding of the return value, no --fee override. A live Testnet smoke test is documented here but NOT exercised in CI.

CI gating (copy-paste)

Gate a PR on the static audit and a release on upgrade-safety. See ci-cd.md for the full workflows.

Audit on PR Workflow

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: critical

Upgrade-Safety Workflow

Ensure 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