Qanum RPC Reference
JSON-RPC methods, wire formats, authentication and error codes.
This page reproduces docs/RPC.md from qanum-core v0.9.0 (release tag). Links to files that are not published on this site are shown as plain text until the source repository is public.
Contents
How to talk to a Qanum node over RPC: authentication, wire formats, every method, parameters, responses and error codes.
For practical usage see USER-GUIDE.md; for configuration see CONFIGURATION.md.
Compatibility: method names, parameters, error codes and the JSON-RPC 1.0 envelope follow the de-facto conventions of the Bitcoin node ecosystem, so existing explorers, tooling and scripts work against a Qanum node. Qanum-specific methods and fields are marked as extensions.
1. Transport and authentication
RPC auth cookie: with
--rpcauth-requiredon (the default), the daemon writesdatadir/.cookieat startup and requires it on every request. The cookie file persists for the lifetime of the node (it is regenerated on each start); the client reads it automatically from--datadir(or--rpccookiefile). Without a valid cookie the server answers HTTP 401.
Wallets are required: wallet RPCs that create keys or spend (
getnewaddress,keypoolrefill,sendtoaddress,sendmany) fail withWallet '<name>' does not exist (use createwallet first)if the selected wallet has not been created.
The RPC server listens on loopback by default (port depends on network, see USER-GUIDE.md §3).
Authentication options:
- HTTP Basic (
--rpcuser/--rpcpasswordon the daemon):curl -u user:pass http://127.0.0.1:8338/ -d '{"method":"getblockcount","params":[],"id":1}' - Cookie (default when no user/password is set): the daemon writes
datadir/.cookiewithuser:password;qanum-clireads it automatically. - None: only if
--rpcauth-required=0.
Authentication verification is timing-safe (SHA3-256 digests compared with accumulated XOR, no early exit).
Amount fields (amount, total_amount, and per-entry amounts in
listunspent/listtransactions/gettransaction/listsinceblock) are
exact decimal strings derived from the integer jomas value (e.g.
"123.45678901"), never floating point (as in Bitcoin Core #8317).
Every amount-bearing result also includes the exact integer field
amount_jomas (or total_amount_jomas).
2. Wire formats
Two formats are accepted:
2.1 Compatible JSON-RPC 1.0 (for explorers / scripts)
{"jsonrpc":"1.0","method":"getblockcount","params":[],"id":1}
Response:
{"result": 42, "error": null, "id": 1}
Errors use the ecosystem-standard error codes (see §5). This is the format served for the methods listed in §3.
2.2 Native Qanum format (what qanum-cli uses)
{"method":"get_block_count","params":{}}
The native format is a typed envelope: method is the snake_case tag of the
request and params is an object (or array) matching the request fields. It
supports the same methods plus the wallet/custody/debug ones that the CLI
exposes (build_payment, submit_tx, datadir_info, reorg_plan, …).
Prefer the JSON-RPC 1.0 format for anything that is not qanum-cli.
3. Methods (JSON-RPC 1.0 string)
Blockchain
| Method | Params | Returns |
|---|---|---|
getblockcount |
— | height of best tip (int) |
uptime |
— | seconds since the node started (int) |
stop |
— | "Qanum server stopping" (string). Requests a graceful shutdown. The node replies before it stops, so the caller always gets the confirmation; the process exits about a second later with status 0. Without a running qanumd daemon behind the RPC server the call returns an error instead of pretending. |
estimatesmartfee |
[conf_target?] |
fee-rate estimate {feerate (QAN/kvB), blocks}. It uses a confirmation-history estimator (median fee rate of txs confirmed within ≤ conf_target blocks, persisted in datadir/feerate_history.json), with fallback to the percentile of live mempool fee rates while the history is thin; floored at the node’s min fee. A missing conf_target defaults to 6. A present but invalid value (-1, "x", non-integer) is -8 (Amount out of range: conf_target must not be negative / conf_target must be a non-negative integer), not a silent fallback. |
getbestblockhash |
— | hash of best tip (hex string) |
getblockhash |
[height] |
hash at height (hex string) |
getblockchaininfo |
— | chain info (see below) |
getblock |
[identifier, verbose?] |
block JSON (verbose=1, default) or raw hex (verbose=0) |
gettxoutproof |
[txid, blockhash?] |
Merkle inclusion proof: {txid, blockhash, root, index, path[]} (Qanum extension: includes root/index, unlike a flat sibling array) |
getblockfilter |
[blockhash, filtertype?] |
compact block filter (BIP157/158, basic type): {filter, header} both hex. With -blockfilterindex both are served from the persistent index in O(1); without it the first call computes the filter, chains the header from the genesis and lazily populates the index (subsequent calls O(1)). Requires undo data for non-coinbase-only blocks. filtertype: only basic (default) |
getblockstats |
[hash_or_height, stats?] |
block statistics: blockhash, height, time, mediantime (median of the block’s time and its 10 ancestors’, as Core), txs, ins, outs, total_size, total_weight (= 4×size; no segwit), subsidy, total_out, totalfee/avgfee/minfee/maxfee/medianfee (from undo data), fee rates in jomas/byte + percentiles, sw*=0, utxo_increase. The stats array filter is accepted but ignored (all fields returned). |
getblockheader |
[identifier, verbose?] |
header JSON or raw hex (152 bytes). JSON: hash, confirmations, height, version, versionHex, version_hex, merkleroot, auth_root, utxo_root, time, mediantime, nonce, bits, difficulty, chainwork, nTx, previousblockhash, nextblockhash — the chain fields mean the same as in getblock (below) |
getchaintxstats |
[nblocks?, bestblock_hash?] |
transaction statistics over a window of nblocks blocks (default 1, >= 1) ending at bestblock_hash (default: tip, must be on the active chain else -5): time (tip → current time, else final block timestamp), txcount (cumulative txs up to the final block), window_final_block_hash, window_final_block_height (Qanum extension), window_block_count, window_tx_count, window_interval (seconds), txrate (tx/s; 0 if interval 0). nblocks < 1 → -8 (Invalid number of blocks). Cost note: txcount scans the stored canonical block files (one short header+count read per block — no full parse, no txindex); pruned/missing canonical data returns -1 with a clear message instead of undercounting. |
gettxout |
[txid, vout] |
UTXO JSON or null |
gettxoutsetinfo |
— | UTXO set statistics: height, bestblock, txouts, transactions (txs with unspent outputs), bogosize, hash_serialized_3 (Qanum-specific SHA3-256 hash, not comparable to Core’s), disk_size, total_amount (+ total_amount_jomas extension) |
getindexinfo |
[index_name?] |
sync status of the enabled optional indexes. Each entry is { synced, best_block_height }. Keys: txindex, addressindex, blockfilterindex (only enabled indexes appear). With index_name, returns only that index (empty object if not enabled). |
getblockstoreinfo |
— | Block-store cache + persistent index: { enabled, entries, listings, builds, hits, misses, last_build_duration_ms, index_enabled, index_source, index_journal_records, chainstate_flush, chainstate_dirty, chainstate_flushes, chainstate_flush_enabled }. listings is the process-wide count of real blocks/ directory scans (0 on a clean restart with blockstore_index=1; 1 when the index is missing, dirty, forced with -checkblockindex, or rebuilt). index_source is snapshot, rebuild or memory. -blockstore_cache=0 reports enabled: false. D-0 fields report the deferred chainstate.bin policy. |
getchaintips |
— | known chain tips (height, hash, branchlen, status: active, valid-fork, headers-only or invalid, as in Bitcoin Core) |
getaddressbalance |
[address] |
address balance (requires -addressindex) |
getaddresstxids |
[address, start?, end?] |
txids touching the address (requires -addressindex) |
getaddressutxos |
[address] |
unspent outputs of the address (requires -addressindex) |
getblockchaininfo returns the ecosystem-compatible fields plus Qanum
extensions (ignored by generic explorers):
{
"chain": "regtest", "blocks": 42, "headers": 42,
"bestblockhash": "0000…", "difficulty": 524288.4375,
"mediantime": 1786116723, "verificationprogress": 1.0,
"chainwork": "00000000000000000000000000000000000000000000000000000000002b0002", "pruned": false, "size_on_disk": 5224,
"softforks": {"suites5": {"type": "buried", "active": true, "height": 0}},
"warnings": [],
"datadir": "/tmp/node", "snapshot": "/tmp/node/chainstate.bin",
"stored_blocks": 43, "utxos": 120, "headers_count": 43,
"tip_bits": "0x1e0fffff",
"asert_anchor_height": 0, "asert_anchor_timestamp": 1700000000,
"asert_anchor_bits": "0x1e0fffff"
}
softforks lists the signature-suite deployments as Bitcoin Core lists its
buried deployments: suites5 always and, when a retirement height is
scheduled (testnet, or regtest with -testactivationheight=retire12@<h>),
retire12 too. active is true when the next block (tip + 1) is at
or above height.
blocks and headers are both heights: on a fully
synchronised chain they are equal and verificationprogress is exactly 1.0.
mediantime is the median time past of the tip (the tip’s time and its 10
ancestors’), as in Bitcoin Core; it is the time a new block must exceed.
A freshly initialised node with only the genesis block reports
blocks = headers = 0. The Qanum-specific headers_count field is the number
of stored headers, i.e. headers + 1 (it counts the genesis).
Node warnings
getblockchaininfo, getnetworkinfo and getmininginfo return warnings
as a list of strings, as Bitcoin Core v28+ does: [] when there are none
(there is no -deprecatedrpc=warnings string form). When a warning is
raised, the node also logs it and runs -alertnotify once
(CONFIGURATION.md). There are two warnings:
- Invalid chain with more work (Bitcoin Core
LARGE_WORK_INVALID_CHAIN, same rule and text):"Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade."It is active while the best invalid header has more than six blocks of work (at the tip’s difficulty) over the active tip, and it clears when that is no longer true. It is not evaluated during initial block download. Usual causes: this node runs other consensus rules than the miners, or its chainstate is corrupt. A deep enoughinvalidateblockalso raises it, andreconsiderblockclears it. - Deep reorganization (Qanum extension; Core has no such warning):
"Warning: Deep chain reorganization: a chain with more work required disconnecting <n> blocks (more than 100). Check your node against other sources."It is raised when a reorg disconnects more than 100 blocks (DEEP_REORG_WARNING_DEPTH). The reorg itself is accepted, never refused. The warning stays until the node restarts; a later deep reorg does not replace it or run-alertnotifyagain.
getblock JSON includes (by height or by 64-hex hash): hash, confirmations,
size, height, version, versionHex, version_hex, merkleroot,
auth_root, utxo_root, time, mediantime, nonce, bits, difficulty,
chainwork, nTx, previousblockhash, nextblockhash, tx[], raw_hex,
pruned, plus Qanum extensions path, tx_count, coinbase_txid. The
fields that depend on the chain follow Bitcoin Core:
confirmations: depth on the active chain (1 = tip); -1 for a block that is not on the active chain (a stale branch);versionHex: the version as 8 hex digits, no prefix (00000001);version_hexkeeps Qanum’s0x-prefixed form;chainwork: cumulative work from genesis up to and including the block, 64 hex digits (the same format asgetblockchaininfo);mediantime: median of the timestamps of the block and its 10 ancestors;nTx: number of transactions (same value astx_count);nextblockhash: the next block on the active chain; omitted for the tip and for a block off the active chain.
For a stale block, chainwork and mediantime follow the block’s own
branch (read back through the block store until it joins the active chain),
not the active blocks at the same heights. They are null only if that
ancestry is no longer in the block store.
Chain state (local)
These three change only this node’s view of the chain. They are not consensus rules and they are not gossiped: no peer learns that you marked a block invalid, and a restart preserves the marks (they live in the datadir). Semantics match the widely-deployed invalidate/reconsider convention.
| Method | Params | Returns |
|---|---|---|
invalidateblock |
[blockhash] |
ApiInvalidateResult (see below) |
reconsiderblock |
[blockhash] |
ApiInvalidateResult — removes the mark set by invalidateblock on the block and on its marked ancestors; a descendant invalidated on its own keeps its mark (Bitcoin Core also clears marked descendants) |
getinvalidblocks |
— | array of currently-invalidated block hashes (Qanum extension) |
invalidateblock marks a block and everything built on it as invalid. If the
marked branch was the active chain, the node reorganises onto the best
remaining branch: the abandoned blocks are detached and their non-coinbase
transactions are returned to the mempool. Marking a block that is not on the
active chain is legal and simply records the mark.
The best known header follows the marks, as in Bitcoin Core: after
invalidateblock it leaves the marked branch, and after reconsiderblock it is
recalculated from the active tip (it can return to the reconsidered branch).
The marks are applied again at start, so a peer that later sends a header of a
marked branch, or one built on it, has it refused (duplicate-invalid /
bad-prevblk) and its headers are ignored for a short while.
As in Bitcoin Core, both RPCs end in ActivateBestChain. Every block they
connect (the new best branch after invalidateblock, the reconsidered branch
after reconsiderblock) passes full block validation with the node’s
--verifier; a stored block that fails is discarded and is not attached, and
the next best branch is tried. attached lists only blocks that passed
validation.
If the node’s own undo data for a block it has to detach is damaged, the RPC
returns that error (the undo data of block … is damaged …, with the
qanumd reindex advice) and changes nothing: the chain and the saved
chainstate stay as they were (Bitcoin Core InvalidateBlock returns the
DisconnectTip failure the same way).
Running-daemon semantics (iteration 9). Against a live daemon both RPCs
are applied through the chain actor: the actor flushes its in-memory state,
persists the invalid set (datadir/invalid_blocks.json) and disconnects with
the same code path as the one-shot command, reloads its memory from disk and
runs the node’s own ActivateBestChain (the one the daemon runs at start). The mark therefore survives immediately, is visible to getinvalidblocks,
is not undone by subsequent block imports or local mining, and survives daemon
restarts. Against a stopped datadir (one-shot CLI mode) they operate directly
on disk, as before.
Both accept the hash positionally ([hash]) or by name ({"blockhash": …},
{"hash": …} also works). A missing hash is -8
(missing blockhash parameter); an unknown or malformed hash is -5.
Both return the same object, which reports exactly what moved:
{
"blockhash": "0000…",
"previous_tip": "0000…", "new_tip": "0000…",
"previous_height": 1158, "new_height": 1155,
"detached": ["0000…", "0000…", "0000…"],
"attached": [],
"mempool_readded": 2,
"invalid_blocks": ["0000…"]
}
previous_tip equals new_tip when the call changed no active chain (for
example, invalidating a block on a stale branch). detached and attached
are in the order applied. mempool_readded counts transactions put back into
the mempool from detached blocks — coinbases are not readded.
As in Bitcoin Core (InvalidateBlock), every transaction put back is fully
validated, signatures included, and is exempt from the fee floor but not
from the mempool size limits; the node keeps its mempool policy
(-minrelaytxfee, -maxmempool, orphan limit). Only the transactions of
the first 10 disconnected blocks (the ones nearest the old tip) return; a
deeper invalidation logs invalidateblock: N blocks disconnected;
transactions of the M deepest are not returned to the mempool.
Typical use is testing reorg handling on regtest without needing a second node or a competing miner:
qanum-cli -regtest invalidateblock $(qanum-cli -regtest getblockhash 1156)
qanum-cli -regtest getinvalidblocks
qanum-cli -regtest reconsiderblock $(qanum-cli -regtest getblockhash 1156)
Generating
| Method | Params | Returns |
|---|---|---|
generatetoaddress |
[nblocks, address] |
array of block-hash hex strings, in order. Regtest only. As Bitcoin Core generateBlocks: for each block, the node’s one template builder (the checked template of getblocktemplate, with TestBlockValidity), the nonce search with no chain lock, then the same import as any received block (full validation, best-chain activation); the new tip is announced to connected peers. Regtest skips durable fsync and the header hash-vs-target check (nonce 0); testnet/mainnet keep both. Other regtest nodes accept these blocks; a node that still checks PoW at 0x1e0fffff will reject them. Cost per block is the normal import (release build on a 6-core aarch64 board: about 19 ms near genesis, 50–63 ms at height 2,000–6,000). Calls are served one at a time on a dedicated thread, so they do not delay wallet reads, transaction submission or getblocktemplate. The reward address is validated even when nblocks = 0 (returns []). An invalid, wrong-network, or non-P2PQK address is -8 (invalid reward address: …) with no Rust Debug wrapper. An ML-DSA-87 address is accepted once the level-5 suites are active at the block’s height; before that it is -8 (block generation failed: reward address suite ML-DSA-87 is not active yet: …). Likewise an ML-DSA-44 address from the retire12 height is -8 (… ML-DSA-44 is retired: …); the built-in miner pauses with the same message instead of producing an invalid block. Called outside regtest is also -8. A template that fails validation (block generation failed: Couldn't create new block: block template failed TestBlockValidity: …, the node error’s text, not its Rust debug form) or a block the chain rejects (block generation failed: ProcessNewBlock, block not accepted: …) is -32603; blocks generated before the failure stay, as in Core. The RPC waits up to 240 + 240 * nblocks seconds; a timeout or a node without net_v2 is -32603. Clients on slow hardware/CI can raise their read timeout with qanum-cli --rpc-timeout=<secs>. |
Mining
| Method | Params | Returns |
|---|---|---|
setgenerate |
[enabled] |
start (1) / stop (0) the built-in CPU miner at runtime. Any network (real PoW mining, unlike generatetoaddress) |
getblocktemplate |
— | block template for an external miner: version, previousblockhash, bits, target (BE hex), height, curtime, mintime, mutable, tx (txids), coinbasevalue, total_fees, block_hex (serialized candidate, nonce=0). As Bitcoin Core (BIP 23): mintime is the tip’s median time past + 1 and mutable is ["time", "transactions", "prevblock"]. The difficulty (ASERT) uses the parent’s time and height, so a miner may set any time in [mintime, now + 2 h] and keep the template’s bits. Any network. The coinbase reward goes to the daemon’s --reward-key-id / --reward-address if configured. Every template is checked like Bitcoin Core’s TestBlockValidity before it is returned: if the candidate is not a valid block (a local defect, e.g. in the mempool data) the call fails with -1 failed to build block template: block template failed TestBlockValidity: <reason>, logged once as ERROR: block template failed TestBlockValidity: …; the same error is returned without rebuilding until the tip or the mempool changes. Refresh policy (as Bitcoin Core): the node keeps the last template and rebuilds it only when the tip changed, or the mempool changed and 5 s passed since it was built, or another reward key is configured; otherwise it returns the same template with only curtime moved forward (bits and mintime stay): same tx, merkle roots and utxo_root, block_hex with the new timestamp. A transaction accepted less than 5 s after the last rebuild can therefore take up to 5 s to appear. Building a template no longer copies the UTXO set: about 70–80 ms with transactions at 54k UTXOs (was 6–15 s), a reused one about 35 ms |
submitblock |
[blockdata] |
submit a block solved by an external miner (validates PoW, then imports via the chain actor); returns null on success. Any network |
getmininginfo |
— | mining state: blocks, gen, nblocks_mined, pooledtx, difficulty (tip difficulty — same calculate_difficulty as getblockchaininfo/getblockheader), hashrate (LOCAL miner, average since the node started: hashes attempted since start / node uptime. It is not the current rate: if mining starts late (after sync or setgenerate true) it starts near 0 and rises slowly, and after setgenerate false it decays slowly instead of dropping to 0. For the current rate, take two readings of hashrate × uptime (uptime RPC) a few seconds apart and divide the difference by the time between them; embedded apps use total_hashes() the same way, see qanum-embed/INTEGRATION.md), networkhashps (NETWORK estimate = getnetworkhashps(120, -1)), workers (Qanum extension: effective nonce-scan threads of the built-in miner = --mining-workers capped at the CPU threads; 1 by default), chain, warnings (list of the active node warnings, [] when there are none; see Node warnings). The conditional currentblockweight/currentblocktx fields (present only after a template was ever assembled) are intentionally NOT emitted |
getnetworkhashps |
[nblocks=120] [height=-1] |
estimated network hashes/s from chain work over the window’s time span (Core v28 GetNetworkHashPS: (work[pb] − work[pb−lookup]) / (maxTime − minTime); guards: nblocks 0/<-1 → -8, height > tip → -8, genesis or flat-timestamp window → 0, nblocks > height → clamped). ASERT deviation: nblocks=-1 = whole chain back to genesis (Qanum retargets every block; Core’s “since last difficulty change” window does not exist). Same value on every node at the same height. |
Mempool / transactions
| Method | Params | Returns |
|---|---|---|
getmempoolinfo |
— | loaded, size (tx count), bytes (serialized), usage, maxmempool (live serialized-byte cap, default 32 000 000), total_fee (jomas), mempoolminfee / minrelaytxfee (QAN/kvB), plus Qanum extensions datadir and mempool_path. The pool is also capped at 40 000 entries (overridable: -maxmempoolentries / maxmempoolentries=). After eviction, a new transaction that still does not fit is rejected (MempoolFull), not accepted. |
getrawmempool |
[verbose?] |
array of txids (verbose=0) or txid→detail map (verbose=1: size, vsize, weight, fee (jomas), fee_rate (jomas/byte, float), height, time (unix seconds of entry; 0 for v1 snapshots), depends[], spentby[], descendants[], descendantcount, ancestorcount) |
getmempoolentry |
[txid] |
detail of one mempool tx (same fields as verbose); -5 if not in mempool |
getmempoolancestors |
[txid, verbose?] |
ancestors of a mempool tx: array of txids (verbose=0) or txid→detail map (verbose=1); excludes the tx itself; -5 if not in mempool |
getmempooldescendants |
[txid, verbose?] |
descendants of a mempool tx (same shape); excludes the tx itself; -5 if not in mempool |
settxfee |
[fee_rate] |
set the node’s payment fee rate in jomas per byte (persisted in datadir/paytxfee); 0 clears it. A send with fee=0 then charges rate × estimated size. Returns true |
prioritisetransaction |
[txid, dummy, fee_delta] |
adjust the mining priority of a mempool tx (fee_delta in jomas, accumulated, may be negative; the dummy arg is ignored). Affects block-template selection only; -5 if not in mempool. Returns true |
getrawtransaction |
[txid, verbose?] |
tx hex or JSON; requires -txindex for mined txs. In verbose mode size/vsize are the real serialized byte length and weight is 4 × size, for both mempool and confirmed transactions |
sendrawtransaction |
[hexstring, verifier?] or {hexstring, verifier?} |
txid (hex string); verifier only accepts oqs (the default): any other value is refused with “not available” (verification is mandatory). A transaction with an output that uses a level-5 suite (ML-DSA-87 0x0104, SLH-DSA-SHAKE-256s 0x0203) before the chain activates it (tip + 1 below the suites5 height) is refused with -25 Validation(SuiteNotActive); testmempoolaccept reports it as allowed: false with reject-reason validation error: SuiteNotActive (...). Likewise an output of a retired level-1/2 suite (ML-DSA-44, SLH-DSA-SHAKE-128s) from the retire12 height is Validation(SuiteRetired); spending old outputs is allowed, and so is unvaulting a VAULT funded before the height |
testmempoolaccept |
[[hexstring], verifier?] or {rawtxs: [hexstring], verifier?} (a bare string is also accepted) |
array with one object per transaction: {txid, allowed, size, reject-reason?}. Reports whether the transaction would enter the mempool without inserting or relaying it: the full mempool policy is applied (fee floor, ancestor limits, conflicts with queued transactions), not only consensus. One transaction per call in this release (an empty list or more than one is -8); an invalid hex or verifier is -25. verifier only accepts oqs, the default. |
sendrawtransaction example:
curl -u u:p -H 'Content-Type: application/json' \
-d '{"jsonrpc":"1.0","method":"sendrawtransaction","params":["0100…f3"],"id":1}' \
http://127.0.0.1:28338/
# {"result":"775ad9af…","error":null,"id":1}
Resubmitting an already-known mempool tx returns error -25 (DuplicateTxid).
With -txindex, resubmitting an already-confirmed tx is also rejected as
DuplicateTxid (transaction already confirmed) before input lookup.
All submission methods (sendrawtransaction, submit_tx and the wallet send
commands) return just the txid as a hex string, matching their help text.
Use getmempoolinfo to read the live pool: size is the transaction count
and maxmempool is the serialized-byte cap (default 32 000 000, overridable
with -maxmempool / maxmempool=; the entry cap defaults to 40 000
and is overridable with -maxmempoolentries / maxmempoolentries=).
Persistence. The pool survives restarts, mirroring Core’s
mempool.dat (-persistmempool, default true): it is snapshotted to
mempool.bin (the mempool_path extension) on graceful shutdown, on block
connect/reorg, and whenever policy state changes (prioritisetransaction,
min-fee-rate updates); it is preloaded at startup (loaded reports the
persisted-load state) and the node’s own transactions are re-injected and
relayed to peers on reconnect. The byte-cap and entry-cap overrides are
themselves persisted in the snapshot, so maxmempool reflects the configured
value across restarts.
Eviction policy. The pool is capped at maxmempool serialized bytes
(default 32 000 000) and 40 000 entries. A single transaction larger than
the byte cap is rejected outright. When an insert puts the pool over
capacity, the package with the lowest descendant-package fee rate is
evicted, with a random tie-break among equal minima (the anti-gaming
property of Core’s TrimToSize); if the incoming transaction is itself
evicted, the insert fails closed with MempoolFull instead of pretending
acceptance. Entries older than mempoolexpiry (default 14 days, Core’s
default) and expired orphans are evicted lazily, and raising the node’s
minimum fee rate evicts everything below it.
Network / peers
| Method | Params | Returns |
|---|---|---|
getnetworkinfo |
— | version, subversion (/Qanum:x.y.z/), protocol version, services, fees, warnings (list, see Node warnings), localpeerid (this node’s P2P identity: 64 hex, SHA3-256("qanum-peer-id-v1:" ‖ suite_id u16 BE ‖ ML-DSA-87 public key); absent without net_v2) |
getpeerinfo |
— | array of live peer stats. peer_id (the peer’s node identity proved in the handshake, same format as localpeerid) and transport ("noise-hybridxx-mlkem1024": every P2P connection is Noise hybridXX with X25519 + ML-KEM-1024; there is no TLS or plain-text P2P). One connection per identity: a second one is refused or resolved (crossed pair: the socket dialed by the lower peer id stays). minping (seconds, present once a keepalive ping was measured), last_block and last_transaction (Unix time the peer last gave us a block we did not have / a tx accepted to our mempool, 0 = never) are the Bitcoin Core fields that inbound eviction uses to protect useful peers. bytes_sent/bytes_recv are serialized wire bytes per peer; block data served through the GetData handler counts toward the -maxuploadtarget window but is not added to bytes_sent (that spawned path has no &mut access to the peer stats) |
getconnectioncount |
— | number of live P2P connections (Bitcoin Core parity). Same live source as getpeerinfo (connman with net_v2; addrman fallback without net_v2) |
getnettotals |
— | cumulative network traffic since startup: totalbytesrecv, totalbytessent (0 without net_v2 or with no traffic), timemillis. The totals are tracked by the connman and do not reset when a peer disconnects. |
getsnapshotinfo |
— | UTXO snapshot status/progress (AIP-0001): serving capability, bootstrap provenance, staged artifacts, live serving state; see below |
addnode |
[node, "add"\|"remove"\|"onetry"] |
null |
getaddednodeinfo |
[node?] |
array of addnode-list entries |
disconnectnode |
[node] |
null on success (removes from the addnode list and closes the connection). A node that is not currently connected returns -29 (Node not connected). Missing node is -8. |
setban |
[ip, "add"\|"remove", bantime?, absolute?] |
null. Single addresses only: ip is a bare IPv4/IPv6 address or its /32 / /128 form (wider subnets are -30). bantime in seconds, 0/negative = default 86400; with absolute=true it is a unix time (in the past = -8). add disconnects every peer on that IP; an address already banned is -23 (Error: IP/Subnet already banned); remove of an address that is not banned is -30. remove also forgets the IP’s misbehavior score. A wrongly typed bantime/absolute is -8. Bans persist (banlist.json). A ban does not stop the operator’s own targets: -addnode/-connect addresses are still dialled (remove them with addnode <node> remove). |
listbanned |
— | array of active bans: {address ("ip/32" or "ip/128"), ban_created, banned_until, ban_duration, time_remaining, reason} (reason is a Qanum extension). Includes the automatic bans from the misbehavior threshold. ban_duration is null for a ban written by a version that did not record ban_created (then 0). |
clearbanned |
— | null. Lifts every ban and forgets every misbehavior score (Qanum extension: the node keeps per-IP scores, and they are reset so a lifted ban is not re-applied by the next minor offence). |
listtofupeers |
— | the running node’s identity pins (TOFU, peer_ids.json): [{address, peer_id, last_seen}] sorted by address (address = ip:port, last_seen = unix time). A pin is created only by an outbound or feeler handshake; it expires after 7 days (private addresses) or 60 days (public), at most 4096 pins (least recently seen goes first) |
removetofuentry |
[ip[:port]] |
null. A bare IP removes every port’s pin, ip:port that one only. Needed before reconnecting to a peer that changed its identity without a signed succession. A malformed specifier or one that matches no pin is -8 |
resettofu |
— | null. Removes every pin (memory and disk); each peer is pinned again on its next outbound handshake. With the node stopped, deleting peer_ids.json does the same |
rotatep2pidentity |
[force?] |
{old_peer_id, new_peer_id, succession_until}. Replaces the node’s P2P identity (p2p_identity.json, ML-DSA-87) with a new one signed by the current key. Until succession_until (unix seconds, 60 days) the handshake carries that signature, and peers that pinned exactly the old identity move their pin to the new one; peers that learn the node later pin the new one. A second rotation within 60 days needs force=true, else -8; a non-boolean force is -8; a failure writing the file is -1. The old secret key is wiped from memory and the file is replaced atomically, but the filesystem may keep its old blocks: treat a suspected key compromise as a disk-level matter. Daemon only (not in qanum-embed) |
The four TOFU / identity RPCs need net_v2; without it they return -1
(net_v2 peer admin not available).
P2P compact filters (BIP157 semantics)
With -blockfilterindex=1, an up-to-date node advertises service bit
NODE_COMPACT_FILTERS = 1 << 6 (localservices includes 0x40) and protocol
version 2. net_v2 supports the full server message family:
| Request | Response | Limit / semantics |
|---|---|---|
getcfilters {filter_type,start_height,stop_hash} |
one ordered cfilter per block |
basic type 0; stop-start < 1000 |
getcfheaders {filter_type,start_height,stop_hash} |
cfheaders {previous_filter_header,filter_hashes[]} |
basic type 0; stop-start < 2000; max 2000 hashes |
getcfcheckpt {filter_type,stop_hash} |
cfcheckpt {filter_headers[]} |
positive multiples of 1000 through the accepted stop block |
Unknown stop hashes, unsupported filter types, invalid ranges, stale indices,
and missing/corrupt index entries receive no response. Responses are served
only from filterindex/*.qfh; filters are never generated from block/undo data
on a P2P request. The Qanum net_v2 transport uses its authenticated framed
bincode envelope while preserving BIP157 field and range semantics.
P2P UTXO snapshots (QUSN v2, AIP-0001)
Snapshot distribution is P2P-only — downloads never go through the RPC
surface. Archive nodes (prune=0) advertise service bit
NODE_UTXO_SNAPSHOTS = 1 << 7 (0x80), visible in
getnetworkinfo.localservices and in each peer’s services in
getpeerinfo. Peers holding the bit serve the GetQusn /
GetQusnChunk P2P messages (rate-limited: one stream per peer, 60 s
re-request interval); the local CLI equivalents are the one-shot commands
utxo-snapshot-export / utxo-snapshot-verify / utxo-snapshot-install,
and a fresh node can bootstrap over the network with -snapshot-ibd=1.
See AIP-0001 and USER-GUIDE →
“Snapshot sync (QUSN v2)”.
getsnapshotinfo
Status and progress of UTXO snapshots for this node. No parameters.
| Method | Params | Returns |
|---|---|---|
getsnapshotinfo |
— | object: advertises_snapshot_service, can_serve, bootstrap, last_bootstrap_attempt, body_backfill, snapshots_on_disk, serving, limits |
Fields:
advertises_snapshot_service(bool) — whether the node announcesNODE_UTXO_SNAPSHOTS. With net_v2 running the connman reports it; without net_v2 it is derived from the prune mode (archive ⇒ true).can_serve(bool) — whether this node can produce and serve snapshots (archive: neither block-pruned nor auth-pruned).bootstrap(object|null) — provenance of a snapshot install, if this chainstate was bootstrapped from one:installed_height,installed_tip,source(peer:<addr>for-snapshot-ibd,local:<path>forutxo-snapshot-install),installed_unix,format_version. Written todatadir/snapshot_bootstrap.jsonat install time.last_bootstrap_attempt(object|null) — the last-snapshot-ibdbootstrap attempt, successful or not; null if none was ever made (a datadir that already had a chain and no kept snapshot attempts nothing and keeps the previous record). Fields:outcome—installed,no_peers(no candidate to try),unconfirmed(a verified snapshot was not installed because onlyconfirmedof theneededindependent peers confirmed its tip within the 15-minute bootstrap budget: make a peer in another network group reachable; the snapshot is kept and retried at the next start while the local chain is below it, and once confirmed it replaces the partial chain) orfailed(no usable snapshot, a contradicting peer, budget exhausted…);finished_unix,elapsed_ms;heightandserver(ip:port) when an artifact was involved;confirmed/needed(only forunconfirmed; otherwise null);detail(human-readable, not for parsing). Written todatadir/snapshot_bootstrap_attempt.json; an unreadable file reads as null.body_backfill(object|null) — background IBD progress (-backfill-bodies, iteration 11), absent if it never ran:snapshot_height(range ceiling),next_height(first body still missing),blocks_done,complete(all bodies1..=snapshot_heightstored),updated_unix. Progress persists indatadir/.body-backfill/resume.json.snapshots_on_disk(array) — staged artifacts underdatadir/snapshots/(qusn-<height>.qusn), each withfile,height,bytes, sorted by height. The P2P serving cache keeps a single slot: a fresh export replaces the previous artifact, so normally exactly one file is listed.serving(object|null) — live serving state from the connman (absent without net_v2 or if the connman does not answer):active_streams(peers with a stream in flight),rate_limited(peers inside the serve window, withavailable_in_secs),served_total(streams served since this process started),served_from_cache_total(streams served from the export cache: the tip did not change since the previous export, so the staged artifact was reused without flush/re-export/rewrite).nullcan ALSO appear transiently on a healthy net_v2 node during the first moments after startup, while the RPC→connman bridge (5 s reply budget) has not been answered yet; the field settles to the live object on subsequent calls. This is graceful degradation, never an error.limits(object) — serving policy:max_artifact_bytes(512 MiB — the install cap; artifacts above it are neither exported, served nor installed),max_chunk_bytes(2 MiB),serve_interval_secs(60).
The RPC is read-only and degrades gracefully: without net_v2 it reports only
the disk-based fields (bootstrap, snapshots_on_disk) and the capability
derived from the prune mode.
Multi-wallet
Wallets are stored under datadir/wallets/<name>/wallet_keys.json; the
default wallet lives at datadir/wallet_keys.json and is always loaded. A
named wallet is selected with the wallet field of the JSON-RPC request, with
the wallet member of the native envelope, or with qanum-cli --wallet=<name>.
| Method | Params | Returns |
|---|---|---|
createwallet |
[name] |
creates and loads a named wallet |
loadwallet |
[name] |
loads an existing named wallet |
unloadwallet |
[name] |
unloads a named wallet (locks it) |
listwallets |
— | names of the loaded wallets |
The loaded-wallet set is persisted in datadir/wallets.json and restored on
node restart. Invalid names and entries whose key store no longer exists are
ignored during restoration. Using a named wallet that is not loaded fails with
Wallet '<name>' is not loaded (use loadwallet first). With an encrypted
wallet, unlock is per wallet (walletpassphrase selects it with --wallet).
Wallet (via JSON-RPC 1.0)
| Method | Params | Returns |
|---|---|---|
getnewaddress |
[{label?}, {address_type?}] |
P2PQK address string. Types (aliases in brackets): mldsa44 (ml-dsa-44, mldsa, ml-dsa, p2pqk, legacy; NIST level 2), mldsa87 (ml-dsa-87; level 5), slhdsa (slh-dsa, slhdsa128s, slh-dsa-128s; level 1) and slhdsa256s (slh-dsa-256s; level 5). Without a type: -addresstype; unset, mldsa87 once the next block may use level 5 and mldsa44 before (plain regtest: mldsa87 from genesis). The two SLH-DSA types store a recovery key and return {key_id, suite, note} — not a P2PQK address: SLH-DSA has no standalone payment address, the key_id is the recovery key of addr-p2rkpq (enforced at build). A level-5 type before the chain activates it (tip + 1 below the suites5 height, also when it comes from -addresstype) is -4 (ML-DSA-87 is not active yet: new outputs may use it from block H (the next block is N)), as Core refuses tr() before Taproot. From the retire12 height a level-1/2 type (mldsa44, slhdsa, also when it comes from -addresstype) is -4 (ML-DSA-44 is retired: new outputs may not use it from block H (the next block is N); use a level-5 suite (mldsa87)). An unknown type is -5 and the message lists the valid ones. Encrypted wallet must be unlocked |
getwalletinfo |
— | state of the SELECTED wallet (--wallet=<name>; the walletname field reports which one; "" = root): walletversion, encrypted, unlocked_until, keypoolsize, live balance, txcount; balance and txcount include confirmed + mempool P2PQK/custody data, and ownership fields remain readable while a new-format encrypted wallet is locked |
encryptwallet |
[passphrase] |
encrypts wallet_keys.json (Argon2id + AES-256-GCM); locks the wallet afterwards |
walletpassphrase |
[passphrase, timeout?] |
unlock the wallet in the daemon’s RAM for timeout s (default 300). timeout = 0 validates the passphrase and relocks immediately; larger values are capped at 100 000 000 s, so unlocked_until in getwalletinfo is always a real Unix timestamp |
walletlock |
— | lock the wallet (drop decrypted keys from RAM) |
walletpassphrasechange |
[old, new] |
change the wallet passphrase (wallet stays locked) |
validateaddress |
[address] |
validate a Qanum address: isvalid, address, ismine (when the wallet can tell), script_type, template_id, nist_level (NIST category of the address’s suites: ML-DSA-44 = 2, SLH-DSA-128s = 1, ML-DSA-87 = 5, SLH-DSA-256s = 5; the lowest one for a mixed P2RKPQ or multisig). Never errors for an invalid address — it returns isvalid: false (added for the testnet faucet) |
getaddressinfo |
[address] |
address info: address, ismine, script_type, template_id, solvency, key_id, label, nist_level (as in validateaddress); ismine recognizes P2PQK, P2RKPQ, Vault, VaultPending and canonical HTLC addresses and remains available while locked for new encrypted blobs |
getreceivedbyaddress |
[address, minconf?] |
total received by an address |
listtransactions |
[count?, skip?] |
wallet history, newest first, for P2PQK/P2RKPQ/Vault/VaultPending/HTLC; includes confirmed and mempool receive/send entries (confirmations=0 in mempool), including chained mempool spends |
gettransaction |
[txid] |
wallet tx details + raw_hex; confirmed and mempool custody transactions include both send and receive details |
listsinceblock |
[blockhash?] |
wallet history since a block: {transactions, lastblock} |
signmessage |
[address, message] |
sign a message with a wallet key (post-quantum); returns base64 |
verifymessage |
[address, signature, message] |
verify a message signature (wallet keys only). Boolean contract: false for a suite-mismatched, structurally invalid or non-matching signature — errors are reserved for missing/invalid parameters, malformed base64, addresses not owned by the wallet and crypto-backend failures; the rejection reason is written to the rpc debug category |
sendtoaddress |
[address, amount, fee] |
txid of the signed and broadcast payment; amounts in QAN, fee "0" uses the settxfee rate (fee required if none is set). Errors as sendmany; a broadcast the node rejects is -25 |
sendmany |
[outputs, fee] |
send to multiple addresses (addr1:amt1,addr2:amt2), amounts in QAN. Errors (Bitcoin Core codes, plain text): -18 wallet not found, -13 wallet locked, -5 invalid address (Invalid Qanum address '…': …), -3 invalid amount, fee or output syntax, -6 not enough spendable funds (insufficient spendable funds: …) or no fee (fee required: …), -4 any other wallet refusal: a recipient suite that is retired (ML-DSA-44 is retired: …) or not active yet, no usable key, a fee above -maxtxfee |
keypoolrefill |
[size?] |
creates size new wallet keys (OQS required) of the -addresstype type when the next block may use it, else ML-DSA-44 before the level-5 activation and ML-DSA-87 from the level-1/2 retirement; without -addresstype, the default by height. size = 0 generates nothing and returns 0; the maximum is 100 000 |
migratesuites |
[target?, execute?, options?] |
moves the wallet’s funds to NIST level 5 (qanum extension). target: only "level5" (default). execute default false: a dry run with no side effect that reports activation, holdings (per template and suite), plan (per transaction: kind, source_key_ids, inputs, exact fee and total_bytes), totals, skipped (with the reason and the manual path: multisig, vault, HTLC, immature coinbase, P2RKPQ whose recovery key is not in this wallet, uneconomic outputs) and future_spend (the cost of spending the new outputs). Single-key ML-DSA-44 P2PQK outputs go to fresh ML-DSA-87 keys, one transaction per source key (so addresses kept apart are not linked); P2RKPQ outputs whose two keys are in the wallet go to a fresh ML-DSA-87 / SLH-DSA-256s pair, one per input. Transactions are split within the consensus limits and the mempool’s 101 000-byte size limit (about 26 ML-DSA-44 inputs each). options (object or JSON string): fee_rate (jomas per byte; default the settxfee rate or the relay minimum), max_inputs_per_tx, include_p2rkpq (default true), consolidate (default false; true puts every P2PQK input into as few transactions as the limits allow). execute = true creates the keys (label migrated-from:<key id prefix>), signs and submits each transaction in order, stops at the first failure keeping the txids already submitted, and fills txid and new_key_ids. Errors: bad parameters -8; locked encrypted wallet -13; level 5 not active at the next block, or a fee above -maxtxfee, -4. Spending old outputs is never blocked: migration is optional |
abandontransaction |
[txid] |
mark a wallet tx as abandoned (excluded from history). The txid must belong to the wallet, be unconfirmed and be absent from the mempool; otherwise the call fails. Abandoning an already abandoned tx is idempotent |
backupwallet |
[destination] |
atomically copy the wallet file; encrypted wallets remain encrypted; output permissions are owner-only on Unix |
dumpwallet |
[filename] |
export all keys as plaintext JSON; encrypted wallets must be unlocked; output permissions are owner-only on Unix |
importwallet |
[filename] |
returns the number of keys imported; validate and merge keys by key_id; encrypted wallets must be unlocked. Rejects dumps larger than 16 MiB, more than 4096 keys, keys whose public_key/secret_key lengths do not match the suite (ML-DSA-44: 1312/2560; SLH-DSA-128s: 32/64), and — when built with crypto-oqs — secrets that cannot sign a challenge accepted by the public key. Malformed dumps return -8 |
getbalance |
— | live balances in jomas + QAN; excludes confirmed outputs spent by mempool transactions and includes unspent P2PQK/P2RKPQ/Vault/VaultPending/HTLC mempool outputs with branch-aware spendable/delayed classification |
listunspent |
[minconf?] |
wallet-controlled UTXOs |
listreceivedbyaddress |
[minconf?, include_empty?] |
received grouped by address. amount is the total historically received (spent receipts included), not the current balance. include_empty = true also lists wallet addresses with no receipts, with amount = 0 |
Encrypted-wallet read model: encrypted blobs contain non-secret ownership metadata (
suite_id,key_id, and label) plus the key count outside the ciphertext. This allows balance, UTXO, history andisminequeries while the wallet is locked; public keys, secret keys and creation timestamps remain encrypted. Signing, key generation,listkeys,showkey,dumpwallet, andimportwalletstill requirewalletpassphrase. Blobs created before this metadata existed retainWalletLockedread behavior until a successful passphrase change or another authenticated wallet rewrite migrates them.
Wallet history uses exact template payloads from -addressindex when the
index is available. Without it, the node scans canonical blocks and resolves
spent inputs through previously seen transactions. Mempool history resolves
both chainstate inputs and chained unconfirmed outputs. Multi-key custody
outputs are emitted once even when the wallet owns both branches.
Live balance applies branch semantics to confirmed and unconfirmed outputs: P2RKPQ main is immediate while recovery observes its delay; Vault/VaultPending cold branches are immediate while hot branches remain guarded or timelocked; HTLC refund becomes spendable at timeout. HTLC recipient value is controlled but reported as delayed/locked because the wallet does not store the preimage. Mempool outputs spent by a child transaction are excluded.
Address index (explorer)
getaddressbalance, getaddresstxids, getaddressutxos (see Blockchain
table). These require the daemon to run with -addressindex=1 (which implies
-txindex=1); otherwise the node reports that the index is disabled.
3.5 Per-command help
qanum-cli -h <command> (or qanum-cli help <command>, --help <command>,
<command> --help) prints detailed help for that command: usage,
arguments, result and examples (both qanum-cli invocations and a curl
JSON-RPC 1.0 example when the command has a string-RPC equivalent). qanum-cli
help alone prints the general usage.
4. Native Qanum methods (used by qanum-cli)
The CLI maps commands to the native envelope. The native-only set (no JSON-RPC 1.0 string equivalent) includes:
| Method tag | Purpose |
|---|---|
list_blocks |
all stored blocks (extended) |
datadir_info |
datadir diagnostics |
reorg_plan |
fork realignment plan (null if on best tip) |
build_payment / build_payment_auto |
build unsigned payment |
submit_tx |
submit raw tx ({tx_hex, verifier}, verifier = oqs only); returns the txid as a hex string, like sendrawtransaction |
get_address_* family |
address index lookups |
| custody builders | build_p2rkpq_main, build_p2rkpq_recovery, build_vault_init, build_vault_recover, build_vault_finalize, build_vault_cancel |
Threshold spending (P2MULTI) is CLI-only
buildp2multispend and signp2multi are not RPC methods and reach no
node: building the spend needs only the outpoint data the caller already has,
and signing needs only the local key store. This is deliberate. A guardian
signs on a machine that does not have to run a node, or even be online, and a
signing step that required an RPC connection would put the key on the same
host as the network service.
The node is involved once, at the end, through sendrawtransaction. See the
User Guide for the full build →
sign → assemble flow, and note the two rules consensus enforces on the
assembled transaction:
- one
authentry per input (auth.len() == vin.len()), - inside each entry, seats in strictly increasing
key_indexorder, and - each entry within
MAX_AUTH_BYTES_PER_INPUT(131 072 bytes). The threshold cap isP2MULTI_MAX_KEYS= 16 keys; the byte budget is sized so that 16 ML-DSA-87 seats (7 222 bytes each, 115 553 bytes with the entry header) fit, and ML-DSA-44 seats (3 735 bytes) fit with room to spare. The budget is enforced twice — inparser.rswhen the transaction is deserialised and again invalidator.rs— so an oversized entry fails asAuthLengthrather than as a multisig error.
Custody builders are strict multi-wallet operations. The selected wallet must own the key required by the exact spending branch before a signing request is returned. HTLC claim/refund also enforce preimage and timeout conditions:
| Builder | Required key in selected wallet |
|---|---|
build_p2rkpq_main |
P2RKPQ main key |
build_p2rkpq_recovery |
P2RKPQ recovery key |
build_htlc_claim |
HTLC recipient key + matching SHA3-256 preimage; current height must be before timeout |
build_htlc_refund |
HTLC refund key; current height must be at or after timeout |
build_vault_init |
vault hot key |
build_vault_recover |
vault cold key |
build_vault_finalize |
pending-vault hot key |
build_vault_cancel |
pending-vault cold key |
Ownership checks use non-secret wallet metadata and therefore also work for a
new-format encrypted wallet while locked. Signing still requires the selected
wallet to be unlocked. A mismatch returns KeyNotOwnedByWallet instead of
building a request that would later fail against another wallet’s key store.
The canonical HTLC payload is 109 bytes:
hash_algo=0x01 | payment_hash[32] | recipient_suite/key | refund_suite/key |
timeout_height. addr-htlc <sender> <receiver> <payment_hash> <timeout> maps
receiver to the claim/recipient branch and sender to the timeout/refund
branch; both keys must belong to the selected wallet so their suites are
unambiguous.
You normally reach these through qanum-cli <command>; calling the native
envelope directly is supported but the tags may change across versions.
5. Error codes
| Code | Meaning |
|---|---|
-1 |
Generic/internal error (human-readable detail in the message; Rust wrapper types are never exposed) |
-3 |
Type error: an invalid amount, fee or output syntax in sendtoaddress / sendmany |
-4 |
Wallet error: a key type or recipient suite that the next block may not use (not active yet, or retired), no usable key, a fee above -maxtxfee |
-5 |
Not found (block, tx, …) or invalid address, including an address belonging to another network (Invalid Qanum address: network mismatch (expected regtest, got testnet)), which is rejected even by read-only queries. getrawtransaction distinguishes: without -txindex, "No such mempool transaction. Enable -txindex to query confirmed transactions (the transaction may already be mined)."; with -txindex, "The transaction is not in the mempool or the transaction index." |
-8 |
Invalid parameter: bad hex, out-of-range height, missing argument, negative value where a count is expected (count must not be negative), a hash that is not 64 hex chars (txid must be a hexadecimal string of length 64 (not '…')), or surplus positional parameters (listtransactions takes at most 2 argument(s) (3 given)) |
-6 |
Insufficient funds: sendtoaddress / sendmany without enough spendable funds, or without a fee (fee required) |
-13 |
The wallet is encrypted and locked (walletpassphrase first) |
-18 |
The selected wallet does not exist (use createwallet first) |
-23 |
setban add of an address that is already banned (Error: IP/Subnet already banned) |
-25 |
Transaction rejected (e.g. DuplicateTxid, invalid fee, missing inputs, MempoolFull) |
-29 |
Peer not connected (disconnectnode of an address that is not a live peer: Node not connected) |
-30 |
setban: invalid IP/subnet (single addresses only), or remove of an address that is not banned (Error: Unban failed. Requested address was not banned.) |
-32700 |
Parse error: the request body is not valid JSON. Answered with a JSON-RPC envelope and "id": null, then the connection is closed |
-32601 |
Method not found |
-32603 |
Internal error (e.g. generatetoaddress timeout or generation failure on a node without net_v2) |
Example error envelope:
{"result": null, "error": {"code": -32601, "message": "Method not found: get_unknown"}, "id": 1}
6. Concurrency
The RPC server uses a worker-pool + bounded queue, mirroring Bitcoin Core’s
-rpcthreads / -rpcworkqueue (see httpserver.cpp):
-rpcthreads=<n>(default 16) — number of worker threads that serve RPC calls. A pool ofnthreads is started and each drains the work queue.-rpcworkqueue=<n>(default 64) — capacity of the RPC work queue. When the queue is full, excess requests are rejected with HTTP 503 “Work queue depth exceeded” (and a log line naming-rpcworkqueue), instead of closing the connection in the middle.
Both are also accepted in qanum.conf as rpcthreads= / rpcworkqueue=.
Long-running calls (e.g. generatetoaddress with many blocks) do not block
other RPC requests, wallet reads such as getbalance included. Note that
the chain itself is serialized (blocks are generated/imported sequentially),
so concurrent calls observe consistent chain state at each point in time.
Request/response bodies are bounded by MAX_RPC_BODY_BYTES = 16 MiB: the
server answers 413 Payload Too Large without reading an oversized body,
and the CLI refuses oversized responses.