pecu

Configuration#

Everything resolves in one order: command-line flag → environment → config file → built-in default.

There is no config file until you make one; the testnet and mainnet profiles are built in. Files live under $PECU_HOME, else $XDG_CONFIG_HOME/verus-pecu, else ~/.config/verus-pecu — XDG rather than the platform convention, because ~/.config is where someone reaching for a terminal wallet will look.

# ~/.config/verus-pecu/config.toml
default_profile = "testnet"

[profiles.testnet]
node = "https://api.verustest.net"

# Built in, but shipped unable to spend: moving real coins from an example app
# should take a deliberate act, not a forgotten --profile.
[profiles.mainnet]
allow_spend = true

# Ceiling on a single RPC reply. A memory bound against a hostile or overloaded
# node, not a performance knob. 8 MiB covers any ordinary wallet; a long-lived
# mining address can need far more.
max_response_mb = 8

# How long to wait for a single RPC reply, in seconds. Short by default so a
# wrong URL fails while you are still looking at the terminal; a busy public node
# can take longer than that to answer `getaddressutxos`, and past the ceiling the
# read fails outright rather than slowly. Nothing to do with `id register
# --timeout`, which is in minutes and bounds a wait for confirmations.
timeout_secs = 20

A profile that appears only in the file inherits testnet's defaults for whatever it leaves out. Unknown keys are refused rather than ignored, so a typo is an error instead of a setting that silently does nothing.

$PECU_HOME also makes the tests hermetic — they point it at a temporary directory and cannot see, or damage, a real keystore.

Global flags#

Flag Meaning
--json Machine-readable output instead of the rendered UI
--dry-run Build and sign, but never broadcast
--explain Print the verus-sdk calls this command makes
-y, --yes Answer yes to every confirmation
--profile <NAME> Config profile (env: PECU_PROFILE)
--node <URL> Override the endpoint (env: VERUS_ENDPOINT)
--theme auto|phosphor|plain Phosphor on a TTY, plain when piped. NO_COLOR always wins
-v, -vv More logging

Under --json, stdout carries one JSON document and nothing else. Panels, notes, progress lines and the --explain record are not written there at all — --explain goes to stderr instead — so pecu … --json | jq never has to step over prose, and a consumer that stops reading gets the exit code back rather than a broken-pipe panic.

Two commands are exceptions, and both are cases where there is no document to print: dev ui renders the widget gallery, which is a picture of how things look and has no machine-readable form, and completions <shell> prints a shell script. Both accept --json, say so on stderr where it matters, and exit 0.

Failures in JSON#

A failing run prints a document too. The human report still goes to stderr, unchanged, and the exit code is still non-zero — the JSON is additional.

$ pecu wallet balance --address bob@ --json --node https://127.0.0.1:1
{
  "error": {
    "causes": [
      "transport: https://127.0.0.1:1/: Connection Failed: Connection refused"
    ],
    "code": "pecu::node_unreachable",
    "help": "check your connection, or point somewhere else with --node — …",
    "message": "looking up the identity failed against https://127.0.0.1:1"
  }
}
$ echo $?
3

code is the diagnostic's own pecu::… identifier — the same token stderr prints after Error:, not a second naming scheme derived from it. That is the thing to switch on. message and help are the sentences the report renders, and causes is the chain underneath the head, which is where the SDK's own wording lives. All four keys are always present; code and help are null when the diagnostic carries none, and causes is [].

Three commands build a document of their own and then fail: doctor, whose local half is worth having when the node is down; send, whose signed hex cannot be recovered afterwards; and sign, whose partial is what the next signer has to be handed. Each gets the error object folded into that document under the same top-level error key rather than followed by a second document, so .error.code reads the same on every failing --json run and every run prints exactly one document.

Exit codes#

Deliberately few. There are over a hundred distinct pecu::… diagnostic codes, and a status per diagnostic would be a contract nobody could keep — the code is the fine-grained discriminator, and it travels in the JSON. What the exit status answers is the coarser question a script branches on: is this worth retrying?

Code Meaning Retry?
0 It worked
1 The request was understood and the answer was no: a refusal, a missing key, an amount that will not parse, a daemon that answered with an error code No. Nothing about running it again is different
2 Usage error — clap could not parse the command line. Printed before anything else runs, so there is no JSON on this path No
3 The endpoint did not answer the question: nothing came back, or what came back was not an answer this build can use — a refused connection, a timeout, a proxy's HTML, a method the node will not serve. Nothing happened Yes, or point --node somewhere else
4 The outcome is genuinely unknown. A broadcast whose bytes may or may not be propagating No — check first. The document carries the txid and the signed hex; pecu tx explain <txid> says whether the chain has it

4 exists because 3 has to be safe to retry. A connection that breaks after a sendrawtransaction goes out is not a node that was never reached: the transaction may already be in a mempool, and a blind retry is a second payment. The SDK reports that case separately and so does this.

2 is narrower than "the command line was wrong". A flag that names something pecu understands and cannot do is declared and refused here rather than left unknown to clap, so it exits 1 with a pecu::… code and a JSON document — unexpected argument '--currency' found reads as a misspelling and carries neither. pecu plan send --currency and pecu plan send --from-identity moved from 2 to 1 for that reason.

2 still covers everything clap settles before a command runs: a flag the parser has never heard of, and a flag it has — these two included — given no value. pecu plan send -c exits 2 with a value is required for '--currency', because there is nothing yet to refuse by name.

The status and .error.code answer different questions, and on one diagnostic they look like they disagree. pecu::node_unreachable is a single diagnostic over every failed node request, including the ones the node answered — a daemon error code renders under that name and exits 1, because the request was understood. The status is the finer discriminator there; the code says which request failed, not what kind of failure it was.

Edit this page on GitHub