Agentic wallet CLI for Vara Network — designed for AI coding agents.
All output is structured JSON by default. No interactive prompts. Wallets are encrypted automatically with zero setup required.
# Install
npm install -g vara-wallet
# Create a wallet (auto-generates passphrase, encrypts, no secrets shown)
vara-wallet wallet create --name my-wallet
# Check balance
vara-wallet balance
# Transfer VARA
vara-wallet transfer <destination> 10
# Interact with a Sails program
vara-wallet call <programId> Service/Method --args '["arg1", "arg2"]'
# Pass hex strings for binary args — auto-converted to byte arrays
vara-wallet call <programId> Service/Upload --args '["0xdeadbeef"]'
# Pass SS58 or hex addresses for ActorId args — SS58 auto-normalized to hex
vara-wallet call <programId> Vft/BalanceOf --args '["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"]'Requires Node.js 20 or newer.
npm install -g vara-walletThe CLI ships as a single bundled file (~2 MB gzipped) with only two runtime dependencies (better-sqlite3 and smoldot), so global install is a few seconds on any network.
git clone https://github.com/gear-foundation/vara-wallet.git
cd vara-wallet
npm install --legacy-peer-deps
npm run build
npm link--ws <endpoint> WebSocket endpoint (default: wss://rpc.vara.network)
--light Use embedded light client (smoldot) instead of WebSocket
--seed <seed> Account seed (SURI like //Alice or hex)
--mnemonic <mnemonic> Account mnemonic phrase
--account <name> Wallet name to use
--json Force JSON output
--human Force human-readable output
--quiet Suppress all output except errors
--verbose Show debug info on stderr
--network <name> Network shorthand: mainnet, testnet, or local
--network maps to the well-known WS endpoint for each network. Cannot be used with --ws.
| Variable | Description | Default |
|---|---|---|
VARA_WS |
WebSocket endpoint | wss://rpc.vara.network |
VARA_SEED |
Account seed | — |
VARA_MNEMONIC |
Account mnemonic | — |
VARA_LIGHT |
Set to 1 to use embedded light client (smoldot) |
— |
VARA_PASSPHRASE |
Wallet passphrase (CI/Docker fallback) | — |
VARA_WALLET_DIR |
Config directory | ~/.vara-wallet |
VARA_DEX_FACTORY |
DEX factory program address | — |
VARA_FAUCET_URL |
Faucet API URL | https://faucet.gear-tech.io |
When a command needs a signing account, it checks in order:
--seedflagVARA_SEEDenv var--mnemonicflagVARA_MNEMONICenv var--accountflag (loads wallet file)- Default account from config
Wallets are encrypted by default using Polkadot's xsalsa20-poly1305 + scrypt KDF.
Passphrase resolution for decryption:
~/.vara-wallet/.passphrasefile (primary — agent never sees it)VARA_PASSPHRASEenv var (fallback for CI/Docker)
On first wallet create: If no passphrase source exists, a random 256-bit passphrase is auto-generated and saved to ~/.vara-wallet/.passphrase with 0600 permissions. The agent never sees the passphrase value.
# Zero-setup: just works
vara-wallet wallet create --name agent-key
# Passphrase auto-generated, wallet encrypted, secrets suppressed
# Human override
vara-wallet wallet create --name human-key --passphrase "memorable-phrase"
# Opt out of encryption (not recommended)
vara-wallet wallet create --name unsafe --no-encrypt --show-secretInitialize wallet infrastructure with a default wallet.
vara-wallet init [--name <name>]Request testnet TVARA tokens. Proves address ownership via a challenge-sign-claim flow. Automatically connects to testnet RPC.
vara-wallet faucet [address] [--faucet-url <url>]Skips the request if the account already has >= 1000 TVARA. Faucet URL resolves from: --faucet-url flag > VARA_FAUCET_URL env > config.faucetUrl > default.
vara-wallet wallet create [--name <n>] [--passphrase <p>] [--no-encrypt] [--show-secret]
vara-wallet wallet import [--name <n>] [--mnemonic <m>] [--seed <s>] [--json <path>] [--passphrase <p>] [--no-encrypt]
vara-wallet wallet list
vara-wallet wallet export <name> [--decrypt] [--output <path>]
vara-wallet wallet keys <name>
vara-wallet wallet default [name]wallet keys outputs the raw key material: { address, publicKey, secretKeyPkcs8, type }. The PKCS8 blob contains the full secret key and can be used with Polkadot tooling to reconstruct the keypair. This is a sensitive operation — the secret key is exposed in the output. For a redacted export suitable for sharing, use wallet export.
vara-wallet node infovara-wallet balance [address]
vara-wallet transfer <to> <amount> [--units human|raw]
vara-wallet transfer <to> --all--all drains the entire account via Substrate's native transferAll extrinsic (no client-side fee/ED math). Without --all, transfers use transferKeepAlive. --all and an explicit amount are mutually exclusive.
vara-wallet message send <destination> [--payload <hex>] [--gas-limit <n>] [--value <v>] [--units human|raw] [--metadata <path>] [--voucher <id>]
vara-wallet message reply <messageId> [--payload <hex>] [--gas-limit <n>] [--value <v>] [--units human|raw] [--metadata <path>] [--voucher <id>]
vara-wallet message calculate-reply <programId> [--payload <hex>] [--value <v>] [--units human|raw] [--origin <addr>] [--at <blockHash>]Gas is auto-calculated if --gas-limit is omitted. Destination can be any actor (program, user, wallet). Use --value to transfer VARA tokens alongside a message. Use --voucher <id> to pay for the message using a voucher instead of the sender's balance.
vara-wallet program upload <wasm> [--payload <hex>] [--idl <path>] [--init <name>] [--args <json> | --args-file <path>] [--gas-limit <n>] [--value <v>] [--units human|raw] [--salt <hex>] [--metadata <path>] [--dry-run]
vara-wallet program deploy <codeId> [--payload <hex>] [--idl <path>] [--init <name>] [--args <json> | --args-file <path>] [--gas-limit <n>] [--value <v>] [--units human|raw] [--salt <hex>] [--metadata <path>] [--dry-run]
vara-wallet program info <programId>
vara-wallet program list [--count <n>] [--all]Use --idl to auto-encode the constructor payload from a Sails IDL file. The constructor is auto-selected if the IDL has only one; use --init <name> when multiple constructors exist. --args passes constructor arguments as a JSON array, or use --args-file <path> to read JSON from a file (or - for stdin). --payload and --idl are mutually exclusive. --dry-run encodes the constructor payload and exits without signing or submitting; the response reports the resolved constructor name, encoded hex, and willSubmit: false.
# Deploy with IDL-based constructor encoding (auto-selects "New" constructor)
vara-wallet program upload ./demo.opt.wasm --idl ./demo.idl --args '["MyToken", "MTK", 18]'
# Explicit constructor name
vara-wallet program upload ./demo.opt.wasm --idl ./demo.idl --init New --args '["MyToken", "MTK", 18]'
# Deploy from existing code ID
vara-wallet program deploy 0xCODE_ID --idl ./demo.idl --args '["MyToken", "MTK", 18]'vara-wallet code upload <wasm> [--voucher <id>]
vara-wallet code info <codeId>
vara-wallet code list [--count <n>]High-level method invocation on Sails programs. Auto-detects queries vs functions. --estimate computes gas cost (account required). --dry-run encodes the SCALE payload without signing / submitting (no account needed, useful for previewing on read-only machines). The two compose: passing both encodes AND estimates in one call.
vara-wallet call <programId> <Service/Method> [--args <json> | --args-file <path>] [--value <v>] [--units human|raw] [--gas-limit <n>] [--idl <path>] [--voucher <id>] [--estimate] [--dry-run]For v2 programs (sails ≥ 1.0.0-beta.1) the IDL is auto-resolved from the program's on-chain WASM — --idl is only needed for v1 programs (use idl import) or when overriding with a local file. Resolved IDLs are cached under ~/.vara-wallet/idl-cache/ so subsequent calls skip the fetch. Inspect or evict the cache with vara-wallet idl list/remove/clear.
--args-file <path> reads the JSON args from a file instead of the --args string; use - for stdin (echo '[...]' | vara-wallet call ... --args-file -). Eliminates shell-escape failures with nested JSON containing hex actor IDs or 64-byte vec u8 signatures. Mutually exclusive with --args (INVALID_ARGS_SOURCE).
--dry-run output includes encodedPayload (the actual SCALE-encoded call bytes — round-trippable via decode), destination (the program ID the message is bound for), and willSubmit: false. With --estimate also set, estimateGas: { gasLimit, minLimit } is appended.
The JSON response from a real submission includes an events: [...] field with any decoded Sails events emitted by the call, phase-correlated to the submitting extrinsic (cross-transaction events from the same block are excluded). Nested numeric leaves (U256, u128) inside Option, Vec, tuples, structs, enums, Result, or user types are recursively decoded to decimal strings to match the declared IDL return type.
Introspect a Sails program's services, functions, queries, and events.
vara-wallet discover <programId> [--idl <path>]Same auto-resolution as call.
Manage the local IDL cache at ~/.vara-wallet/idl-cache/. The cache is populated automatically when v2 programs auto-resolve their IDL from on-chain WASM, or manually via idl import for v1 programs (no embedded IDL section).
vara-wallet idl import <path.idl> (--code-id <hex> | --program <hex|ss58>)
vara-wallet idl list
vara-wallet idl remove <code-id>
vara-wallet idl clear [--yes]idl importseeds the cache with an out-of-band IDL.--code-idis fully offline;--programresolvescodeIdvia RPC. Once imported,call/discover/vft/dexfind the IDL automatically for that program'scodeId.idl listprints[{ codeId, version, source, importedAt, idlSizeBytes }, ...]. Empty cache returns[]. Corrupted entries surface as{ codeId, error: 'corrupted', ... }rows so a single bad file never crashes the listing.idl remove <code-id>removes one entry. Idempotent: removing a non-existent entry returns{ removed: false, codeId }and exit 0.idl clear(terraform-style) — bare invocation prints awouldRemovepreview without unlinking;--yescommits and prints{ removed: N, path }. Snapshot-then-unlink: ENOENT for entries removed by parallel writers between enumeration and unlink is swallowed.
Works out of the box with standard VFT programs — no --idl needed (bundled IDL fallback).
vara-wallet vft info <tokenProgram> [--idl <path>]
vara-wallet vft balance <tokenProgram> [account] [--idl <path>]
vara-wallet vft allowance <tokenProgram> <owner> <spender> [--idl <path>]
vara-wallet vft transfer <tokenProgram> <to> <amount> [--idl <path>] [--units human|raw] [--voucher <id>]
vara-wallet vft approve <tokenProgram> <spender> <amount> [--idl <path>] [--units human|raw] [--voucher <id>]
vara-wallet vft transfer-from <tokenProgram> <from> <to> <amount> [--idl <path>] [--units human|raw] [--voucher <id>]
vara-wallet vft mint <tokenProgram> <to> <amount> [--idl <path>] [--units human|raw] [--voucher <id>]
vara-wallet vft burn <tokenProgram> <from> <amount> [--idl <path>] [--units human|raw] [--voucher <id>]--units human passes amounts using the token's declared decimals (e.g., 1.5 auto-converts via the on-chain Decimals query). Default is raw (minimal units, passthrough as bigint). The legacy token literal was renamed to human in 0.15.0 for cross-command consistency. vft balance and vft allowance safely return '0' when the underlying query is opt u256 and the account has no row (was a BigInt(null) crash in 0.14.x).
Trade tokens on the vara-amm decentralized exchange (Rivr DEX). Works with bundled IDLs — no --idl needed. Requires a factory address via --factory, VARA_DEX_FACTORY env, or dexFactoryAddress in config.
Rivr DEX testnet factory: 0xaec14c514124fffa6c4b832ba7c12fa19e7fa663774c549c114786e220dd0a4e
vara-wallet dex pairs [--factory <addr>] [--limit <n>]
vara-wallet dex pool <token0> <token1> [--factory <addr>]
vara-wallet dex quote <tokenIn> <tokenOut> <amount> [--reverse] [--units human|raw]
vara-wallet dex swap <tokenIn> <tokenOut> <amount> [--slippage <bps>] [--deadline <s>] [--exact-out] [--skip-approve] [--voucher <id>]
vara-wallet dex add-liquidity <token0> <token1> <amount0> <amount1> [--slippage <bps>] [--deadline <s>] [--skip-approve] [--voucher <id>]
vara-wallet dex remove-liquidity <token0> <token1> <liquidity> [--slippage <bps>] [--deadline <s>] [--skip-approve] [--voucher <id>]Slippage is in basis points (100 = 1%, default). Swaps auto-approve input tokens unless --skip-approve is set. Use --units human to pass amounts in the token's declared decimals (e.g. 1.5); default is raw (minimal units).
vara-wallet voucher issue <spender> <value> [--units human|raw] [--duration <blocks>] [--programs <ids>]
vara-wallet voucher list <account> [--program <id>]
vara-wallet voucher revoke <spender> <voucherId>vara-wallet mailbox read [address]
vara-wallet mailbox claim <messageId>vara-wallet state read <programId> [--payload <hex>] [--origin <addr>] [--at <blockHash>]vara-wallet tx <pallet> <method> [args...]
vara-wallet query <pallet> <method> [args...]Wait for a reply to a message.
vara-wallet wait <messageId> [--timeout <seconds>]Subscribe to on-chain events with NDJSON streaming and optional SQLite persistence. Events are stored in ~/.vara-wallet/events.db so they survive between runs.
vara-wallet subscribe blocks [--finalized]
vara-wallet subscribe messages <programId> [--event <Service/Event | EventName | pallet:Name>] [--from-block <n>] [--idl <path>] [--no-decode]
vara-wallet subscribe mailbox <address>
vara-wallet subscribe balance <address>
vara-wallet subscribe transfers [--from <addr>] [--to <addr>]
vara-wallet subscribe program <programId>Global subscribe options:
--count <n>— exit after N events (useful for scripting)--timeout <seconds>— exit after N seconds--no-persist— stream only, skip SQLite persistence
subscribe messages and watch share the IDL-aware filtering / decoding behavior. Both use --event (renamed from --type in 0.15.0 for consistency).
Query captured mailbox messages from the event store.
vara-wallet inbox list [--since <duration>] [--limit <n>]
vara-wallet inbox read <messageId>Query and manage all captured events from the event store.
vara-wallet events list [--type <type>] [--since <duration>] [--program <id>] [--limit <n>]
vara-wallet events prune [--older-than <duration>]Stream program events as NDJSON. For persistent event capture with SQLite storage, see subscribe above.
vara-wallet watch <programId> [--event <type>] [--idl <path>] [--no-decode]--event accepts:
- Gear pallet event names (
UserMessageSent,MessageQueued, ...) — bare names that match the legacy pallet vocab always resolve to the pallet path, so existing scripts keep working. - Qualified Sails events as
Service/Event— always unambiguous. - Bare Sails event names — succeed when exactly one service declares the event. Multiple-service collisions hard-fail with
AMBIGUOUS_EVENTlisting the alternatives. pallet:<Name>prefix — explicitly forces pallet vocabulary even when an IDL is loaded. Replaces the old--pallet-eventflag (removed in 0.15.0). Example:--event pallet:UserMessageSent.
When an IDL is loaded (explicit --idl or auto-resolved from the on-chain WASM), each emitted UserMessageSent is augmented with a decoded: { kind: 'sails', service, event, data } block alongside the existing raw fields (payload, source, destination, ...) — additive, so consumers parsing raw NDJSON keep working. The kind discriminator future-proofs the surface for additional decoder types. (Renamed from sails: {...} in 0.15.0.) --no-decode disables the opportunistic IDL auto-load entirely.
vara-wallet encode <type> [value] [--args-file <path>] [--metadata <path>] [--idl <path>] [--program <id>] [--method <Service/Method>]
vara-wallet decode <type> <hex> [--metadata <path>] [--idl <path>] [--program <id>] [--method <Service/Method>]For encode, the JSON value can be passed positionally or via --args-file <path> (use - for stdin). Positional and --args-file are mutually exclusive (INVALID_ARGS_SOURCE). Stdin via --args-file - rejects fast with STDIN_IS_TTY when no pipe is attached.
Manage persistent CLI configuration. Settings are stored in ~/.vara-wallet/config.json.
vara-wallet config list
vara-wallet config get <key>
vara-wallet config set <key> <value>
vara-wallet config set network testnet # shorthand for wsEndpointValid keys: wsEndpoint, defaultAccount, dexFactoryAddress, faucetUrl. The network alias maps mainnet/testnet/local to the corresponding wsEndpoint URL.
Endpoint resolution order: --ws flag > --network flag > VARA_WS env > config.wsEndpoint > default (wss://rpc.vara.network).
Sign arbitrary data and verify signatures. Uses raw sr25519 signing (no <Bytes> wrapping). No network connection needed.
# Sign data (UTF-8 string by default)
vara-wallet sign "hello world"
# Output: { signature, publicKey, address, cryptoType }
# Sign hex data
vara-wallet sign 0xdeadbeef --hex
# Verify a signature
vara-wallet verify "hello world" 0x<signature> --address <signer-address>
# Output: { isValid, address, cryptoType }The --hex flag treats input as 0x-prefixed hex bytes (strict validation: even-length, valid hex chars). Without --hex, input is treated as a UTF-8 string.
~/.vara-wallet/
config.json # wsEndpoint, defaultAccount, dexFactoryAddress, faucetUrl
.passphrase # Auto-generated or human-provided (0600)
events.db # SQLite event store (subscribe/inbox/events)
wallets/
default.json # Encrypted keystore (0600)
*.json
idl-cache/
<codeId>.cache.json # Auto-populated from on-chain WASM or `idl import`
| Code | Meaning |
|---|---|
PASSPHRASE_REQUIRED |
Encrypted wallet, no passphrase available |
DECRYPT_FAILED |
Wrong passphrase |
WALLET_NOT_FOUND |
Wallet file doesn't exist |
WALLET_EXISTS |
Wallet name already taken |
NO_ACCOUNT |
No account configured |
TX_TIMEOUT |
Transaction not included in 60s |
TX_FAILED |
On-chain extrinsic failure |
IDL_NOT_FOUND |
No Sails IDL available. Distinguishes v1 contracts (no sails:idl WASM section — import manually) from chain-unavailable cases (RPC down, no program) |
INVALID_ARGS_FORMAT |
--args shape mismatch. Sails methods take positional args; pass as JSON array. 1-arg struct methods also accept bare {...} (wrapped automatically) |
INVALID_ADDRESS |
actor_id field received a non-string non-array value. Message names the offending field: Invalid ActorId for "<name>": ... |
METHOD_NOT_FOUND |
Method not in Sails IDL |
DEX_FACTORY_NOT_CONFIGURED |
No factory address set |
DEX_SERVICE_NOT_FOUND |
DEX method not found in IDL |
PAIR_NOT_FOUND |
Trading pair doesn't exist |
TOKEN_MISMATCH |
Tokens don't match pair |
INVALID_SLIPPAGE |
Slippage out of range (0-5000 bps) |
CONNECTION_TIMEOUT |
WebSocket or light client connection timed out (10s) |
CONNECTION_FAILED |
Network unreachable or request timed out |
WRONG_NETWORK |
Command not available on this network (e.g., faucet on mainnet) |
INVALID_NETWORK |
Unknown --network value |
INVALID_CONFIG_KEY |
Unknown config key passed to config set/get |
CONFLICTING_OPTIONS |
Mutually exclusive options used together (e.g., --network + --ws). Note: --estimate + --dry-run compose on call since 0.15.0 (no longer conflicting) |
INVALID_ARGS_SOURCE |
--args and --args-file (or positional value + --args-file on encode) used together |
STDIN_IS_TTY |
--args-file - used with no pipe attached |
INVALID_UNITS |
--units value isn't human or raw (rejects legacy vara / token literals from pre-0.15) |
PERMISSION_DENIED |
OS-level permission error (e.g. idl clear --yes on a read-only cache dir) |
INVALID_CODE_ID |
--code-id argument isn't a 32-byte hex string |
AMBIGUOUS_EVENT |
Bare Sails event name resolves to multiple services — qualify as Service/Event |
FAUCET_ERROR |
Faucet request failed |
PROGRAM_ERROR |
Sails program execution failed (panic/error). Includes meta.reason (panic / unreachable / inactive / not_found) and meta.programMessage (contract error variant name) for structured matching |
FAUCET_LIMIT |
Faucet daily/hourly limit reached |
RATE_LIMITED |
Too many requests (429) |
AUTH_ERROR |
Signature verification failed |
npm run build # Bundle CLI with esbuild → dist/app.js
npm run dev # Run from source via ts-node
npm test # Run tests
npx tsc --noEmit # Type check onlyThe published artifact is a single bundled file (dist/app.js) built by scripts/build.mjs. better-sqlite3 and smoldot are kept external because they ship native binaries / WASM that cannot be inlined.
MIT