How to Plan and Price a Bitcoin Spend with GetBlock's Blockbook Add-on
Build a Bitcoin coin selector and fee planner on GetBlock's Blockbook add-on: choose which unspent outputs to spend, and price the transaction before signing.
Bitcoin has no account balances. A wallet's balance is a fiction; it computes by adding up unspent transaction outputs, and to send a payment it must choose which of those outputs to spend, a decision with no obvious right answer and real money riding on it.
The choice is circular: the fee is a price per virtual byte, so it depends on the transaction's size, the size depends on how many inputs it has, and how many inputs it needs depends on the fee. Worse, some outputs only look spendable — a mining reward is locked by consensus for 100 blocks, and an output worth less than the fee to spend it is stranded value.
Get any of this wrong, and you produce a transaction that overpays by an order of magnitude, cannot be relayed, or cannot be mined at all.
In this guide, you will learn how to decide which unspent outputs to spend for a Bitcoin payment and what that payment will cost, using GetBlock's Blockbook add-on.
What you'll build
A planAt(spendable, { amount, satPerKvB, inputType, toType, changeType }) function that:
Converts every output to its effective value — its worth minus the fee for its own input — which removes the circularity between fee and input count.
Drops outputs that cost more to spend than they hold, and reports how much value that strands.
Searches for a changeless combination that lands just above the target, so the transaction needs no change output at all.
Falls back to largest-first and random-draw selection when no changeless combination exists.
Prices each candidate plan, dropping a change output that would be dust and handing the leftover to the miner instead.
Ranks the plans by total cost — the fee plus what the change it leaves behind will cost to spend a day on.
Prerequisites
Node.js 20.6 or later — for the built-in
fetchand--env-file, so there is nothing to install.A Bitcoin endpoint with the Blockbook add-on enabled (REST). Blockbook keeps the address-indexed view of the chain that a plain Bitcoin node does not.
An address to plan against. No wallet or funds needed: the guide points at a busy public address whose UTXO set is fragmented and dust-heavy, which is exactly what makes coin selection worth watching.
Basic JavaScript knowledge.
Project Setup
Get the REST endpoint
In the GetBlock dashboard, create a Bitcoin mainnet endpoint with the Blockbook add-on. Copy the URL the dashboard gives you exactly as it appears — it will look like one of these:
Your access token is in that URL, so store it safely
Handle satoshis, and size a transaction
Create plan.js.
Start with the arithmetic and the sizing, because a planner that cannot size a transaction cannot price one.
Every amount is a BigInt count of satoshis, from the moment it is parsed to the moment it is printed. A wallet that puts a satoshi value through a float is a wallet that eventually sends the wrong amount — 0.1 + 0.2 is not 0.3, and here that is somebody's money.
Talk to Blockbook
Two helpers: one that fetches, and one that converts fee estimates to satoshis.
Reusing toSats on the estimator's reply is the whole unit conversion: a BTC decimal string becomes an integer count of satoshis per kilo-virtual-byte, and feeFor divides by a thousand at the point of use.
Price each plan, and rank them
Selection picks the inputs; costing decides what happens to the leftover.
Notice what the two targets do. targetWithChange and targetChangeless are computed with zero inputs — they cover only the overhead and the outputs. Because each candidate has already paid for its own input inside its effective value, the target never moves as inputs are added. That is what breaks the circularity, and it is why the leftover at the end is exactly the change.
Classify the outputs and report
The last piece is the part most easily skipped: two kinds of output look spendable in a balance and are not.
The full reporting code — the fee-tier table, the strategy comparison and the sensitivity table — is in the project repo.
Run it
Two things are already visible. The address holds 0.0954 BTC but only 0.0230 BTC of it is spendable — 122 of its 270 outputs are still in the mempool. And a 0.0015 BTC payment requires 10 inputs because this wallet has no large outputs to spend.
At this fee rate, the change is nearly free, so keeping it wins. Force a busy mempool and the answer changes:
The changeless plan now wins, twelve outputs have dropped out of play entirely, and the fee to send 0.0015 BTC is 0.00276 BTC — nearly twice the payment. That is the true cost of a fragmented wallet, and it only becomes visible when fees rise.
Push further and the wallet stops working altogether:
Every output is worth less than the 20,400 sats its own input would cost. The balance is real, the keys work, and not one satoshi can move.
Watch the coinbase rule bite
Point the planner at a mining address to see the other exclusion. This one comes from a block's coinbase transaction:
Of 226 BTC held, 77 BTC is unspendable — 28 recent mining rewards still inside their 100-block maturity window. Spending one is not merely unwise; consensus rejects the transaction.
The changeless search also reports a miss here, and that is the honest answer rather than a bug: a wallet holding a few very large outputs has no subset that falls within 100 satoshis of the target. This is exactly why a real wallet needs fallback strategies.
Understanding the response
A /api/v2/utxo/{address} entry describes one unspent output:
txid
string
The transaction that created this output. Half of the outpoint that identifies it.
vout
integer
Which output of that transaction. The other half of the outpoint.
value
string
The amount, in satoshis as a decimal string. Parse it with BigInt, never Number.
height
integer
The block that confirmed it. Absent while the output is unconfirmed.
confirmations
integer
How many blocks bury it. 0 means it is still in the mempool, and inherits its parent's replaceability.
coinbase
boolean
Present and true for a mining reward. Unspendable until 100 confirmations, by consensus.
Fields are omitted rather than zeroed when they do not apply: an unconfirmed output has no height, and coinbase appears only on mining rewards. Treat every field as optional and default it, which is what utxo.confirmations ?? 0 is doing.
And the two fee endpoints:
result
string
estimatefee — the feerate in BTC per kilo-virtual-byte. Multiply by 100,000,000 for sat/kvB, then divide by 1,000 for sat/vB.
averageFeePerKb
integer
feestats — the mean feerate a block actually paid, in satoshis per kvB. Different unit from result above.
decilesFeePerKb
array
Eleven feerates from cheapest to dearest transaction in that block, in sat/kvB. Index 5 is the median.
txCount
integer
Transactions in the block, which tells you how much the deciles are worth trusting.
Troubleshooting
npm start prints the usage message even though .env is filled in
npm keeps the arguments for itself, so the amount never reaches the script.
npm start -- 0.0015 — the -- passes the rest through. Or call node --env-file=.env plan.js 0.0015 directly.
The response is HTML, not JSON
The path was wrong, so Blockbook served its explorer page instead of the API.
Check the path starts /api/v2/. A 200 with <!doctype html> is a routing mistake, not a chain error.
Fees are roughly a thousand times too high or too low
estimatefee returns BTC per kilo-virtual-byte, and it is easy to read as sat/vB or BTC/vB.
"0.00000490" is 490 sat/kvB, so 0.49 sat/vB. Convert once, in one place.
estimatefee returns Internal server error
The confirmation target is outside the node's horizon of 1–1008 blocks.
Ask for a target in range. The error is bare, so treat any failure as "no estimate for this tier" and carry on.
Invalid address '...', checksum mismatch
A typo in the address, or a network mismatch such as a testnet address on a mainnet endpoint.
Check the address, and check the endpoint is for the chain you meant.
The plan is rejected by the network as non-standard
A change output below the dust threshold.
Compare change against (output size + input size) × 3 sat — the dust relay fee is 3 sat/vB by default — and drop the output if it falls short, letting the leftover become fee.
A plan is never mined
It included an immature coinbase output, or an unconfirmed one whose parent was replaced.
Exclude coinbase outputs under 100 confirmations, and require at least one confirmation for the rest.
The fee is larger than the payment
The wallet is fragmented, so the payment needs many inputs, and each input costs 68 vB.
This is a real result, not a bug. Consolidate when fees are low, so a payment does not need forty inputs when they are high.
Balance looks right but nothing can be spent
Every output is worth less than the fee for its own input at the current feerate.
Wait for cheaper fees. Value in outputs smaller than their own input cost is stranded, not lost.
Confirmation counts look stale
Blockbook's index has fallen behind its node.
Compare blockbook.bestHeight with backend.blocks in /api/v2. The inSync flag can read true while the index sits a block behind.
Where to take it
Three changes turn this into a wallet's selection layer.
Plan across the whole wallet, not a single address: The same
/api/v2/utxo/path accepts an xpub, ypub or zpub in place of an address and returns the unspent outputs across every derived address, so selection runs over the real wallet rather than one slice of it. Selection logic is unchanged; what changes is that each chosen input has to carry the derivation path its signer will need, so check which fields your endpoint returns for an xpub before relying on them.Mix script types honestly: The sizing here assumes that every input is the same type, which is true for a single address but false for a real wallet. Carry a per-input vsize instead of a single
inputType, and compute each input's effective value from its own cost — a Taproot input at 58 vB and a legacy input at 148 vB are worth genuinely different amounts of the same nominal value.Turn the plan into a PSBT: The plan already names the outpoints, the amounts and the change; a PSBT is that plus the metadata a signer needs. Building one keeps the split this guide relies on — the process that chooses coins never has to hold a key.
Two limits worth knowing. The changeless search is capped at 100,000 attempts, so on a large UTXO set it may miss a combination that exists; Bitcoin Core's version also minimizes long-term waste rather than immediate excess, which matters when today's feerate is unusually far from normal. And every plan is a snapshot: an output can be spent by another process between the query and the broadcast, so a production wallet locks the outputs it has selected and re-checks before signing.
Conclusion
You built a Bitcoin coin selector and fee planner in one dependency-free file, using Blockbook's UTXO and fee-estimate endpoints to answer the question a wallet must answer before it can sign: which outputs to spend, and at what cost. The ideas that made it work were switching to effective value so each input pays for its own fee and the target stops moving, excluding outputs that consensus or economics put out of reach, searching for a combination that needs no change at all, dropping change that would be dust, and comparing plans by total cost rather than by fee. None of it needed a private key.
Resources
Blockbook add-on — which chains it serves, and what the index answers
Official Upstream Blockbook API reference — Trezor's own docs for the endpoints
Official Upstream Blockbook fee documentation — how the fee endpoints derive their numbers
Bitcoin Core's coin selection — branch-and-bound and the waste metric, in full
Last updated
Was this helpful?