Skip to content

Latest commit

 

History

History
1065 lines (852 loc) · 46.4 KB

daemon_cli_guide.md

File metadata and controls

1065 lines (852 loc) · 46.4 KB

waved / wavecli User Guide

wavecli exit batch --outpoint TXID1:0 --outpoint TXID2:1 submits one cooperative batch. See cooperative exit batches for partial admission and unknown-outcome handling.

This guide covers installing, configuring, and operating the Ark client daemon (waved) and its CLI (wavecli).

Installation

From Source

Requires Go 1.26+ (see go.mod).

git clone https://github.com/lightninglabs/wavelength.git
cd wavelength

# Build both binaries into bin/
make build

# Or install to $GOPATH/bin
make install

After building, two binaries are produced:

  • bin/waved -- the long-running daemon process
  • bin/wavecli -- the CLI for controlling the daemon

Optional: wavewalletrpc

The "user-facing" verbs in the CLI (balance, recv, send, activity, create, unlock) route through the wavewalletrpc subserver. That code is gated behind the wavewalletrpc build tag; the default make build does not enable it. Without the tag, those verbs return:

daemon was not built with -tags wavewalletrpc;
rebuild with `make build-wavewalletrpc` or see docs/wavewalletrpc_build.md

Two options:

  1. Use the ark * and dev * subtrees — same RPCs as the top-level verbs but exposed under power-user parents. Works with the default build.
  2. Build with the tag enabled when you want the top-level verbs:
    make build-wavewalletrpc       # produces wavewalletrpc-enabled waved
    make install-wavewalletrpc

See wavewalletrpc_build.md for more.

Automation Contract

Command results are written to stdout. Failures are written to stderr as one JSON envelope with stable code, message, and retryable fields, plus details and remediation when useful:

{
  "error": {
    "code": "UNAVAILABLE",
    "message": "connection refused",
    "remediation": "verify waved is running and --rpcserver points to it, then retry",
    "retryable": true
  }
}

The process exits with 2 for invalid arguments, 3 for authentication or authorization failures, 4 for missing resources, and 5 when a valid fund-moving command needs explicit confirmation on non-interactive stdin. Other failures exit with 1. A successful --dry-run is a normal result: it prints a JSON preview containing "dry_run": true and exits with 0.

If send needs confirmation but stdin is not interactive, its prepared amount, fee, rail, and destination preview is written to stderr before the CONFIRMATION_REQUIRED envelope. Review it and rerun with --yes.

Daemon Configuration

waved supports two wallet backends: lwwallet (standalone, in-process) and lnd (uses an existing lnd node).

lwwallet Mode (Standalone)

The lightweight wallet requires only an Esplora API endpoint for chain access. No external lnd node is needed.

waved \
  --network=regtest \
  --wallet.type=lwwallet \
  --wallet.esploraurl=http://localhost:3000 \
  --server.host=localhost:10010 \
  --server.insecure \
  --rpc.listenaddr=localhost:10029

lnd Mode

Uses an existing lnd node for signing and key derivation. Point the daemon at the lnd gRPC interface with the TLS cert and admin macaroon.

waved \
  --network=regtest \
  --wallet.type=lnd \
  --lnd.host=localhost:10009 \
  --lnd.tlspath=~/.lnd/tls.cert \
  --lnd.macaroonpath=~/.lnd/data/chain/bitcoin/regtest/admin.macaroon \
  --server.host=localhost:10010 \
  --server.insecure \
  --rpc.listenaddr=localhost:10029

Daemon Flags Reference

Flag Default Description
--datadir ~/.waved Root data directory for all daemon state
--network mainnet Bitcoin network: mainnet, testnet, testnet4, regtest, simnet, signet
--debuglevel info Logging verbosity: trace, debug, info, warn, error, critical
--logdir ~/.waved/logs/<network> Directory for persistent daemon logs
--allow-mainnet false Required to run on mainnet (safety guard)
--wallet.type lwwallet Wallet backend: lwwallet, lnd, or btcwallet
--wallet.esploraurl network default Esplora REST API URL (lwwallet only); required on regtest/simnet, see docs/signet.md
--wallet.feeurl network default Fee-estimate JSON endpoint URL (btcwallet only); required on regtest/simnet, see docs/signet.md
--wallet.btcwallet_blockheaderssource Block header import source for btcwallet fast sync
--wallet.btcwallet_filterheaderssource Filter header import source for btcwallet fast sync
--wallet.pollinterval 30s Esplora poll interval (lwwallet only)
--wallet.recoverywindow 100 Address look-ahead window (lwwallet only)
--wallet.dbbackend bolt Wallet database backend (lwwallet only): bolt or sqlite; fixed once the wallet is created
--wallet.password_file Auto-unlock password file path (lwwallet/btcwallet)
--lnd.host localhost:10009 lnd gRPC address
--lnd.tlspath Path to lnd TLS certificate
--lnd.macaroonpath Path to lnd admin macaroon
--lnd.rpctimeout 30s Timeout for lnd RPC calls
--lnd.account (empty) lnd wallet account to spend from; empty uses lnd's default account. Set when sharing an lnd node with another daemon. The account must already exist and be taproot-scoped; a watch-only account is accepted only on a remote-signing lnd (one whose default account is itself watch-only) and must report an extended public key and derivation path
--assets.backend none Taproot Assets backend: none refuses asset VTXOs, tapd verifies their proofs through a tapd node, adding issuance proofs of unknown assets to its universe
--assets.tapd.host tapd gRPC address; required with --assets.backend=tapd
--assets.tapd.tlspath Path to tapd TLS certificate
--assets.tapd.macaroonpath Path to tapd admin macaroon
--assets.tapd.rpctimeout 30s Timeout for tapd RPC calls
--server.host network default Ark operator address override for the selected transport
--server.transport grpc Ark operator transport: grpc or rest
--server.insecure false Disable TLS for server connection
--server.tlscertpath Operator TLS certificate path
--rpc.listenaddr localhost:10029 Daemon gRPC listen address
--rpc.tlscertpath Custom TLS cert for daemon RPC
--rpc.tlskeypath Custom TLS key for daemon RPC
--swap.serveraddress network default Swap server address override for swapruntime builds
--swap.servertransport grpc Swap server transport: grpc or rest
--maxpaymentcltv 480 in swap-enabled builds Largest total Lightning payment CLTV reserved by automatic VTXO refresh; 0 disables the payment reserve

Empty Ark and swap addresses resolve from the selected network and transport. See signet.md for the testnet3, testnet4, and signet endpoints and override examples.

There is no mailbox-ID flag: the client and compound server mailbox IDs are derived automatically from the client's identity key at connect time.

Environment Variables

All flags can be set via environment variables with the WAVED_ prefix and dots replaced by underscores:

Variable Description
WAVED_WALLET_PASSWORD Wallet password for create/unlock
WAVED_LWWALLET_SEED Hex-encoded raw seed (dev/CI only). Without WAVED_WALLET_PASSWORD the wallet uses a public dev password, which mainnet refuses unless allow-insecure-mainnet is set
WAVED_LWWALLET_SEED_BIRTHDAY Creation date of that seed, YYYY-MM-DD or RFC3339
WAVED_NETWORK Bitcoin network override
WAVED_WALLET_TYPE Wallet backend type
WAVED_WALLET_ESPLORAURL Esplora URL
WAVED_SERVER_HOST Operator server address

A raw seed carries no birthday, unlike an aezeed mnemonic, so a wallet created from WAVED_LWWALLET_SEED without WAVED_LWWALLET_SEED_BIRTHDAY starts scanning at genesis. That is slow but never misses funds. Set the birthday only when the seed is known to have no history before that date: too early only costs scan time, while too late silently hides earlier funds. A malformed date is rejected rather than treated as unset.

The lwwallet backend usually resolves its own start height from the Esplora address index and ignores this, so it matters most for the neutrino-backed btcwallet backend, which has no index to consult.

Initial Wallet Setup

After starting the daemon, the wallet must be created and unlocked before any operations can proceed.

The create and unlock CLI commands require a daemon built with wavewalletrpc (see Installation above). For the default build, configure auto-unlock via --wallet.password_file and skip the CLI step.

Step 1: Create the Wallet (wavewalletrpc only)

In lwwallet mode, wallet creation generates a new aezeed mnemonic and creates the wallet database with its key material encrypted under your password. Record the mnemonic offline: it is shown once and is the only backup.

# Via environment variable (recommended for automation)
WAVED_WALLET_PASSWORD=your_password wavecli create

# Via explicit stdin (never consumed unless requested)
printf '%s\n' 'your_password' | wavecli create --password-stdin

# Via password file
wavecli create \
  --wallet-password-file=/path/to/password_file

# Interactive (prompts for password on TTY)
wavecli create

Important: The mnemonic is displayed on stderr during creation. Write it down and store it securely -- it is your only backup.

Step 2: Unlock the Wallet (wavewalletrpc only)

Each time the daemon restarts, the wallet must be unlocked:

WAVED_WALLET_PASSWORD=your_password wavecli unlock

Auto-Unlock

To skip the manual unlock step, provide the password file at daemon startup:

waved \
  --wallet.type=lwwallet \
  --wallet.password_file=/path/to/password_file \
  ...

The daemon reads the file, decrypts the seed, and starts the wallet automatically.

CLI Reference

wavecli connects to the daemon's gRPC server. All output is JSON.

Authentication

wavecli authenticates to the daemon over TLS with the daemon's admin macaroon. By default it derives both from the daemon data directory:

  • TLS cert: <datadir>/data/<network>/tls.cert
  • Macaroon: <datadir>/data/<network>/admin.macaroon

where --datadir defaults to ~/.waved and --network to mainnet. A daemon run on a non-default datadir or network must be matched on the CLI, otherwise wavecli looks under ~/.waved/data/mainnet/ and fails with read macaroon: ... no such file. For a signet instance under ~/.waved-signet:

wavecli --network=signet --datadir=~/.waved-signet getinfo

The daemon also writes readonly.macaroon beside the admin credential. Use the admin macaroon only for administration and provisioning. An admin client can create a least-privilege credential with wavecli bakemacaroon; the command accepts either a known entity:action permission or an exact uri:/package.Service/Method grant. Exact URI grants are useful when one method shares a broader write permission with unrelated operations.

For example, this payment credential can inspect balances and activity and spend through the two-phase send flow, including a full-balance on-chain send. It cannot call Deposit, SweepWallet, Exit, wallet-signing, recovery, or administrative RPCs:

wavecli bakemacaroon \
  --save-to ~/.waved/payment.macaroon \
  --valid-for 3600 \
  uri:/wavewalletrpc.WalletService/Balance \
  uri:/wavewalletrpc.WalletService/List \
  uri:/wavewalletrpc.WalletService/PrepareSend \
  uri:/wavewalletrpc.WalletService/Send \
  uri:/wavewalletrpc.WalletInspectionService/InspectActivity

The saved file contains the binary macaroon with mode 0600. Point the integration at it with --macaroonpath ~/.waved/payment.macaroon. Omitting --save-to prints the hex-encoded macaroon as JSON. BakeMacaroon requires the admin-only macaroon:generate permission. A credential carrying that permission, or the exact BakeMacaroon URI, can mint any supported permission and must also be treated as an admin credential. Root key ID 0 uses the same default root key as admin.macaroon and readonly.macaroon. ListPermissions exposes the active RPC-to-permission map to authenticated clients.

A macaroon cannot ride an unencrypted connection, so --no-tls and the macaroon are mutually exclusive. There are two working modes:

  • TLS + macaroon (default). Point --datadir / --network at the daemon, or override --tlscertpath / --macaroonpath directly.
  • Plaintext (regtest / dev). Pass both --no-tls and --no-macaroons. Passing only --no-tls fails with credentials require transport level security, because the CLI still tries to attach the macaroon.

The command examples below omit these connection flags. Set them once via an alias:

# signet instance over TLS
alias wave='wavecli --network=signet --datadir=~/.waved-signet'

# local regtest over plaintext
alias wave='wavecli --no-tls --no-macaroons'

Command tree

The everyday wallet verbs and daemon introspection make up the default --help face. The advanced ark, recovery, and dev subtrees are hidden from --help (set WAVELENGTH_DEV=1 to reveal them under an "Advanced" group) but stay fully runnable in every build — wavecli ark … works with or without the env var. WAVELENGTH_DEV only changes visibility; it never gates execution.

The swap subtree was retired — send/recv --offchain and activity cover it, and a stale wavecli swap … fails with a hint toward send/recv. The swapruntime daemon runtime that powers the offchain verbs is unchanged.

wavecli
├── getinfo                   — daemon status (no wavewalletrpc)
├── bakemacaroon              — provision a scoped daemon credential
├── balance                   — wallet balances (wavewalletrpc)
├── create / unlock           — wallet bring-up (wavewalletrpc)
├── recv                      — boarding address / Lightning invoice (wavewalletrpc)
├── send                      — Lightning invoice / onchain leave (wavewalletrpc)
├── activity [inspect]        — unified wallet activity feed (wavewalletrpc)
├── exit {status|summary|plan|export} — cooperative leave by default, forced unroll (wavewalletrpc)
├── wallet-sweep              — preview a backing-wallet sweep; broadcast requires approval
├── mcp serve                 — MCP server for AI agents (wavewalletrpc)
├── schema                    — JSON dump of CLI methods
├── ark                       — power-user parent (hidden; no wavewalletrpc)
│   ├── board                 — board confirmed boarding UTXOs
│   ├── vtxos {list|refresh|leave}
│   ├── oor {receive|get|list}
│   ├── send {oor|inround}    — real transfers require approval
│   ├── rounds {get|list|join|watch}
│   ├── sweep [list]          — broadcast requires approval
│   ├── fees {estimate|history}
│   └── listtransactions
├── recovery {list|status|escalate|cancel} — daemon-owned vHTLC recovery rows (hidden)
└── dev                       — generated low-level RPC (hidden; no wavewalletrpc)
    └── daemon <Method>       — call any waverpc.DaemonService method

Global Flags

Flag Default Description
--rpcserver localhost:10029 Daemon gRPC address
--datadir ~/.waved Daemon data dir; TLS cert + macaroon are derived from <datadir>/data/<network>/
--network mainnet Network segment of the derived cert / macaroon paths
--tlscertpath Explicit daemon TLS cert path (overrides --datadir)
--macaroonpath Explicit admin macaroon path (overrides --datadir)
--no-tls false Disable TLS (regtest / dev); requires --no-macaroons
--no-macaroons false Disable macaroon auth (required alongside --no-tls)
--timeout 30s Maximum duration for each finite daemon RPC; 0 disables the deadline. Live watches use their stream-specific bounds.
--json false Emit machine-readable JSON output
--request-json Raw JSON request payload (overrides bespoke flags)

Flag names are canonical kebab-case in help and examples. Equivalent snake_case spellings remain silent compatibility aliases; proto JSON and MCP argument field names remain snake_case.

Money-movement confirmation

wallet-sweep --broadcast, ark sweep --broadcast, and real ark send inround|oor calls require a y/N confirmation on a terminal. Agents, pipelines, --no-input, and CI=true must pass the command-local --yes flag. Without it, the CLI describes the blocked action and returns CONFIRMATION_REQUIRED with exit code 5 before dialing the daemon. Preview and --dry-run paths never require approval.

getinfo

Display daemon status information.

wavecli getinfo

bakemacaroon

Bake a daemon macaroon from one or more known entity:action or exact uri:/package.Service/Method permissions. The caller must authenticate with a credential authorized to call BakeMacaroon.

Flag Type Description
--save-to string Write the binary macaroon to this path with mode 0600
--root-key-id uint64 Numeric macaroon root key ID (default 0)
--valid-for uint64 Add an expiry caveat for this many seconds; 0 does not expire
wavecli bakemacaroon --save-to ./info.macaroon --valid-for 3600 info:read
wavecli bakemacaroon uri:/waverpc.DaemonService/GetInfo

balance (wavewalletrpc) / dev daemon GetBalance (no wavewalletrpc)

Display wallet balance (boarding, VTXO, total, onchain) in satoshis.

wavecli balance                   # requires wavewalletrpc
wavecli dev daemon GetBalance     # always available

recv (wavewalletrpc) / dev daemon NewAddress (no wavewalletrpc)

Allocate an inbound payment surface.

Flag Type Description
--offchain bool Returns a BOLT-11 invoice via the swap subsystem (default)
--onchain bool Returns a fresh boarding address
--amt uint Required for --offchain; ignored for --onchain
--amt-hint uint Optional expected amount for --onchain (accounting only)
--memo string Optional memo embedded in the offchain invoice
wavecli recv --onchain                  # boarding address
wavecli recv --offchain --amt 5000 --memo coffee

# No-wavewalletrpc equivalent for the boarding-address case:
wavecli dev daemon NewAddress

dev daemon deposit-asset / dev daemon list-asset-deposits

Fund a boarding output holding a Taproot Asset from the tapd wallet, and list those deposits. Requires --assets.backend=tapd and tapd v0.9.0 or later, which signs its own asset inputs when it publishes. The units come from one issuance, and any surplus returns to tapd as change. The daemon's own wallet pays and signs for the network fee, bounded by --max-fee-sat, and the Bitcoin value of the deposit output (value_sat) beyond what tapd's spent asset outputs carry. tapd signs only those asset outputs, when it publishes. The daemon sizes the deposit value from the operator's terms: two minimum VTXOs plus twice the quoted round fee. When the deposit boards, the value pays the round fee and the asset VTXO's satoshis, and the rest becomes a BTC VTXO. Retrying with the same --request-id resumes the deposit instead of funding it again. A deposit is published once tapd broadcasts it and confirmed once it has the confirmations the operator requires of a boarding input.

wavecli dev daemon deposit-asset --request-id alice-1 \
    --asset-ref <asset id or group key> --asset-amount 500 \
    --max-fee-sat 5000
wavecli dev daemon list-asset-deposits

dev daemon board-asset-deposit

Join the next round with a confirmed asset deposit. The round creates an asset VTXO holding the deposited units and the operator's minimum VTXO amount, and a BTC VTXO with the rest of the deposit's value after the round fee. Once the round checkpoints, list-asset-deposits shows its round_id. A deposit that a round already spends is refused; a failed round frees it to board again.

wavecli dev daemon board-asset-deposit --request-id alice-1

dev daemon claim-asset-deposit

Take back a confirmed asset deposit that no round holds. The claim spends the deposit through the owner's exit path, so it is accepted only once the boarding exit delay has passed since the deposit confirmed, and it moves the units and the deposit's full Bitcoin value to a new tapd address. The claim pays no fee itself; the daemon wallet pays it through a CPFP child and bumps it until it confirms. list-asset-deposits shows the claim_txid, and claimed once the claim is six blocks deep. A deposit that is claimed, or being claimed, can no longer board, and a deposit that a round holds cannot be claimed. Retrying returns the same claim. The daemon refuses claims unless tapd confirms transfers spending assets it does not hold, first released in v0.9.0.

wavecli dev daemon claim-asset-deposit --request-id alice-1

ark board / dev daemon Board

Trigger the client to join the next round with any confirmed boarding UTXOs.

Flag Type Description
--target-vtxo-count uint32 Fan boarded balance into N VTXOs (default 1)
--no-persist bool Skip restart-safe replay (testing only)
wavecli ark board
wavecli ark board --target-vtxo-count 4

ark oor receive — incoming OOR pubkey

Allocate an out-of-round receive script backed by a newly derived wallet key, or replay the exact allocation for a previously used idempotency key. Returns pk_script_hex, pubkey_xonly_hex, the wallet key locator, and the absolute indexer registration expiry.

Flag Type Description
--label string Optional indexer registration label
--idempotency-key string Return the same allocation on exact retry
wavecli ark oor receive
wavecli ark oor receive --idempotency-key my-app:durable-operation-42

Idempotency keys share one daemon-wide namespace. Prefix them with an application or tenant identity when several callers use the same daemon.

ark vtxos list

List VTXOs known to the wallet with optional filters.

Flag Type Description
--status string, repeatable Filter by status: live, pending_forfeit, forfeiting, forfeited, spent, unilateral_exit, failed, spending, pending_round, expired. Default: every status except forfeited and spent
--all bool List every status; checkpoint PSBTs only with --fields oor_final_checkpoint_psbts
--min-amount int64 Minimum amount in sats
--fields string Comma-separated field names to include
--ndjson bool Emit one JSON object per VTXO (newline-delimited)

Only live VTXOs are spendable; use balance for balances.

# VTXO inventory: every status except forfeited and spent
wavecli ark vtxos list

# Every VTXO the wallet has ever held
wavecli ark vtxos list --all

# Live VTXOs above 10k sats, only outpoint and amount
wavecli ark vtxos list --status live --min-amount 10000 \
  --fields outpoint,amount_sat

# Consumed VTXOs only
wavecli ark vtxos list --status forfeited,spent

# Streaming NDJSON for piping to jq
wavecli ark vtxos list --status live --ndjson | jq '.amount_sat'

ark vtxos refresh

Queue VTXOs for refresh in the next round and (by default) join that round immediately. Pass --no-join to leave the intent queued in PendingRoundAssembly so it can batch with subsequent refresh / leave RPCs; commit the batch later with ark rounds join.

Automatic expiry handling delays to the advertised window boundary when doing so preserves the wallet's dynamic critical threshold and minimum cooperative retry buffer. If the operator configures a narrower, later window, the wallet keeps its normal earlier threshold and pays the quoted fee instead of weakening unilateral-exit safety. When one VTXO reaches its trigger, the wallet coalesces eligible siblings from the same expiry batch into one automatic round registration, even when their individual trigger heights are staggered. A sibling remains deferred when it still has a safe future free-refresh window worth preserving. The daemon learns the window from its cached operator terms at bootstrap (and on later terms refreshes). When the cached boundary fires, it fetches a fresh GetInfo snapshot and rechecks the current window before reserving the VTXO. An operator fee-schedule hot reload is not pushed proactively to already-connected wallets.

A refresh is charged an operator fee (on-chain share + liquidity + margin), set by the server-issued quote at seal time and auto-accepted up to the daemon's maxoperatorfeesat cap. --dry-run previews an itemized advisory estimate for the selected VTXOs — per outpoint: amount, remaining lifetime, liquidity / on-chain / margin components — resolved entirely from the daemon's own view, with no manual amount or remaining-blocks input. Selections fully inside the operator's advertised free-refresh window preview with a zero total (free_refresh_eligible), while the per-outpoint rows keep the ordinary paid quote to show what the waiver saves. The estimate is best-effort: if the operator or chain tip is unreachable the preview still returns with estimate_error set. The binding fee remains the seal-time quote and may differ from any estimate.

Automatic maintenance waits for the advertised free-refresh window and only accepts quotes with zero total fee. Every re-quote is checked as well. Paid refresh remains available through an explicit CLI/RPC request, subject to maxoperatorfeesat. The old autorefreshfeefloorsat and autorefreshfeerateppm settings are deprecated and cannot authorize automatic fees. Nonzero legacy values produce one startup notice and do not enforce an automatic fee budget. If manual and automatic requests share a round, its entire fee must be zero; otherwise the round is rejected and its reservations are released. Retry the manual request separately in that case.

A rejected automatic quote waits six blocks before retrying while the VTXOs are still safe. Critical and expired recovery bypass that cooldown, but still require a zero-fee quote. A zero advertised window disables automatic refresh before expiry. Operators must open the free window before wallets reach their critical-exit boundary, with enough time for retries. A late or disabled window does not postpone critical exits, which can incur on-chain mining fees.

For scheduled batches, plan against the usable maintenance time: min(free window, wallet refresh threshold) - critical threshold. That time must cover each attempted slot's wait through its cutoff, the signing ceremony and confirmation, six blocks per retry cooldown, and an additional margin. A missed slot can require waiting another full interval; the registration window is part of that interval, not a separate interval. For example, an 864-block window with a 738-block wallet target and a 186-block critical threshold leaves 552 blocks for attempts before critical handling. An offline wallet returning later has less time. Schedule intervals use wall clock time, while expiry uses block height: convert delays conservatively for planning; a ten-minute average block time is not a safety guarantee. Widening the free window alone cannot fix a wallet target that starts too late.

While waiting outside the window, the wallet polls updated terms every six blocks rather than every block. Allow that discovery delay when changing a window. Entering an already-known window fetches fresh terms immediately, and critical-exit assessment is never postponed by this polling limit.

Swap-enabled builds also default maxpaymentcltv to 480 blocks. Automatic maintenance adds that payment window above each VTXO's dynamic unilateral-exit budget and 72-block refresh-retry buffer. For example, a VTXO with a 186-block critical threshold targets 186 + 72 + 480 = 738 blocks remaining, but waits if the free window has not opened. Set the value to zero to keep only the ordinary exit-safety policy. This is a liquidity-readiness target, not an override of send-time expiry validation: a payment whose actual route needs more lifetime still fails safely. If a fresh, round-direct VTXO cannot provide the configured reserve plus one 72-block healthy window, the wallet logs the mismatch and keeps the ordinary refresh threshold. Repeated refreshes cannot extend an operator's batch lifetime, so they would only create unnecessary rounds. An OOR descendant keeps the reserve because one refresh can replace its inherited partial lifetime with a new full batch.

An interactive real refresh shows the estimate and asks for confirmation. On non-interactive stdin (agents, pipelines) the command refuses to prompt: pass --yes (explicit consent) or --dry-run (preview). This mirrors the leave --all consent gate.

Flag Type Description
--outpoint string[] VTXO outpoint(s) to refresh (txid:index)
--all bool Refresh all live VTXOs
--dry-run bool Validate without queuing and preview the estimated fee
--yes bool Skip the interactive fee confirmation
--no-join bool Skip the implicit ark rounds join follow-up
# Preview the itemized fee estimate without queuing anything
wavecli ark vtxos refresh --outpoint <txid:idx> --dry-run

# Explicit outpoints (auto-joins the next round; prompts with the
# estimate on a TTY, requires --yes when stdin is not interactive)
wavecli ark vtxos refresh --outpoint <txid:idx> --yes

# Batch with other intents — explicitly join later
wavecli ark vtxos refresh --outpoint <txid:idx> --yes --no-join
wavecli ark vtxos leave   --outpoint <txid:idx> --no-join \
  --address bcrt1p...
wavecli ark rounds join

ark send inround

Send via in-round refresh (waits for next round to commit).

Flag Type Description
--to string[] Recipient address(es) (bech32m)
--pubkey string[] Recipient x-only pubkey hex(es); paired after --to entries
--amount int64[] Amount(s) in sats (one per recipient, --to then --pubkey order)
--dry-run bool Validate without submitting
--yes bool Approve submitting the real transfer without a prompt
wavecli ark send inround --to bcrt1p... --amount 50000 --yes

# Multiple recipients
wavecli ark send inround \
  --to bcrt1p...addr1 --amount 50000 \
  --to bcrt1p...addr2 --amount 30000

# Via JSON input
wavecli ark send inround --request-json '{
  "recipients": [
    {"address":"bcrt1p...","amount_sat":50000},
    {"address":"bcrt1p...","amount_sat":30000}
  ]
}'

ark send oor

Send via out-of-round transfer (immediate, through operator).

Flag Type Description
--to string Recipient address (one of --to / --pubkey)
--pubkey string Recipient 32-byte x-only pubkey hex
--amount int64 Amount in sats
--idempotency-key string Caller-provided key for retry-safe sends
--dry-run bool Validate without initiating
--yes bool Approve initiating the real transfer without a prompt
wavecli ark send oor --pubkey <pubkey_xonly_hex> --amount 25000 --yes
wavecli ark send oor --pubkey <hex> --amount 25000 \
  --idempotency-key my-attempt-1

send <invoice-or-address> (wavewalletrpc)

Unified send for Lightning invoice (--offchain, default) or onchain send (--onchain). Onchain sends are atomic: the destination receives exactly --amt sats and any residual lands back in the wallet as a change VTXO. Use --sweep-all with --amt=0 to drain every live VTXO to the destination instead.

By default send blocks until the payment reaches a terminal state (printing the Lightning preimage on success); pass --no-wait to return as soon as the send is dispatched. Non-interactive callers must pass --force or --yes to skip the confirmation prompt.

Flag Type Description
--offchain bool BOLT-11 dispatch via swap subsystem (default)
--onchain bool Atomic onchain send via SendOnChain
--amt uint Amount in sats (required for onchain unless --sweep-all)
--max-fee uint Max swap fee in sats (invoice sends only)
--max-credit uint Offchain only: max total server credit to reserve; 0 disables credit use
--max-credit-topup uint Offchain only: max new Ark value to move into server credit; 0 disables top-ups
--note string Caller-supplied label
--sweep-all bool Onchain only: drain wallet; --amt must be 0
--force / --yes bool Skip the interactive confirmation prompt
--no-wait bool Return once dispatched instead of blocking to a terminal state
--dry-run bool Prepare and print the preview without dispatching
wavecli send lnbcrt... --offchain --force
wavecli send lnbcrt... --offchain --max-credit 1000 \
  --max-credit-topup 1000 --force
wavecli send bcrt1... --onchain --amt 1000 --force
wavecli send bcrt1... --onchain --sweep-all --force

exit (cooperative by default, unilateral with --force-unroll-ack)

Cooperatively exit a VTXO by default; queues the outpoint for cooperative leave and joins the next round. Unilateral (on-chain) unroll only starts when --force-unroll-ack is set to the exact string I_KNOW_WHAT_I_AM_DOING. exit replaces the legacy unroll verb at the user surface.

Flag Type Description
--outpoint string VTXO outpoint to exit (txid:vout)
--onchain-address string Cooperative leave destination; omitted generates a fresh wallet address
--force-unroll-ack string Must be exactly I_KNOW_WHAT_I_AM_DOING to force unilateral unroll
--dry-run bool Validate locally and print the preview without dispatching
wavecli exit --outpoint <txid:vout>
wavecli exit --outpoint <txid:vout> --force-unroll-ack I_KNOW_WHAT_I_AM_DOING
wavecli exit status --outpoint <txid:vout>
wavecli exit summary
wavecli exit plan --outpoint <txid:vout>

exit status reports progress for a forced unilateral unroll job (it survives daemon restarts); exit summary aggregates all in-progress exits; exit plan previews backing-wallet funding readiness before forcing an exit. Status enum (UNROLL_JOB_STATUS_*) still uses the old "unroll" naming.

exit export (signed ancestry for external broadcasting)

Export one VTXO's signed transaction ancestry for an external service to broadcast and fund with child-pays-for-parent (CPFP) transactions:

wavecli exit export --outpoint <txid:vout> --output ancestry.json
# Or write the same JSON to stdout:
wavecli exit export --outpoint <txid:vout> > ancestry.json

--output creates a new file with mode 0600 and refuses to overwrite an existing file or symlink. Export requires an unlocked, ready wallet and onchain:write permission because the bundle contains executable signed transactions. It does not require confirmed fee inputs in the user's backing wallet, allocate a funding address, contact the operator, broadcast anything, or start a local unroll job. The external service provides and signs its own confirmed Bitcoin inputs to fund the CPFP children.

The export does not reserve the VTXO or disable automatic refresh. Coordinate handoff before broadcasting: spending, refreshing, or forfeiting the VTXO can make an earlier bundle unusable. Sharing the file reveals its transaction ancestry and allows the recipient to initiate unrolling. The file contains no private keys. With --sweep-address, it also authorizes a final sweep to the chosen address.

Add --sweep-address ADDRESS to include a presigned final sweep. The daemon validates the address network and signs a version-3 timeout spend paying the full VTXO value to that address, plus a zero-value P2A fee anchor. The service waits for CSV maturity and funds its own CPFP child to relay the sweep. No onchain fee inputs or fee estimate are needed from the user. An unavailable signer, invalid payout, or asset-bearing VTXO fails a sweep export.

wavecli exit export --outpoint <txid:vout> \
  --sweep-address <bitcoin-address> --output exit.json

Without --sweep-address, export contains ancestry only and a final sweep still needs a later user signature. Both modes produce format version 2. Export does not start an unroll or reserve the VTXO.

Exports also carry the saved batch expiry height and public sweep evidence when available. Backup integrations can use WalletService.ExportExits to export several VTXOs with shared ancestry included once and a separate sweep destination per target.

See the bundle format and executor contract for field definitions, validation, timelock handling, and package submission.

ark listtransactions

List local transaction history from the daemon's persisted accounting and boarding-sweep records.

Flag Type Description
--from string Include entries at or after this ISO 8601 timestamp
--to string Include entries at or before this ISO 8601 timestamp
--limit uint32 Maximum entries to return
--offset uint32 Number of filtered entries to skip
--type string Optional filter: boarding, round, oor, or sweep
wavecli ark listtransactions --limit 25

wavecli ark listtransactions \
  --type oor \
  --from 2026-05-01T00:00:00Z \
  --to 2026-05-08T23:59:59Z

activity (wavewalletrpc)

The merged wallet activity feed: send / recv / deposit / exit history.

Flag Default Purpose
--pending Only entries still in flight
--kind Filter by kind (send,recv,deposit,exit); repeatable
--limit daemon default Page size
--cursor Page token from a prior page's next_cursor
--format table Output format (table, expanded/x, json)
wavecli activity
wavecli activity --pending --kind send,recv
wavecli activity --format json
wavecli activity --json
wavecli activity --cursor <next_cursor>
wavecli activity inspect <id>

The VTXO inventory and onchain history are not part of the activity feed. Use the ark subtree for those:

wavecli ark vtxos list          # VTXO inventory (all but forfeited/spent)
wavecli ark listtransactions    # raw transaction / onchain history
wavecli ark sweep list          # boarding-timeout sweep records

schema

Introspect available CLI commands and their parameters.

wavecli schema
wavecli schema ark.vtxos.list
wavecli schema --all

Every schema entry includes a stable output_schema_id and output_schema_version, plus a side_effect boolean and whether the same method is exposed through MCP. The unit suite walks the real cobra command tree and the live MCP tool list, so a declared flag or tool cannot silently drift from the executable surface.

mcp serve (wavewalletrpc)

Start an MCP (Model Context Protocol) server on stdio for AI agent integration. Exposes daemon RPCs as typed tool calls.

wavecli mcp serve

Note: Wallet management tools (create, unlock, genseed) are intentionally excluded from MCP to prevent sensitive material from transiting the protocol. Use the CLI directly for wallet operations. ark.oor.receive is exposed because it only allocates a fresh wallet-derived receive target and does not reveal seed material.

The everyday wallet send MCP tool is intentionally two-phase. First call send.prepare with the destination, rail, amount, fee limit, and note. It validates the payment and returns the exact preview plus a short-lived, single-use send_intent_id without moving funds. After inspecting that preview, call send with only that send_intent_id. The second call consumes the prepared intent and may move funds; it cannot silently replace the reviewed payment parameters. The raw ark.send.inround / ark.send.oor advanced tools are not two-phase — they dispatch in one call, gated only by their optional dry_run flag.

Regtest Quickstart

A complete end-to-end workflow on regtest using the default (no wavewalletrpc) build. With a wavewalletrpc-enabled daemon, swap the relevant commands per the references above.

# 1. Start a regtest bitcoind + Esplora (e.g., via Nigiri)
nigiri start

# 2. Start the daemon. Auto-unlock so you don't need wavewalletrpc to
#    create/unlock from the CLI.
waved \
  --network=regtest \
  --wallet.type=lwwallet \
  --wallet.esploraurl=http://localhost:3000 \
  --wallet.password_file=/path/to/password_file \
  --server.host=localhost:10010 \
  --server.insecure \
  --rpc.listenaddr=localhost:10029

# 2b. Alias the CLI for this regtest daemon: plaintext transport (no TLS,
#     no macaroon) on the regtest network. See the Authentication section
#     above for why both --no-tls and --no-macaroons are needed.
alias wave='wavecli --no-tls --no-macaroons --network=regtest'

# 3. Get a boarding address.
ADDR=$(wave dev daemon NewAddress | jq -r .address)

# 4. Fund it on-chain and confirm.
bitcoin-cli -regtest sendtoaddress "$ADDR" 0.01
bitcoin-cli -regtest -generate 6

# 5. Verify balance.
wave dev daemon GetBalance

# 6. Board into the next round.
wave ark board

# 7. After the round confirms, list the new VTXO.
wave ark vtxos list

# 8. Send funds (in-round to a peer's bech32m address).
wave ark send inround --to bcrt1p... --amount 5000

For per-client manual testing under the arktest harness, see MANUAL_TESTING.md at the server (lumos) repo root.

Password Handling

Wallet passwords are never accepted as CLI arguments. The priority order for password resolution:

  1. Environment variable -- WAVED_WALLET_PASSWORD=pass
  2. Password file -- --wallet-password-file=/path/to/file
  3. Explicit stdin -- printf '%s\n' 'pass' | wavecli unlock --password-stdin
  4. Interactive prompt -- prompted on TTY if none of the above

Plain piped stdin is never consumed as a password. Pass global --no-input when an invocation must never prompt or implicitly read input. CI=true also suppresses prompts while leaving stdout and stderr output unchanged. Explicit sources such as --password-stdin, a password file, or the environment remain available under --no-input.

For production deployments, use the password file approach with restrictive file permissions (chmod 600).

Troubleshooting

Problem Solution
daemon was not built with -tags wavewalletrpc Rebuild with make build-wavewalletrpc, or use the ark * / dev * subtrees
connection refused Daemon not running or wrong --rpcserver address
wallet not ready Run wavecli unlock (requires wavewalletrpc) or restart the daemon with --wallet.password_file
wallet already exists Wallet was already created; use unlock instead
GenSeed is only available in lwwallet/btcwallet mode Switch daemon to --wallet.type=lwwallet or btcwallet
read macaroon: ... no such file The CLI is looking under the wrong data dir/network. Pass --datadir / --network to match the daemon (see Authentication), or --macaroonpath directly
credentials require transport level security A macaroon can't ride a plaintext connection. Use TLS (drop --no-tls), or add --no-macaroons alongside --no-tls
TLS certificate errors Point --datadir / --network at the daemon's cert, set --tlscertpath, or use --no-tls --no-macaroons for regtest
password must be at least 8 bytes Wallet password minimum is 8 characters
decryption failed: wrong password Incorrect password or corrupted seed file

Health probes

Enable the dedicated health listener with --health.listen=127.0.0.1:10032. It serves only GET /readyz and GET /livez, without authentication or wallet RPCs. Use a private interface for a container supervisor. The listener starts before wallet and operator initialization. An empty address disables it.

State /readyz /livez
Starting, locked wallet, or waiting for startup dependencies 503 200
Startup complete and both core actors responding 200 200
Actor probe fails or last successful sample is over 7 seconds old 503 200 during grace
Either actor makes no progress for 2 minutes after startup 503 503
Shutdown 503 or connection closed 503 or connection closed

Full startup includes wallet-dependent actors, mailbox ingress, authenticated operator terms, and wallet-ready hooks. Slow startup reconciliation does not start the liveness failure timer. A permanently stuck startup therefore remains unready and needs operator diagnosis; these probes do not impose a startup restart deadline.

After startup, the daemon probes the round actor and virtual transaction output (VTXO) manager every 5 seconds, with one shared 2-second deadline. Each probe traverses the actor's ordinary receive queue and acknowledges without invoking business logic, storage, or another actor. Admission never waits for queue room. Each actor retains at most one pending probe even after a timeout. HTTP requests read cached status and cannot create additional probe work.

Liveness also observes each actor's completed-turn counter. A full queue or a late acknowledgement can fail readiness while the actor continues to make progress. Such backlogs stay live. Each actor has its own activity deadline, so progress in one cannot hide a stuck peer.

These checks measure local receive-loop responsiveness. They do not measure block freshness, wallet balances, transaction progress, or operator/LND reachability. Idle wallets and long block intervals remain healthy. Temporary external calls that delay an actor withdraw readiness but have 2 minutes to recover before liveness fails. An external call that itself parks a core actor for longer than that is indistinguishable from a local wedge. Use dependency monitoring separately; restarting waved cannot repair a remote service.

For Kubernetes, use /readyz to remove an unresponsive pod from service and /livez to permit a restart after sustained failure. Readiness alone cannot recover a wedged background client. Poll the cached endpoints every 5–15 seconds with a 2-second HTTP timeout. Install an image supporting health.listen before enabling these probes. To roll back to an older image, first disable the health flag and restore its previous probes. No database or RPC migration is required.