JSON-RPC Extensions
satd preserves Bitcoin Core's JSON-RPC contract by default: the same method
names, response field names, and types, so existing clients work unchanged.
On top of that, satd adds opt-in extensions for developers and integrators.
Each extension is either enabled by a server flag (and is therefore
live-reloadable over SIGHUP) or exposed as an additional method or
parameter. None of them alters the default Core-compatible wire shape. All
are governed by the
stability policy.
The authoritative catalogue of where satd differs from Core is
CORE_DIFFERENCES.md.
For the push-based event firehose and cursor-resumable watch subscriptions (gRPC, WebSocket, SSE, ZMQ), see the Streaming Consumption API chapter. That is a distinct surface from the extensions described here.
Note. JSON-RPC keeps Bitcoin Core's cookie /
rpcuser/rpcauthcredentials by default. Capability-scoped bearer tokens (-rpcauthbearer,rpc:read/rpc:write) are an opt-in addition. See Authentication & Authorization.
Satoshis-as-integers
Bitcoin Core emits every amount as an IEEE-754 double in whole BTC
(0.00001000), which loses precision near dust and at the supply boundary.
This is Core's long-standing
#3249, open since 2013.
satd can instead emit exact integer satoshis.
This is a server-wide default, --rpc-default-units=sats|btc
(rpcdefaultunits as a config key), not a per-request flag. The default is
btc, where output is byte-identical to Core: a fixed 8-decimal number,
formatted from the integer satoshi value so it is exact. Set it to sats
and amounts serialize as JSON integers everywhere. In that mode responses
also carry a _units: "sats" tag so a client can confirm the shape it
received. The tag is absent in the default btc mode, which stays
byte-for-byte compatible. The option is live-reloadable. A per-request
HTTP-header override is a planned follow-up.
Structured RPC errors
By default, error responses are byte-identical to Core's {code, message}.
--rpc-extended-errors (rpcextendederrors; default off, live-reloadable)
is a server-wide option. With it enabled, satd additionally populates the
JSON-RPC data object with machine-actionable fields:
category: a stable taxonomy string, for examplemempool.policy.feerate,validation.consensus,storage.not_found.suggestion: a concrete remediation hint, when one applies.debug: arbitrary structured detail (field positions, computed values), when present.
Category names are stable once shipped in a release: new names can be
added, and existing ones never change meaning. As with the units default,
this is a server-wide option, since the common deployment pattern is satd
driven only by satd-aware tooling. A per-request X-Satd-Extended-Errors
header is a planned follow-up.
Fee estimation
Core's estimatesmartfee conf_target [estimate_mode] is kept with its
exact response shape ({feerate, blocks, errors}) and is Core-compatible
by default. The optional mode argument accepts Core's economical /
conservative / unset vocabulary, all treated as the historical
estimator. It also accepts satd's own historical / mempool / blend
values.
satd also adds an estimatefees [targets] [mode] method (default mode
blend, default targets [1, 3, 6, 12, 24]). It simulates the next N
block templates from the current mempool, with ancestor-feerate
(CPFP-aware) package sorting. It never hard-errors; it always returns a
result. The response maps each target to a {feerate, confidence} pair,
where confidence is high | medium | low, and includes a feerate
histogram. This is the basis for Core's
#11500.
Mempool subscription stream
subscribemempool is a JSON-RPC WebSocket subscription, paired with
unsubscribemempool, that emits structured lifecycle events. Each event is
tagged by a kind field:
enter: a transaction was admitted to the mempool.leave_confirmed: it was confirmed in a block.leave_evicted: it was dropped, with an explicitreason(full_pool|expiry|block_conflict|policy|reorg).leave_replaced: it was RBF-replaced, carrying thereplacing_txid.
Bitcoin Core requires polling getrawmempool or rebuilding this state from
per-tx ZMQ frames. This stream carries explicit eviction reasons and RBF
replacement linkage directly. For the richer firehose with cursor replay,
see the Streaming Consumption API; subscribemempool is
the lightweight JSON-RPC option.
Silent-payment block data
getsilentpaymentblockdata "blockhash" ( verbosity dust_limit ) returns the
public BIP 352 tweak data for one block, from the tweak index
(-silentpaymentindex=1, default off). It is the JSON-RPC fallback for the
streaming tweaks category — the same bytes, for scripts, the
reference-implementation differential, and integrators not yet on an SDK.
verbosity 0(default) →{ "block_hash", "height", "tweaks": ["<33-byte hex>", …] }.verbosity 1→ each entry becomes{ "txid", "tweak", "max_value" }.dust_limit(sats, default0) drops entries whose largest taproot output value is below the floor.
Errors: -5 for an unknown or non-active block, -8 when the index is
disabled, and -1 when the block is not yet indexed at that height (the row is
absent — a height-by-height scanner cannot proceed past a gap, but unlike BIP
157 it cannot silently miss its own outputs either). The method is read-only. A
light client runs one ECDH per returned tweak locally, so the scan key never
reaches the node; for the streaming firehose with cursor replay, and the
integrator guide to every silent-payment consumption mode, see
Silent Payments (BIP 352).
Stratum
getstratuminfo reports the Stratum mining server. It takes no
arguments, is read-only, and answers on a node with the server off too — with
enabled: false, null listeners and zero counters — so a monitor can poll it
unconditionally.
{
"enabled": true,
"listeners": { "v1": "127.0.0.1:3333", "v1_tls": null, "v2": "0.0.0.0:3336" },
"authority_pubkey": "<32-byte x-only key, hex>",
"job_declaration": false,
"connections": 2,
"channels": 3,
"current_job": {
"height": 912345,
"job_id": "1a",
"prev_hash": "<hex>",
"template_txs": 3210,
"template_fees": 12345678
},
"shares": { "accepted": 412, "rejected": 3, "stale": 1 },
"blocks_found": 0,
"last_block": null,
"hashrate": 1210000000000.0,
"miners": [
{
"protocol": "v1",
"peer": "192.0.2.10:51234",
"channel_id": null,
"address": "bc1q…",
"worker": "rig1",
"device": "<user agent the firmware sent>",
"difficulty": 10000,
"connected_time": 1760000000,
"shares": { "accepted": 208, "rejected": 1, "stale": 0 },
"best_share_difficulty": 4812337.6,
"last_share_time": 1760006280,
"hashrate": 1210000000000.0
}
]
}
listenersare the bound addresses, with the real port when a bind used:0.authority_pubkeyis null without a Stratum V2 listener.channelscounts authorized Stratum V1 connections and open Stratum V2 channels.current_jobis the work being handed out, or null while none is (during initial block download).job_ididentifies the work within this server; the job ids a miner sees are per connection.sharescount since startup.staleis a share for a job that is no longer current;rejectedis every other refusal.blocks_foundcounts blocks found through the server that joined the active chain;last_blockis{ "height", "hash", "time" }for the most recent, or null.minerslists every miner connected now, oldest first: an authorized Stratum V1 connection, or a Stratum V2 channel (channel_idset). A miner leaves the list when it disconnects.addressis the payout address, or null when--stratumaddresspays;workeris the part of the username after the first., reduced to printable ASCII and at most 64 characters.deviceis the Stratum V1 user agent, or the Stratum V2 vendor, hardware version and firmware, reduced to printable ASCII; null if the miner sent none.difficultyis the share difficulty the miner is set to now.connected_timeandlast_share_time(null before the first accepted share) are Unix times.sharescount this miner's shares, as the node-widesharesdo.best_share_difficultyis the highest difficulty an accepted share's header achieved.hashrateis an estimate in hashes per second, from the difficulty of the shares accepted over the last ten minutes (see Verifying a miner).
- The top-level
hashrateis the sum overminers.
Client-side PSBT signing (no signing RPC)
There is no signing method: satd never handles private keys. Signing is a
client-side sat-cli command.
sat-cli signpsbtwithkey reads a WIF private key or a BIP-32 xpriv from
stdin, prompting without echo when stdin is a terminal. It signs the PSBT
entirely locally, using only the prevout data already carried in the PSBT.
It covers the common single-sig script types (Legacy, SegWit v0, nested
SegWit, and Taproot key-path) and writes partial_sigs / tap_key_sig for
the node's finalizepsbt to assemble, rather than finalizing itself. An
xpriv is expanded over the standard BIP 44/49/84/86 paths, so it can sign
PSBTs that carry no derivation metadata, including satd's own createpsbt
output. The key never crosses the JSON-RPC boundary, so satd stays strictly
keyless.