Skip to content

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.

blockd derives scripthashes from raw public keys across four script types:

Script typePrefixAddress formatEra
P2PKp2pknone (raw pubkey)Pre-address Bitcoin (early chain)
P2PKHp2pkh1... (Base58Check)Legacy — original address format
P2SH-P2WPKHp2sh-p2wpkh3... (Base58Check)Wrapped SegWit — transitional
P2WPKHp2wpkhbc1q... (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.

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.

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}
}
}

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.

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.