Read and verify Bitcoin from your terminal. Zero dependencies, no Bitcoin Core required.
Reads the chain through the Mempool.space public API, or your own Mempool instance, and checks what it can by computation: address checksums offline, and a transaction against its block's Merkle proof and proof of work.
The $320M Liquid Network drain was negotiated on-chain. Two of its messages, decoded live — read the whole story.
pipx install btc-toolkit # or: pip install btc-toolkit · brew install devdavidejesus/tap/btc-toolkit
btc-toolkit fees # current fee rates
btc-toolkit opreturn f4ac7abcb689df30ec5e8d829733622f389ca91367c47b319bc582e653cd8cab
btc-toolkit verify c103de95817b43f2df635ec6f35ff126ca26a7c6d20570c4b01866b2b3e69a19 # check it is in a block
btc-toolkit validate bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq # offline: nothing sentTry it without installing anything: uvx btc-toolkit block 0.
Options go after the command: btc-toolkit fees --network signet, not
btc-toolkit --network signet fees. -n is short for --network, and
btc-toolkit <command> --help lists every option of a command.
| Command | Description |
|---|---|
btc-toolkit opreturn <txid> |
Decode OP_RETURN messages from a transaction |
btc-toolkit tx <txid> |
Transaction details: status, fees, size, input/output totals, RBF |
btc-toolkit address <address> |
Aggregated overview: type, balance, lifetime totals |
btc-toolkit balance <address> |
Confirmed + unconfirmed balance of any address |
btc-toolkit fees |
Recommended fee rates + mempool backlog |
btc-toolkit block <height|hash|latest> |
Block metadata by height, hash, or latest |
btc-toolkit utxo <address> |
Unspent outputs of any address |
btc-toolkit verify <txid> |
Check a transaction is in a block by computation: its Merkle proof and the header's proof of work |
btc-toolkit decode <rawtx> |
Decode a raw transaction offline (or --txid: fetch it and check its hash) |
btc-toolkit validate <address> |
Check an address offline: checksum, network, type — nothing is sent |
btc-toolkit inscription <txid> |
Ordinals inscription envelopes in a transaction: content type, size, SHA-256, text |
Requirements: Python 3.11+. Supported versions follow CPython's own support window: a Python version is dropped in the first minor release after it reaches end of life (policy).
pip install btc-toolkitOr isolated, via pipx:
pipx install btc-toolkitOr with uv: run it once without installing anything, or install it as a tool:
uvx btc-toolkit block 0
uv tool install btc-toolkitOr via Homebrew on macOS and Linux, with shell completion included:
brew install devdavidejesus/tap/btc-toolkitFrom source:
git clone https://github.com/devdavidejesus/btc-toolkit.git
cd btc-toolkit
pip install -e .Shell completion (optional): static scripts in
completions/ for bash and zsh — tab-complete
commands, networks and flags, zero dependencies as always. The Homebrew formula installs them for you.
btc-toolkit tx f4ac7abcb689df30ec5e8d829733622f389ca91367c47b319bc582e653cd8cabShows confirmation status and block, fee and fee rate (sat/vB), total input/output, size/weight/vsize, version, locktime — and flags coinbase and RBF-signaling transactions.
# Every input (the output it spends, value, address) and every output (address, spent or not, and by what)
btc-toolkit tx <txid> --inputs --outputs
# JSON output for scripting
btc-toolkit tx <txid> --jsonAddresses in --inputs/--outputs are computed locally from each output script, not taken from the API. With --api-url and no -n, the chain is unknown: the API's address is shown if it is a valid address (it is not checked against the script).
btc-toolkit address 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNaOne call, full picture: address type (P2PKH, P2SH, P2WPKH, P2WSH, P2TR, P2A or witness_vN — decoded offline from the checksum-verified address, per BIP 13/173/350), confirmed and unconfirmed balance, lifetime received/spent, and transaction counts.
# JSON output for scripting
btc-toolkit address <address> --jsonbtc-toolkit balance 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNaShows the confirmed balance in BTC and satoshis and, when the address has a non-zero unconfirmed amount, that amount and the total, plus confirmed and mempool transaction counts. Supports all address types: Legacy (P2PKH), P2SH, SegWit (Bech32), and Taproot.
# JSON output for scripting
btc-toolkit balance <address> --json
# Testnet or signet
btc-toolkit balance <address> --network testnet
btc-toolkit balance <address> --network signetBTC conversion uses integer arithmetic (no floats) — satoshi-exact, always.
btc-toolkit feesShows the five recommended fee tiers (sat/vB) — fastest, half hour, hour, economy, minimum — plus mempool backlog: pending tx count, size in vMB, and a rough estimate of blocks needed to clear it.
# JSON output for scripting
btc-toolkit fees --json
# Testnet
btc-toolkit fees --network testnetbtc-toolkit block latest # chain tip
btc-toolkit block 0 # by height (genesis)
btc-toolkit block 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f # by hashShows height, hash, mined timestamp (UTC), tx count, size, weight, difficulty, nonce, and previous block hash.
# JSON output for scripting
btc-toolkit block latest --json
# Testnet
btc-toolkit block latest --network testnetbtc-toolkit utxo bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdqLists UTXOs sorted by value (largest first; the first 15 by default, see --limit), with txid:vout, value in BTC and sats, confirmation status, and block height. Shows aggregate count and total value.
Known limitation: addresses with tens of thousands of UTXOs (e.g. Satoshi's genesis address, which has received over 80,000 outputs) exceed the upstream electrs response limit and return HTTP 400. Use
balancefor aggregate stats on such addresses — discovered and verified in production.
# Only confirmed UTXOs
btc-toolkit utxo <address> --confirmed-only
# Show more than 15 entries
btc-toolkit utxo <address> --limit 50
# JSON output (ignores --limit; --confirmed-only still applies)
btc-toolkit utxo <address> --jsonbtc-toolkit opreturn f4ac7abcb689df30ec5e8d829733622f389ca91367c47b319bc582e653cd8cab# JSON output
btc-toolkit opreturn <txid> --json
# Raw hex only
btc-toolkit opreturn <txid> --rawKnown protocol markers are named by their prefix: a Runes Runestone (OP_RETURN OP_13), Omni Layer (a payload starting with omni — a plain text message can start that way too), and the BIP 141 witness commitment in a coinbase. They are tagged, not decoded.
Seen in the wild: the $320M Liquid Network drain (Sept 2026) was negotiated on-chain via OP_RETURN — every message of it decoded with this command, transaction IDs included: I Read a $320M Ransom Negotiation From My Terminal. And the wallet holding 562 BTC from the Coldcard hack (July 2026) became a message board: 49 OP_RETURN messages, read with opreturn --file: 49 Messages to a Thief. btc-toolkit is also listed among the developer tools in Jameson Lopp's Bitcoin resources.
Real, verified OP_RETURN transactions on mainnet. Verify each one yourself on mempool.space.
| TXID | Description |
|---|---|
f4ac7abcb689df30ec5e8d829733622f389ca91367c47b319bc582e653cd8cab |
"Craig Wright is a liar and a fraud" — 34 bytes (verify on-chain) |
2033435de7ce307341231e818ed937cd3a5e8597381fd83a7e5b0234f61b38d3 |
"learnmeabitcoin" — 75-byte OP_RETURN with null-padded ASCII (verify on-chain) |
Note: Satoshi's famous "Chancellor on brink of second bailout for banks" message is in the coinbase scriptSig of the genesis block — NOT in an OP_RETURN output. That's a common misconception. This tool reads OP_RETURN outputs only, which is the standard mechanism for embedding data in Bitcoin transactions (introduced as standard in Bitcoin Core v0.9.0, March 2014).
btc-toolkit verify c103de95817b43f2df635ec6f35ff126ca26a7c6d20570c4b01866b2b3e69a19Instead of taking the API's "confirmed" on faith, verify recomputes from raw bytes:
- the txid, by hashing the raw transaction;
- the Merkle root, from that txid and the proof's sibling hashes — refusing any pair of nodes that is also a valid 64-byte transaction, which could pose as a node (CVE-2017-12842);
- the block hash, by hashing the 80-byte header — which must contain that root;
- the proof of work: the header hash is below the target its
bitsencode, within the network's limit.
Any mismatch is verification failed (exit code 1). The verdict depends only on the work proven — never on the height or date the API claims, since a forger picks those:
✓ VERIFIED("work": "strong"): a mainnet header at difficulty 50 T or more. Forging one takes about 2^77.5 hashes — over a third of a block's work at October 2026's difficulty (about 133 T).⚠ CONSISTENT, WEAK WORK("work": "weak"): everything checks out, but the header carries less work — an older block, or testnet, signet (block signatures are not checked) or an unknown chain. That little work is cheap to fake: treat it as consistency, not proof.
With your own instance, add -n mainnet so verify knows which chain it is checking: btc-toolkit verify <txid> --api-url http://umbrel.local:3006/api -n mainnet.
What it does not prove: the block height (the API reports it; a header does not contain it), or that the block is in the chain everyone follows. Only a full node checks everything.
btc-toolkit decode 0100000001... # or pipe it: cat tx.hex | btc-toolkit decode -
btc-toolkit decode --txid <txid> # fetch the raw tx and check it hashes to <txid>Version, inputs, outputs (with their addresses and script types), witness, locktime, txid and wtxid, size, weight and vsize — computed from the bytes. The fee is not shown: it needs the values of the outputs being spent, which a raw transaction does not carry (tx has it).
btc-toolkit validate bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdqVerifies the checksum (Base58Check, or bech32 / bech32m per BIP 173 and BIP 350, tested against their official vectors), the network and the type — P2PKH, P2SH, P2WPKH, P2WSH, P2TR, P2A, or witness_vN for future versions. Nothing is sent anywhere. balance, address and utxo run the same check first, so a typo is caught locally (exit code 2) and never reaches the API. With --api-url and no -n, the chain is unknown, so an address of any network is accepted.
btc-toolkit inscription 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799Finds the OP_FALSE OP_IF "ord" … OP_ENDIF envelopes in each input's Taproot script — parsed by ord's own rules, from its source — and shows the inscription ID, content type, encoding, metaprotocol, parents, delegate, body size and SHA-256, and the text of a text inscription (control characters escaped). This is envelope detection, not the ord index: it does not assign inscription numbers, track sats or owners, or apply ord's "cursed" rules. The witness comes from the API: a txid does not commit to it, so it is not verified.
btc-toolkit/
├── btc_toolkit/
│ ├── __init__.py # Package version
│ ├── __main__.py # python -m entry point
│ ├── cli.py # Unified CLI: subcommands, batch mode, env vars, exit codes
│ ├── api.py # Shared Mempool HTTP client: retry, timeout, custom base URL
│ ├── colors.py # Shared terminal color helpers
│ ├── opreturn.py # OP_RETURN decoder
│ ├── balance.py # Address balance
│ ├── fees.py # Fee estimator
│ ├── block.py # Block explorer
│ ├── utxo.py # UTXO inspector
│ ├── tx.py # Transaction inspector
│ ├── address.py # Address overview + offline type detection
│ ├── encoding.py # Offline: bech32/bech32m, Base58Check, script types and addresses
│ ├── rawtx.py # Offline raw transaction decoder (legacy and SegWit)
│ ├── verify.py # Merkle-proof and proof-of-work verification
│ ├── inscription.py # Ordinals inscription envelope detection
│ └── py.typed # PEP 561 marker: the package ships its types
├── tests/ # One file per module, plus CLI, completion and robustness tests — all API calls mocked
├── fuzz/ # Atheris fuzzer for the parsers of untrusted data
├── completions/ # Static bash + zsh completions
├── assets/ # Logo, icon, social preview and the README demos
├── .github/
│ ├── workflows/ # CI, release, CodeQL, fuzz, Scorecard, weekly live smoke test
│ ├── scripts/ # Release notes, live smoke test, demo recorder, brand assets
│ └── requirements/ # Hash-pinned CI tools
├── docs/
│ ├── json-schema.md # --json output per command + stability policy
│ ├── python-api.md # Using the toolkit as a typed Python library
│ └── releases.md # How releases are built and verified
├── SECURITY.md # Threat model + vulnerability reporting
├── CONTRIBUTING.md # How changes are made and what they must include
├── CHANGELOG.md # Every release, newest first
├── pyproject.toml
├── LICENSE # MIT
└── README.md
Every subcommand shares one HTTP client (api.py) — a new command adds a module, a subcommand, its completions and tests; the HTTP layer stays untouched.
Zero external dependencies — Python standard library only (urllib, json, argparse).
- Retry with backoff — transient failures (HTTP 429, 500, 502, 503, 504 and network errors) get up to 3 attempts, with exponential backoff between them (0.5s, then 1s). Any other HTTP error (e.g. 400, 404) fails immediately.
- Sovereignty —
--api-urlpoints every command at your own Mempool instance;--networkcovers mainnet, testnet and signet. - Bounded waits —
--timeout(default 15s) applies to each network operation of each attempt, so with retries a command can wait longer than the timeout. - Exit codes —
0success,1network/API error (including averifywhose checks fail, or a transaction not confirmed yet),2invalid input. Script accordingly. - Verifiable releases — since v1.5.0, published from GitHub Actions via PyPI Trusted Publishing, with PEP 740 attestations tying each file to this repo; since v1.6.1, every GitHub Release also carries Sigstore-signed files (how to verify).
python -m unittest discover -s tests -t . -vEvery command is tested with all API calls mocked — the suite runs offline, from the source tree, with nothing to install (pytest works too if you prefer it). Every pull request measures line and branch coverage and fails if it drops below 90%. The package is type-checked with mypy --strict and ships py.typed. A separate weekly job reads known on-chain facts from mainnet, and an Atheris fuzzer exercises every parser of untrusted data on each change.
The Mempool.space /address endpoint returns chain_stats (confirmed) and mempool_stats (unconfirmed), each with funded_txo_sum and spent_txo_sum in satoshis.
confirmed = chain_stats.funded_txo_sum - chain_stats.spent_txo_sum
unconfirmed = mempool_stats.funded_txo_sum - mempool_stats.spent_txo_sum
total = confirmed + unconfirmed
This is the same model used by Esplora/Electrs. Don't trust this README — verify against https://mempool.space/api/address/<address> yourself.
btc-toolkit is built to sit inside pipelines. Every command takes --json,
and the commands that take a txid, address or block read one item per line
from stdin (-) or a file (--file), emitting JSON Lines you can pipe straight
into jq (decode - reads a single raw transaction from stdin):
# many txids -> one JSON object per line
cat txids.txt | btc-toolkit tx - --json | jq -r '.fee_rate_sat_vb'
# or from a file (blank lines and lines starting with # are ignored)
btc-toolkit balance --file addresses.txt --jsonConfiguration through the environment — for Docker, CI, cron:
| Variable | Effect |
|---|---|
BTC_TOOLKIT_API_URL |
Default for --api-url (your own Mempool instance); ignored by validate and by decode without --txid. With it, the chain is custom: to name it, pass --api-url and -n together (-n alone switches to the public presets) |
BTC_TOOLKIT_NETWORK |
Default for --network (mainnet, testnet, signet; anything else is an error, exit 2; ignored when an API URL is set) |
BTC_TOOLKIT_TIMEOUT |
Default for --timeout (seconds, default 15) |
NO_COLOR |
Any non-empty value disables colors (no-color.org) |
Flags always win over environment variables. Exit codes are a contract
(0 ok · 1 network/API failure · 2 invalid input) in both text and JSON
mode; in batch mode the worst code wins. Full output schemas and the
stability policy: docs/json-schema.md.
Every command is also a plain, typed function — the package ships py.typed, so your type checker sees real types:
from btc_toolkit.opreturn import decode_op_return
for out in decode_op_return("c103de95817b43f2df635ec6f35ff126ca26a7c6d20570c4b01866b2b3e69a19"):
print(out.decoded_text) # we are whitehats. contact us on chainFunctions, result types and errors: docs/python-api.md.
Every command accepts --api-url pointing to any self-hosted
Mempool instance, which runs on top of your
own Bitcoin node (Umbrel, Start9, RaspiBlitz and similar node stacks offer it as an app):
btc-toolkit balance <address> --api-url http://umbrel.local:3006/api -n mainnet-n tells btc-toolkit which chain your instance serves. Without it the chain is
custom (unknown): addresses of any Bitcoin network (mainnet, testnet/signet,
regtest) are accepted, tx shows the API's addresses, and verify reports the
work as weak. Results never link to mempool.space when you use your own instance.
With your own instance, no third party sees your queries — the public mempool.space API is the zero-setup default, not a requirement.
The full threat model — what the tool protects against and what it does not — lives in SECURITY.md; how releases are built and how to verify one yourself is in docs/releases.md.
This is an explorer client for the terminal — a fast, scriptable way to inspect the Bitcoin blockchain without running infrastructure. Ideal for learning, scripting, quick lookups, and teaching how Bitcoin data is structured.
This isn't a substitute for a full node. Data comes from a Mempool API
(mempool.space by default, or your own instance), and most commands report what
it says. Some checks don't trust it: addresses are validated offline, decode
works on raw bytes, and verify recomputes a transaction's inclusion in a block
— its Merkle proof and the block's proof of work — instead of taking "confirmed"
on faith. What no command does: validate blocks, follow the most-work chain, or
check consensus rules. For sovereign, trustless verification, run
Bitcoin Core and query it directly.
- Phase 1 — OP_RETURN Reader
- Phase 2 — Address Balance Checker
- Phase 3 — Fee Estimator (mempool-based)
- Phase 4 — Block Info Explorer
- Phase 5 — UTXO Set Inspector
The original roadmap shipped in v1.0.0; every release since is in the CHANGELOG. What's next lives in the issues — one philosophy throughout: zero dependencies, no Bitcoin Core, verify everything on-chain.
Every txid, address, hex value, and technical claim in this README can be independently verified:
- Transaction data:
https://mempool.space/api/tx/<txid> - Address data:
https://mempool.space/api/address/<address> - Fee data:
https://mempool.space/api/v1/fees/recommended - Block data:
https://mempool.space/api/block/<hash> - UTXO data:
https://mempool.space/api/address/<address>/utxo - OP_RETURN spec: learnmeabitcoin.com/technical/script/return
- Esplora API model: github.com/Blockstream/esplora/blob/master/API.md
btc-toolkit is free, MIT-licensed and has no sponsor. If it's useful to you, you can support its development on-chain:
Bitcoin: bc1qulq8xfcmgxxumjx3yndkfktqgw2exh0rym45sn
(Public address — donations are visible on-chain, as everything here is.)
Found a bug or want to propose or build a new command? Open an issue or a PR. Every contribution keeps the core rules — stdlib only, tests with every change, claims verifiable on-chain. The process and requirements are in CONTRIBUTING.md.
Licensed under MIT · Built by @devdavidejesus
"Don't Trust, Verify."

