# Vara Wallet

> Use when an agent needs to interact with Vara Network or Vara.eth on-chain — deploy programs, call Sails methods, manage wallets, transfer tokens, monitor events. Do not use for building Sails programs.

- Skill: `gear-foundation/vara-wallet` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add gear-foundation/vara-wallet`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gear-foundation/vara-wallet/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: gear-foundation (https://skillmd.com/u/gear-foundation)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/gear-foundation/vara-wallet

---


# Vara Wallet

CLI tool for AI agents to interact with Vara Network on-chain.

**Repository:** https://github.com/gear-foundation/vara-wallet
**Install:** `npm install -g vara-wallet`

## Role

Use this skill for **on-chain interaction** with Vara Network and Vara.eth: deploying programs, calling Sails methods, managing wallets, transferring tokens, querying state, and monitoring events.

Do NOT use this skill for:
- Writing Sails Rust programs — use `vara-skills/skills/sails-feature-workflow/`
- Running gtest — use `vara-skills/skills/sails-gtest/`
- Setting up Rust/Gear toolchain — use `vara-skills/skills/sails-dev-env/`

## Setup

```bash
# Check if installed
if command -v vara-wallet &>/dev/null; then
  VW="vara-wallet"
else
  npm install -g vara-wallet
  VW="vara-wallet"
fi
```

**Minimum version: 0.20.5.** The recipes below assume the structured `PROGRAM_ERROR` / `TRANSPORT_ERROR` surfaces, the enveloped `sails:idl` extractor, transparent auto-retry on transient transport reasons (`timeout`, `ws_close_abnormal`), the Vara.eth `--chain vara-eth` rail, direct `--wait submitted` handoff, and persistent `vara-eth:session` action loops.

## Zero-Setup Wallet

On first use, create a wallet. Encryption and passphrase are automatic — no human setup required.

```bash
# Creates wallet, auto-generates passphrase, encrypts, suppresses secrets
$VW wallet create --name agent

# Verify
$VW wallet list
# → [{ "chain": "vara", "name": "agent", "address": "kG...", "encrypted": true, "isDefault": true }]
```

The passphrase is stored at `~/.vara-wallet/.passphrase` (0600). The agent never sees or handles it.

`wallet list` without `--chain` is a full local inventory: native Vara rows use
`chain: "vara"` and Vara.eth rows use `chain: "vara-eth"`. Use
`--chain vara wallet list` or `--chain vara-eth wallet list` when a workflow
needs only one rail.

## Vara.eth Quick Route

Use `--chain vara-eth` when the user asks for Vara.eth, Hoodi, Mirrors,
WVARA, Ethereum V3 keystores, or `vara-eth:*` commands. Native Vara remains the
default. Vara.eth network names are `mainnet`, `hoodi`, and `local`; do not use
native Vara's `testnet` alias on this rail.

```bash
$VW vara-eth:wallet create agent-eth --passphrase "$PASSPHRASE"
$VW --chain vara-eth --network hoodi vara-eth:wvara balance 0xYOUR_ETH_ADDRESS
$VW --chain vara-eth --network hoodi --account agent-eth \
  vara-eth:message send 0xMIRROR --payload 0xfeed --via eth --wait submitted
```

Vara.eth messages and state-changing Sails calls default to `--wait reply`;
WVARA writes and message replies default to `--wait receipt`. Select `--wait
submitted` only for transaction-hash handoff. Direct messages then avoid the
validator and return `messageId: null`; read the detailed reference first.

Prefer root Sails parity where possible:

```bash
$VW --chain vara-eth --network hoodi --account agent-eth \
  program upload ./program.opt.wasm --idl ./program.idl --args '[]'
$VW --chain vara-eth --network hoodi discover 0xMIRROR --idl ./program.idl
$VW --chain vara-eth --network hoodi call 0xMIRROR Service/Query --args '[]' --idl ./program.idl
```

For detailed Vara.eth wallet workflows, read `references/vara-eth-wallet.md` in this skill.
For multiple Vara.eth Sails actions in one agent process, use `vara-eth:session` (persistent signer, connection, and IDL cache; injected submissions); see the detailed reference before choosing it over a direct L1 write.

## Command Quick Reference

### Read (no account needed)

| Command | Purpose |
|---------|---------|
| `$VW node info` | Chain name, genesis, latest block |
| `$VW balance [address]` | Account balance in VARA |
| `$VW program info <id>` | Program status and codeId |
| `$VW program list [--count N] [--all]` | List on-chain programs (default: 100) |
| `$VW code info <codeId>` | Code blob metadata |
| `$VW code list [--count N]` | List uploaded code blobs |
| `$VW call <pid> Service/Query --args '[]' [--idl <path>]` | Sails read-only query (free; IDL auto-resolved from on-chain WASM for any program with an embedded `sails:idl` section) |
| `$VW discover <pid> [--idl <path>]` | Introspect Sails services, methods, events (IDL auto-resolved from on-chain WASM) |
| `$VW idl import <path.idl> (--code-id <hex> \| --program <hex\|ss58>)` | Seed local IDL cache for contracts without an embedded IDL |
| `$VW idl list` | List cached IDL entries (codeId, version, source, importedAt, idlSizeBytes) |
| `$VW idl remove <code-id>` | Remove one cache entry (idempotent) |
| `$VW idl clear [--yes]` | Wipe IDL cache (terraform-style: bare invocation previews; `--yes` commits) |
| `$VW metadata list` | List cached runtime-metadata entries (`genesisHash-specVersion`, saves ~750ms per warm connect) |
| `$VW metadata clear [--yes]` | Wipe runtime-metadata cache (terraform-style: bare previews; `--yes` commits) |
| `$VW state read <pid>` | Read raw program state |
| `$VW mailbox read [address]` | Read mailbox messages |
| `$VW inbox list [--since <duration>] [--limit <n>]` | Query captured mailbox messages from event store |
| `$VW inbox read <messageId>` | Read a specific captured message |
| `$VW events list [--type <t>] [--since <d>] [--program <id>]` | Query captured events from event store |
| `$VW events prune [--older-than <duration>]` | Delete old events |
| `$VW query <pallet> <method> [args...]` | Generic storage query |
| `$VW vft balance <token> [account] --idl <path>` | Fungible token balance |
| `$VW vft info <token> --idl <path>` | Token name, symbol, decimals, total supply |
| `$VW vft allowance <token> <owner> <spender> --idl <path>` | Token allowance |
| `$VW dex pairs --factory <addr>` | List DEX trading pairs |
| `$VW dex pool <t0> <t1> --factory <addr>` | Pool reserves and prices |
| `$VW dex quote <tIn> <tOut> <amount> --factory <addr>` | Swap quote with price impact |

### Write (account required — add `--account <name>`)

| Command | Purpose |
|---------|---------|
| `$VW transfer <to> <amount>` | Transfer VARA tokens |
| `$VW program upload <wasm> [--idl <path>] [--init <name>] [--args <json>] [--payload <hex>] [--value <v>]` | Upload + init program (use --idl for auto-encoding) |
| `$VW program deploy <codeId> [--idl <path>] [--init <name>] [--args <json>] [--payload <hex>] [--value <v>]` | Deploy from existing code (use --idl for auto-encoding) |
| `$VW code upload <wasm>` | Upload code blob only |
| `$VW message send <dest> [--payload <hex>] [--value <v>] [--voucher <id>]` | Send message to any actor (program, user, wallet) |
| `$VW message reply <mid> [--payload <hex>] [--voucher <id>]` | Reply to a message |
| `$VW mailbox claim <messageId>` | Claim value from mailbox message |
| `$VW call <pid> Service/Function --args '[...]' --value <v> --units human\|raw [--idl <path>]` | Sails state-changing call (response includes decoded `events: [...]`) |
| `$VW call <pid> Service/Function --estimate [--idl <path>]` | Estimate gas cost without sending |
| `$VW call <pid> Service/Function --dry-run [--idl <path>]` | Encode SCALE payload + return `destination` — no signing, no submit, no wallet |
| `$VW call <pid> Service/Function --dry-run --estimate [--idl <path>]` | Encode AND estimate gas (compose; account required for the estimate half) |
| `$VW call <pid> Service/Function --args-file <path> [--idl <path>]` | Read `--args` JSON from file (use `-` for stdin) — avoids shell-escape on nested JSON |
| `$VW vft transfer <token> <to> <amount> --idl <path>` | Transfer fungible tokens |
| `$VW vft transfer-from <token> <from> <to> <amount> --idl <path>` | Transfer from approved allowance |
| `$VW vft approve <token> <spender> <amount> --idl <path>` | Approve token spender |
| `$VW vft mint <token> <to> <amount> --idl <path>` | Admin token minting |
| `$VW vft burn <token> <from> <amount> --idl <path>` | Admin token burning |
| `$VW dex swap <tIn> <tOut> <amount> --factory <addr> [--slippage <bps>]` | Swap tokens (auto-approves) |
| `$VW dex add-liquidity <t0> <t1> <a0> <a1> --factory <addr>` | Add pool liquidity |
| `$VW dex remove-liquidity <t0> <t1> <lp> --factory <addr>` | Remove pool liquidity |
| `$VW voucher issue <spender> <value>` | Issue gas voucher (see `../../references/voucher-and-signless-flows.md`) |
| `$VW voucher revoke <spender> <voucherId>` | Revoke voucher |
| `$VW faucet [address]` | Request testnet TVARA tokens (auto-connects to testnet) |
| `$VW sign <data> [--hex]` | Sign arbitrary data with wallet key (raw sr25519) |
| `$VW tx <pallet> <method> [args...]` | Submit generic extrinsic |

### Verify (no account needed)

| Command | Purpose |
|---------|---------|
| `$VW verify <data> <signature> <address> [--hex]` | Verify signature against data and address |

### Monitor

| Command | Purpose |
|---------|---------|
| `$VW wait <messageId> [--timeout <s>]` | Wait for message reply |
| `$VW watch <pid> [--event Service/Event\|pallet:Name] [--idl <path>] [--no-decode]` | Stream program events (NDJSON; IDL adds `decoded.kind === 'sails'` block) |
| `$VW subscribe blocks [--finalized]` | Stream new/finalized blocks (NDJSON + SQLite) |
| `$VW subscribe messages <pid> [--event Service/Event\|pallet:Name] [--idl <path>] [--no-decode]` | Stream program messages/events (IDL-aware decoding) |
| `$VW subscribe mailbox <address>` | Capture mailbox messages (survives between runs) |
| `$VW subscribe balance <address>` | Stream balance changes |
| `$VW subscribe transfers [--from <a>] [--to <a>]` | Stream transfer events |
| `$VW subscribe program <pid>` | Stream program state changes |

### Wallet Management

| Command | Purpose |
|---------|---------|
| `$VW wallet create [--name <n>]` | Create encrypted wallet |
| `$VW wallet import [--seed <s>] [--mnemonic <m>] [--json <path>]` | Import existing key |
| `$VW wallet list` | List native + Vara.eth wallet inventory; add `--chain` to filter |
| `$VW wallet export <name> [--decrypt]` | Export keyring JSON |
| `$VW wallet default [name]` | Get/set default wallet |
| `$VW init [--name <n>]` | Initialize config + default wallet |
| `$VW config list` | Show all config values |
| `$VW config set network testnet` | Persist network endpoint |
| `$VW config set <key> <value>` | Set any config key |
| `$VW config get <key>` | Get a config value |

## Common Workflows

### Deploy and interact with a Sails program

```bash
# 1. Upload program (auto-encodes Sails constructor from IDL)
UPLOAD=$($VW --account agent program upload ./target/wasm32-unknown-unknown/release/my_program.opt.wasm \
  --idl ./target/idl/my_program.idl --args '["MyToken", "MTK", 18]')
PROGRAM_ID=$(echo $UPLOAD | jq -r .programId)

# 2. Discover interface
$VW discover $PROGRAM_ID --idl ./target/idl/my_program.idl

# 3. Estimate gas before calling
$VW --account agent call $PROGRAM_ID MyService/DoSomething --args '["hello"]' --idl ./my_program.idl --estimate

# 4. Call a function (state-changing)
$VW --account agent call $PROGRAM_ID MyService/DoSomething --args '["hello"]' --idl ./my_program.idl

# 5. Query state (read-only, free)
$VW call $PROGRAM_ID MyService/GetState --args '[]' --idl ./my_program.idl
```

### Local node deployment

When deploying to a local dev node, use `--network local` or set the endpoint explicitly.

```bash
# Use --network shorthand (recommended)
$VW --network local wallet import --seed '//Alice' --name alice

# Or persist in config
$VW config set network local

# Or set for the session
export VARA_WS=ws://localhost:9944

# Deploy with IDL-based constructor encoding
UPLOAD=$($VW --account alice program upload ./target/wasm32-unknown-unknown/release/my_program.opt.wasm \
  --idl ./my_program.idl --args '["arg1"]')
PROGRAM_ID=$(echo $UPLOAD | jq -r .programId)

# Verify
$VW call $PROGRAM_ID MyService/GetState --args '[]' --idl ./my_program.idl
```

### Send message and wait for reply

```bash
RESULT=$($VW --account agent message send $PROGRAM_ID --payload 0x00)
MSG_ID=$(echo $RESULT | jq -r .messageId)

REPLY=$($VW wait $MSG_ID --timeout 60)
echo $REPLY | jq .payload
```

### Monitor program events

```bash
# Raw NDJSON
$VW watch $PROGRAM_ID | while read -r line; do
  echo "$line" | jq .
done

# IDL-aware: each UserMessageSent gets a `decoded: { kind: 'sails', service, event, data }` block
$VW watch $PROGRAM_ID --idl ./my-program.idl | jq '.decoded // .'

# Filter by Sails event (qualified or bare when unambiguous)
$VW watch $PROGRAM_ID --idl ./my-program.idl --event MyService/Transferred
$VW subscribe messages $PROGRAM_ID --idl ./my-program.idl --event Transferred --count 1 --timeout 30

# Force pallet vocab even with IDL loaded — replaces the dropped --pallet-event flag
$VW watch $PROGRAM_ID --idl ./my-program.idl --event pallet:UserMessageSent
```

Bare event names that resolve to multiple services hard-fail with `AMBIGUOUS_EVENT` listing the alternatives — qualify as `Service/Event` or use `pallet:Name` to force pallet vocabulary. `--no-decode` disables the opportunistic IDL auto-load entirely. The `decoded.kind` discriminator future-proofs the surface for additional decoder types.

### Preview a call without signing

`--dry-run` encodes the SCALE payload and exits with `willSubmit: false`. Works on machines with no wallet configured — useful for previewing payloads in CI, code review, or read-only environments.

```bash
# call dry-run — no account needed
$VW call $PROGRAM_ID MyService/DoSomething --args '["hello"]' --idl ./my-program.idl --dry-run

# program upload dry-run — reports resolved constructor + encoded init payload
$VW program upload ./my-program.opt.wasm --idl ./my-program.idl --args '["MyToken", "MTK", 18]' --dry-run
```

### Pass nested JSON via stdin (avoids shell-escape footguns)

`--args-file <path>` (or `-` for stdin) eliminates double-escape failures when the JSON contains hex actor IDs, 64-byte `vec u8` signatures, or other punctuation-heavy payloads.

```bash
echo '[["0xabcd...", 1000]]' | $VW --account agent call $PROGRAM_ID MyService/BatchTransfer --args-file - --idl ./my-program.idl
```

`--args` and `--args-file` are mutually exclusive (`INVALID_ARGS_SOURCE`). Stdin without a pipe attached fails fast with `STDIN_IS_TTY` instead of hanging on EOF.

### Subscribe to events (with persistence)

```bash
# Catch mailbox messages (they vanish after ~1 block)
$VW subscribe mailbox $MY_ADDRESS

# Wait for exactly 1 transfer, then exit (agent-friendly)
$VW subscribe transfers --count 1 --timeout 30

# Query captured events between runs
$VW inbox list --since 1h
$VW events list --type mailbox --limit 10
```

### Token operations

```bash
# Token info
$VW vft info $TOKEN_PROGRAM --idl ./vft.idl

# Check balance
$VW vft balance $TOKEN_PROGRAM --idl ./vft.idl

# Transfer
$VW --account agent vft transfer $TOKEN_PROGRAM $RECIPIENT 1000 --idl ./vft.idl

# Approve
$VW --account agent vft approve $TOKEN_PROGRAM $SPENDER 1000 --idl ./vft.idl
```

### DEX operations (Rivr)

```bash
# Check available pairs
$VW dex pairs --factory $FACTORY

# Get swap quote with price impact
$VW dex quote $TOKEN_IN $TOKEN_OUT 100 --factory $FACTORY

# Swap tokens (auto-approves input token)
$VW --account agent dex swap $TOKEN_IN $TOKEN_OUT 100 --factory $FACTORY --slippage 100
```

### Fund an account with a voucher

```bash
$VW --account sponsor voucher issue $SPENDER_ADDRESS 100 --duration 14400
```

### Sign and verify data

```bash
# Sign arbitrary data
SIG=$($VW --account agent sign "hello world" | jq -r .signature)

# Verify signature
$VW verify "hello world" $SIG $ADDRESS
```

## IDL Resolution

Sails commands (`call`, `discover`, `vft`, `dex`) need an IDL. Resolution order:

1. **`--idl <path>`** — local file, always works, takes precedence
2. **Local cache** — `~/.vara-wallet/idl-cache/<codeId>.cache.json`, populated automatically by previous fetches or by `idl import`
3. **Embedded `sails:idl` section** — auto-extracted from the program's on-chain WASM and cached. Since 0.18.0 the extractor reads the enveloped section format (1-byte version, 1-byte flags with deflate bit, payload) emitted by current sails-rs, with a raw UTF-8 fallback for older beta.1-era programs.
4. **Bundled IDLs** — standard VFT and Rivr DEX IDLs ship in the binary for `vft` / `dex` commands.

**Embedded IDL is the default.** Any contract built with current sails-rs ships its IDL inside the WASM, so `call` / `discover` / `vft` / `dex` work without `--idl`; the first invocation populates the cache and subsequent calls hit it for free. `idl import` is only for contracts that don't have an embedded `sails:idl` section — seed the cache once:

```bash
# Resolves codeId via RPC
$VW idl import ./my-program.idl --program <programId>

# Fully offline if you already know the codeId
$VW idl import ./my-program.idl --code-id 0x<hex>
```

## Output Parsing

All commands output JSON to stdout. Errors go to stderr as `{ error, code }`.

```bash
# Extract a field
$VW balance | jq -r .balance

# Check transaction success
RESULT=$($VW --account agent transfer $TO 1)
echo $RESULT | jq '.events[] | select(.section == "balances")'

# Verbose debug (stderr, won't break JSON parsing)
$VW --verbose balance 2>/dev/null | jq .
```

## Structured Errors

Program-execution failures surface as flat JSON on stderr — `reason` and `programMessage` are top-level keys, not nested under `meta`:

```json
{"code":"PROGRAM_ERROR","reason":"panic","programMessage":"<BareVariant>","error":"<full text>"}
```

`reason` ∈ `panic` | `unreachable` | `inactive` | `not_found`. `programMessage` is the bare Sails error variant — the `called \`Result::unwrap()\` on an \`Err\` value:` wrapper that `#[export(unwrap_result)]` adds is stripped, so agents can switch on it directly:

```bash
ERR=$(vara-wallet --account agent call $PID Service/Method --args '[...]' --idl ./my.idl 2>&1 1>/dev/null)
case "$(echo "$ERR" | jq -r '.programMessage // ""')" in
  InsufficientBalance) refill ;;
  Unauthorized)        switch_account ;;
  *)                   echo "unhandled: $ERR" ;;
esac
```

## Transport Errors

**Migration:** scripts that grepped `"code":"CONNECTION_TIMEOUT"` (WS connect) or `"code":"TIMEOUT"` / `"code":"CONNECTION_FAILED"` (program/dex/vft/message paths) must switch to `"code":"TRANSPORT_ERROR"` + the matching `reason`. Faucet HTTP `CONNECTION_FAILED` is unchanged — different consumer surface. `--light` failures route through the same taxonomy.

Transport-layer failures (DNS, WS handshake, RPC disconnect, TLS, timeout) surface as structured `TRANSPORT_ERROR` with a `reason` subcode instead of the legacy opaque `{"error":"{}","code":"UNKNOWN_ERROR"}`. The error carries `endpoint`, `host` (DNS path), and `meta.cause` (raw underlying message):

```json
{"code":"TRANSPORT_ERROR","reason":"dns_failure","error":"Cannot resolve host nonexistent-host","endpoint":"wss://nonexistent-host","host":"nonexistent-host","meta":{"cause":"<raw>"}}
```

`reason` taxonomy: `dns_failure` | `connection_refused` | `timeout` | `ws_close_abnormal` | `protocol_mismatch` | `unreachable` | `tls_failure` | `unknown`. Switch on it to decide retry vs. fail:

```bash
case "$(echo "$ERR" | jq -r '.reason // ""')" in
  timeout|connection_refused|unreachable|ws_close_abnormal) retry_with_backoff ;;
  dns_failure|tls_failure|protocol_mismatch)                fail_fast ;;
esac
```

`--verbose` writes a `[verbose] cause: code=<x>, message=<y>` line to stderr immediately before the structured JSON (EPIPE-safe), making cause-chain triage cheap without changing the machine-readable surface.

## Network Switching

```bash
# Shorthand (recommended)
$VW --network testnet balance

# Per-command with full URL
$VW --ws wss://testnet.vara.network balance

# Persist across sessions
$VW config set network testnet

# Session-wide env var
export VARA_WS=wss://testnet.vara.network
```

**Endpoint resolution order:** `--ws` > `--network` > `VARA_WS` env > `config.wsEndpoint` > default.

| Network | Endpoint | `--network` shorthand |
|---------|----------|----------------------|
| Mainnet | `wss://rpc.vara.network` (default) | `--network mainnet` |
| Testnet | `wss://testnet.vara.network` | `--network testnet` |
| Local | `ws://localhost:9944` | `--network local` |

Connection timeout is 10s. Bad endpoints fail fast with `TRANSPORT_ERROR` instead of hanging — see *Transport Errors*.

For full network endpoint and account format details, see `../../references/vara-network-endpoints.md`.

## Units

1 VARA = 10^12 minimal units (12 decimals). Amounts default to VARA.

```bash
$VW transfer $TO 1.5                        # 1.5 VARA
$VW transfer $TO 1500000000000 --units raw   # same in raw units
```

All `--units` flags use a unified `human|raw` vocabulary:
- **Native commands** (`balance`, `transfer`, `message`, `call`, `voucher`, `program`): `human` (default) = VARA decimals (12). `raw` = minimal units passthrough.
- **VFT** and **DEX** commands: `raw` (default) = minimal units passthrough. `human` = use the token's declared decimals (queried at runtime).
- The literals `vara` and `token` are **rejected** with `INVALID_UNITS` — pulled from old tutorials, they no longer work.

Existential deposit is ~10 VARA on mainnet.

## Error Recovery

| Code | Meaning | Action |
|------|---------|--------|
| `NO_ACCOUNT` | No signing account | Add `--account <name>` |
| `PASSPHRASE_REQUIRED` | Encrypted wallet, no passphrase | Check `~/.vara-wallet/.passphrase` exists |
| `DECRYPT_FAILED` | Wrong passphrase | Verify passphrase file content |
| `TRANSPORT_ERROR` | Transport-layer failure | Switch on `.reason` to decide retry vs. fail. See *Transport Errors* |
| `WRONG_NETWORK` | Command not available on this network | Use `--network testnet` for faucet |
| `TX_TIMEOUT` | Transaction didn't land in 60s | Retry — network congestion |
| `TX_FAILED` | On-chain failure | Inspect `.events` in output |
| `IDL_NOT_FOUND` | No embedded `sails:idl` and no cache/bundled match | Run `idl import` (the error pre-fills the command) or pass `--idl <path>`. See *IDL Resolution* |
| `METHOD_NOT_FOUND` | Method not in IDL | Check `discover` output; cross-service hint is suggested in the error |
| `AMBIGUOUS_EVENT` | Bare Sails event name maps to multiple services | Qualify as `--event Service/Event` or use `pallet:Name` |
| `INVALID_ARGS_FORMAT` | `--args` shape mismatch | Sails methods take POSITIONAL args. Pass as a JSON array: `'[arg1, arg2]'`. Single-struct-arg methods also accept the bare object form: `'{"field": ...}'`. Multi-arg methods reject named-arg objects |
| `INVALID_ADDRESS` | Typed `actor_id` field got the wrong shape | Read the field name from the error (`Invalid ActorId for "<field>": ...`) and fix that field — use hex (`0x` + 64 chars), SS58, or 32-byte array. Don't rewrite the whole payload |
| `INVALID_ARGS_SOURCE` | `--args` and `--args-file` used together | Pick one |
| `STDIN_IS_TTY` | `--args-file -` used with no pipe attached | Pipe JSON in or pass a real path |
| `CONFLICTING_OPTIONS` | Mutually exclusive options (e.g. `--network` + `--ws`) | Pick one. Note: `--dry-run` + `--estimate` *compose* on `call` |
| `INVALID_UNITS` | `--units` value isn't `human` or `raw` | Use the unified vocabulary; the literals `vara` / `token` are rejected |
| `PROGRAM_ERROR` | Sails program execution failed | Inspect top-level `reason` and `programMessage`. State problem — do NOT bump `--gas-limit`. See *Structured Errors* for the JSON shape, `reason` enum, and case-switch pattern |
| `PERMISSION_DENIED` | OS-level permission error (e.g. `idl clear --yes` on a read-only dir) | Check filesystem permissions on `~/.vara-wallet/` |
| `INVALID_NETWORK` | Unknown `--network` value | Use mainnet, testnet, or local |
| `INVALID_CONFIG_KEY` | Unknown config key | Use `config list` to see valid keys |

## Guardrails

- Never pass secrets (seeds, mnemonics, passphrases) as CLI arguments in committed scripts. Use wallet files.
- Never use `--show-secret` in automated flows. Secrets should stay in encrypted wallet files.
- Always use `--account <name>` for signing, not `--seed`.
- Gas is auto-calculated — omit `--gas-limit` unless you have a specific reason. `calculateGas` failures (program panic during dry-run) surface as classified `PROGRAM_ERROR`, not opaque gas errors — see Error Recovery.
- Messages are async. After `message send`, use `wait` to get the reply.
- `call` auto-detects queries vs functions — no need to specify.
- When targeting a local dev node, use `--network local` or `VARA_WS=ws://localhost:9944`. The default endpoint is mainnet.
- Use `config set network testnet` to persist network choice across sessions.
- `program list` returns 100 programs by default. Use `--all` for unlimited.
- `faucet` only works on testnet. It refuses mainnet endpoints automatically.
- If `sails-local-smoke` is green and you need to interact with a deployed program on a live network, switch to this skill.

