Wallets & ScriptPubKeys
ScriptPubKeys, script types, and the two ways to populate a pubkeys_set
blockd tracks Bitcoin ownership at the level of ScriptPubKeys — the locking scripts that control outputs. “Wallet” is a convenient shorthand for a collection of those scripts along with their transaction history, and blockd uses it throughout, but the model underneath is always ScriptPubKeys.
blockd tracks two things for any set of ScriptPubKeys: the complete transaction history — every transaction that ever created or spent an output locked to those scripts — and the current UTXO set derived from that history.
Script types
Section titled “Script types”blockd derives scripthashes from raw public keys across four script types:
| Script type | Prefix | Address format | Era |
|---|---|---|---|
| P2PK | p2pk | none (raw pubkey) | Pre-address Bitcoin (early chain) |
| P2PKH | p2pkh | 1... (Base58Check) | Legacy — original address format |
| P2SH-P2WPKH | p2sh-p2wpkh | 3... (Base58Check) | Wrapped SegWit — transitional |
| P2WPKH | p2wpkh | bc1q... (Bech32) | Native SegWit |
P2PK outputs predate Bitcoin’s address conventions entirely. They lock directly to a
public key rather than a hash of one. There is no address to derive — the public key
itself is the identifier. blockd constructs the P2PK script (<pubkey> OP_CHECKSIG) and computes
the scripthash from that script, so P2PK outputs from the early chain are fully trackable
alongside modern script types.
Two ways to populate a pubkeys_set
Section titled “Two ways to populate a pubkeys_set”All four script types converge on the same internal representation — a scripthash. How you provide the source material to blockd determines which workflow step you use.
Extended public key derivation
Section titled “Extended public key derivation”For HD wallets, provide an xpub/ypub/zpub and blockd derives the full address set
according to a scanspec. blockd interprets the key encoding as a script type hint —
zpub as P2WPKH, ypub as P2SH-P2WPKH, and xpub as P2PKH. This follows the SLIP-0132
convention. blockd derives both external (receive) and internal (change) chains.
Three scanspec forms are supported:
Gap limit — derive addresses starting at index from until gap_limit consecutive
unused indexes are found. The standard approach for HD wallet imports:
{"from": 0, "gap_limit": 20}Count — derive count addresses starting at index from, regardless of usage:
{"from": 0, "count": 4000}Range — derive addresses from index from through index to inclusive. to must be
greater than or equal to from:
{"from": 500, "to": 1000}Count and range are useful when gap limit scanning is impractical — large wallets with known index ranges, archival imports, or cases where you need a deterministic address count regardless of chain activity.
Full step example using gap limit:
{ "step": "pubkeys.extpubkey.import", "args": { "extpubkey": "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", "scanspec": {"from": 0, "gap_limit": 20} }}Discrete import
Section titled “Discrete import”For non-HD contexts — individual addresses, watch-only outputs, or P2PK outputs from early blocks — provide source material directly. Two formats are accepted.
Plain addresses:
{ "step": "pubkeys.pubkey.import", "args": { "addresses": [ "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", "bc1qp7lsyxsf0p9049dgea2t0cynx6d3ama0xe3pdv" ] }}Raw public keys with explicit script type prefix:
{ "step": "pubkeys.pubkey.import", "args": { "pubkeys": [ "p2wpkh:02d8e638a311b13f6a097b33fead3fe9b02b1e04c9a28a87e80b3598fc7a2c4d90", "p2pkh:02d8e638a311b13f6a097b33fead3fe9b02b1e04c9a28a87e80b3598fc7a2c4d90", "p2sh-p2wpkh:02d8e638a311b13f6a097b33fead3fe9b02b1e04c9a28a87e80b3598fc7a2c4d90", "p2pk:04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f" ] }}p2sh-p2wpkh and p2shwpkh are both accepted as the prefix for wrapped SegWit keys.
The pubkeys format is necessary when tracking a key across multiple script types, or
when handling P2PK outputs that have no address representation. addresses and pubkeys
can be combined in a single step:
{ "step": "pubkeys.pubkey.import", "args": { "addresses": [ "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu" ], "pubkeys": [ "p2pk:04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f" ] }}Derived and discrete addresses cannot be mixed in the same pubkeys_set. A pubkeys_set is either xpub-derived or discretely composed. If you need both, use separate pubkeys_sets and query them independently.
What this means in practice
Section titled “What this means in practice”Most developers working with modern HD wallets will use pubkeys.extpubkey.import and
rarely think about script types directly — the xpub, ypub or zpub encodes the intent. The
discrete import path exists for the cases where HD derivation doesn’t apply: archived
early-chain keys, individual watch addresses, or keys that were used across multiple script
types over their lifetime.
From blockd’s perspective, both paths produce the same thing: a set of scripthashes to query against the indexer, subscribe to for new activity, and use to identify inputs and outputs in the transaction history. The pubkeys_set is the named container for that set. The next page covers how pubkeys_sets are named, managed, and secured.