For block explorers
You want to ingest the full Exfer chain, store it queryable, and surface it to users on a web UI. Concretely:
- Run an archive node — every block, every transaction, available indefinitely.
- Index block / transaction / address data into a search-friendly store (Postgres, Elasticsearch, ClickHouse, whatever).
- Surface a web UI with the usual explorer features.
- Stay live as new blocks arrive, and handle reorgs.
This page sketches the data plane. The community-hosted explorer at https://explorer.exfer.dev is one reference implementation.
Archive node
Run a regular full node — Exfer doesn't prune by default, so a regular
exfer node is an archive node.
exfer node \
--datadir /var/lib/exfer \
--rpc-bind 127.0.0.1:9334 \
--repair-perms
For maximum integrity, disable assume-valid so every block's Argon2id PoW is verified during sync:
exfer node --datadir /var/lib/exfer --rpc-bind 127.0.0.1:9334 --no-assume-valid
(Slow first sync; worthwhile for an explorer where you don't want to trust the binary author's checkpoint.)
Initial ingest (cold start)
Walk every block from genesis to tip:
for h in 0 .. tip:
block = get_block(height=h)
insert blocks(h, block.hash, block.timestamp, ...)
for tx_id in block.transactions:
tx = get_transaction(hash=tx_id)
decode tx.tx_hex per protocol-spec §5
insert txs(tx_id, block_hash, h, raw_hex, ...)
for each input: insert tx_inputs(...)
for each output: insert tx_outputs(tx_id, idx, value, address, is_coinbase)
This is bounded by your RPC + DB throughput, not by the node — a local node easily serves tens of thousands of requests per second.
To compute address balances (the most popular explorer query), you
need to track which outputs are spent. The simplest model is a flat
utxos table that you INSERT on output creation and DELETE on
input consumption:
CREATE TABLE utxos (
tx_id BYTEA NOT NULL,
output_index INTEGER NOT NULL,
address BYTEA NOT NULL,
value BIGINT NOT NULL,
height BIGINT NOT NULL,
is_coinbase BOOLEAN NOT NULL,
PRIMARY KEY (tx_id, output_index)
);
CREATE INDEX ON utxos(address);
Address balance is then SELECT SUM(value) FROM utxos WHERE address = ?.
Stay live
After the initial ingest, poll the tip and apply new blocks in order:
H = last_indexed_height
every 5-10 seconds:
tip = get_block_height()
while H < tip:
H = H + 1
ingest block at H
Handle reorgs
The chain occasionally re-organizes — a block at height h is replaced
by a different block at the same height. You must detect this and
unwind your index.
Approach: persist block_hash alongside height for each indexed
block. Before ingesting H+1, verify the local H's block_hash
still matches get_block(height=H).hash:
while True:
local = lookup_block(height=H)
remote = get_block(height=H)
if local.hash != remote.hash:
# reorg detected at H
unwind H and walk backwards until match
H = H - 1
continue
break
# now safe to ingest H+1
When unwinding, undo each transaction's effects:
- Re-insert UTXOs that were spent in that block.
- Delete UTXOs that were created in that block.
- Mark the orphaned block / txs as "reorged out" rather than deleting outright (users may have URLs pointing at them).
For shallow reorgs (depth < 5) this is rare and fast. Deep reorgs require special care — flag for human review.
Address derivation
Outputs reference an address (32 bytes) that's a hash of the locking
script. Most outputs are plain pay-to-pubkey-hash style; non-trivial
outputs (HTLC, multisig, vault, etc.) lock to a script-hash address
where the address itself doesn't reveal the underlying script.
To surface "this UTXO is an HTLC locked to receiver R", you need to:
- Recognize the script template at decode time (the upstream source enumerates the known patterns).
- Stash a
script_templatefield on each output during indexing.
For arbitrary scripts, get_script_utxos
returns UTXOs for an exact script hex; useful for indexing custom
covenants if your explorer supports them.
Performance notes
- The RPC
get_blockreturns only the txid list; the bodies require N additionalget_transactioncalls per block. For high-throughput ingest, parallelize tx fetches per block (subject to the connection cap). - Run the explorer's node and DB on the same host or LAN — RPC over the internet is the easy bottleneck.
- Index lazily for cold data, eagerly for hot data (recent blocks).
See also
- JSON-RPC reference — the API.
- Protocol specification §5 — how to
deserialize
tx_hex. - The hosted explorer at https://explorer.exfer.dev for a reference user experience.