Tokens and scopes

Walletd uses bearer-token authentication on every request except GET /healthz. Comparison is constant-time (subtle::ConstantTimeEq).

Three scoped tokens

v1.0 issues three tokens, one per scope. On first start walletd auto-generates them at <datadir>/token-{read,manage,spend} (mode 0600).

ScopeMethods
readping, validate_address, get_* family, list_addresses, verify_message, get_status, get_wallet_balance
managegenerate_address, abandon_transfer
spendtransfer, send_raw_transaction, sign_message

Containment: spend ⊇ manage ⊇ read. A token at a higher scope satisfies every lower scope, so an exchange's withdrawal worker only needs the spend token — it gets manage and read for free.

Configuring

The default behaviour (auto-generate on first run) suits most setups. Override any subset from a secret manager:

exfer-walletd \
    --auth-token-read   "$(vault read -field=token secret/walletd-read)" \
    --auth-token-manage "$(vault read -field=token secret/walletd-manage)" \
    --auth-token-spend  "$(vault read -field=token secret/walletd-spend)"

Env equivalents: WALLETD_AUTH_TOKEN_READ, WALLETD_AUTH_TOKEN_MANAGE, WALLETD_AUTH_TOKEN_SPEND. Setting any of them suppresses auto-file creation for that scope.

Typical splits

ComponentToken to issue
Deposit watchertoken-read
Address provisioningtoken-manage
Withdrawal workertoken-spend
Operator dashboard / SREtoken-read

A leaked read token can survey balances and pubkeys but cannot mint addresses or spend. A leaked manage token can mint addresses but cannot spend or sign messages. A leaked spend token is "every wallet, all funds" — guard accordingly.

Bind safety

Walletd enforces at startup:

Bind addressPolicy
Loopback (127.0.0.1, ::1)Always allowed.
Private (RFC1918, ULA, link-local)Allowed; warns if no token is set.
Public (any global IP, 0.0.0.0, ::)Refused unless --tls OR --allow-public-bind.

The reason public binds need an opt-in: by default walletd doesn't terminate TLS, and a plaintext bearer token on the public wire is fatal. --tls (walletd terminates TLS itself, see Quick start → Production) solves it directly; --allow-public-bind is your assertion that an external TLS terminator sits in front. Without one, walletd fail-closes.

Next