PSBT Signing Pipeline
resource: psbt — build, sign, combine, finalize, extract: the full write path
Why a Pipeline?
Bitcoin has no single-call "sign this transaction" primitive. The natural unit of work is a PSBT (Partially Signed Bitcoin Transaction, BIP-174) — a portable, base64-encoded artifact that passes through a multi-step pipeline, each step its own operation:
psbt/build → unsigned PSBT (coin selection + fee calculation)
psbt/sign → +1 signature (call once per required signature)
psbt/combine → merges independently-signed copies (multisig only)
psbt/finalize → final scriptSig/witness (re-validates every UTXO is still unspent)
psbt/extract → raw transaction hex
transaction/broadcast → submits to the network
build
Creates an unsigned PSBT: selects UTXOs to cover the requested outputs plus fee, computes change, and serializes the result.
| Field | Type | Required | Description |
|---|---|---|---|
network | select | ✓ Yes | Fund-moving — must be explicit. |
fromAddress | text | Conditional | Address to spend from — the node fetches its UTXOs automatically. Required unless an explicit utxos[] list is supplied instead. |
utxos[] | array | No | Explicit UTXOs to spend instead of auto-selecting: {txid, vout, valueSats, redeemScript?, address?}. address is required per-entry when it differs from fromAddress, or when fromAddress itself is omitted. Not needed for a P2WSH multisig UTXO — redeemScript alone determines its scriptPubKey. |
outputs[] | array | ✓ Yes | At least one destination: {address, amountSats}. |
changeAddress | text | No | Where leftover value returns. If omitted and change is generated, the build fails validation rather than silently picking an address. |
feeRateSatVb | decimal | No | Sat/vB fee rate. If omitted, consults fee/getEstimates's fastest target automatically. |
addressType | select | No (default p2wpkh) | p2wpkh or p2tr. |
enableRbf | boolean | No (default true) | Signal Replace-By-Fee, allowing this transaction to be fee-bumped before confirmation. |
locktime | number | No (default 0) | Transaction nLockTime. |
reservationTimeoutSeconds | number | No | Overrides Bitcoin:UtxoReservation:LockTimeoutSeconds for this build — use a longer value for human-in-the-loop multisig approval flows. |
idempotencyKey | text | No | Safe-retry key for this build call. |
Example response:
{
"psbtBase64": "cHNidP8BAHECAAAAAd...",
"inputsSelected": [ { "txid": "8f3c...", "vout": 0, "valueSats": 500000, "confirmed": true } ],
"totalInputSats": 500000,
"feeSats": 1400,
"feeRateSatVb": 12.5,
"changeSats": 88600,
"changeAddress": "bc1q...",
"expectedOutputsHash": "a94f...b21"
}
changeAddress/changeSats forward. If
changeSats > 0, include { "address": changeAddress, "amountSats": changeSats }
as one of psbt/sign's expectedOutputs[] entries — see below.
sign
Adds one signature to the PSBT (call once per required signature — e.g. twice for a 2-of-3 multisig).
| Field | Type | Required | Description |
|---|---|---|---|
network | select | ✓ Yes | Fund-moving — must be explicit. |
psbtBase64 | text | ✓ Yes | The PSBT from psbt/build (or a prior sign/combine step). |
walletType | select | ✓ Yes | wif or mnemonic — readOnly cannot produce a signature. |
credentialID | number | ✓ Yes | Vault reference to the signing key/mnemonic. |
addressType | select | No (default p2wpkh) | p2wpkh or p2tr. |
derivationPath | text | No | Overrides the default SLIP-44 path (see Networks) when walletType is mnemonic. |
expectedOutputs[] | array | ✓ Yes | The complete, exact set of outputs the PSBT must contain: {address, amountSats} per entry. See the fund-safety callout below. |
expectedOutputs[] is
required and must be an exact set match against the PSBT's real outputs — same count,
every output's address and amount matching one expected entry exactly. Without this check, a signing
operation would blindly sign whatever PSBT it was handed, with no verification it matches what was
originally built or approved — a validly-signed transaction to a substituted destination is exactly as
valid, and exactly as irreversible once confirmed, as one to the intended destination. If
psbt/build produced a change output (changeSats > 0), its
changeAddress/changeSats must be included in
expectedOutputs[] — there is no implicit "leftover output" tolerance of any kind. A
mismatch fails with PSBT_OUTPUT_MISMATCH.
Example response:
{
"psbtBase64": "cHNidP8BAHECAAAAAd...",
"signaturesAddedForInputs": [0]
}
combine
Merges two or more independently-signed copies of the same PSBT — only needed for out-of-band multisig where different signers each sign their own copy separately.
| Field | Type | Required | Description |
|---|---|---|---|
network | select | ✓ Yes | Fund-moving — must be explicit. |
psbtBase64List[] | array of text | ✓ Yes | At least two signed PSBT copies to merge. |
finalize
Produces the final scriptSig/witness for each input. Re-validates that every input UTXO is still unspent immediately before finalizing — Bitcoin consensus itself is the real safety net here, but this check distinguishes a genuine race/expired reservation from a call that already succeeded on a prior attempt whose response was lost.
| Field | Type | Required | Description |
|---|---|---|---|
network | select | ✓ Yes | Fund-moving — must be explicit. |
psbtBase64 | text | ✓ Yes | The fully-signed PSBT. |
Example response:
{
"psbtBase64": "cHNidP8BAHECAAAAAd...",
"allInputsFinalized": true,
"alreadyBroadcastTxid": null,
"inputOutpoints": [ { "txid": "8f3c...", "vout": 0 } ]
}
| Response Field | Meaning |
|---|---|
alreadyBroadcastTxid | Set when this exact call already succeeded on a prior attempt — treat as success, not a fresh finalize. |
inputOutpoints | Lets the caller release the UTXO reservation early on success rather than waiting out the full reservation timeout. |
An input whose UTXO was spent elsewhere between build and finalize fails with
PSBT_UTXO_NO_LONGER_AVAILABLE — see Troubleshooting.
extract
Extracts the final raw transaction hex from a finalized PSBT — the direct input to transaction/broadcast.
| Field | Type | Required | Description |
|---|---|---|---|
network | select | ✓ Yes | Fund-moving — must be explicit. |
psbtBase64 | text | ✓ Yes | The finalized PSBT. |
Example response:
{
"rawTransactionHex": "02000000000101...",
"txid": "8f3c...e21a"
}
buildTaprootScriptPathInput
PSBTInput has no leaf-script/control-block
fields the way it does for key-path Taproot, so this operation's actual mechanism needs a dedicated spike
before it's safe to ship. It's implemented as a best-effort field-attach for the timelock-leaf case only;
the script-multisig-leaf case fails fast with PSBT_TAPROOT_SPIKE_PENDING. Key-path Taproot
(P2TR) signing via psbt/build/psbt/sign is fully implemented and unaffected —
this limitation only applies to Taproot script-path spends. See Roadmap.
| Field | Type | Required | Description |
|---|---|---|---|
network | select | ✓ Yes | Fund-moving — must be explicit. |
psbtBase64 | text | ✓ Yes | PSBT to attach the script-path input to. |
inputIndex | number | No (default 0) | Which input to build. |
leafType | select | No (default timelock) | Only timelock has best-effort support today. |
Full Pipeline at a Glance
| Step | Operation | Role | Needs a credential? |
|---|---|---|---|
| 1 | psbt/build | Creator/Updater — coin selection, fee, change | No |
| 2 | psbt/sign | Signer — once per required signature | ✓ Yes |
| 3 | psbt/combine | Combiner — multisig only | No |
| 4 | psbt/finalize | Finalizer — re-validates UTXOs, builds final witness | No |
| 5 | psbt/extract | Extractor — raw hex | No |
| 6 | transaction/broadcast | Submits to the network | No |
See Examples for a full worked send-Bitcoin workflow through this pipeline end to end.