Don't trust, verify

Is the bitcoin really gone?

Every claim this app makes can be checked without it. This page shows how, from a one-line script check to your own node. No step needs our server, our code, or our word.

Why it is unspendableRecompute an addressAsk your nodeCheck the balanceRecount the votesAudit this pageWhat you cannot verify

Why a topic address can never be spent

A topic address is a pay-to-witness-script-hash (P2WSH) address. To spend coins sent to it, a transaction must reveal the script whose hash matches, then execute that script, and the script must finish with true on the stack.

The script behind every topic is OP_RETURN <topic name>. The Bitcoin script rules say that executing OP_RETURN marks the transaction as invalid, immediately, whatever comes after it. So the reveal is possible, the execution never succeeds, and no witness data anyone could ever construct will unlock the coins. Every full node on the network enforces this. It is not a lost key, it is a rule.

Compare with a "burn address" like 1111111111111111111114oLvT2. That one is a normal pay-to-pubkey-hash address whose hash nobody has found a key for. Its unspendability is a belief about preimages, and it silently mixes in every other project that used the same address. A topic address is unspendable by consensus, and it is unique to its name.

Reference: BIP 141 (segregated witness, P2WSH), and the script interpreter in Bitcoin Core where OP_RETURN returns SCRIPT_ERR_OP_RETURN.

Recompute a topic address yourself

The address is a pure function of the name. Type any topic name below. Everything is computed in your browser from the name alone, with the same steps you can reproduce by hand.

1 · bytes
2 · script
3 · sha256
4 · address
mempool.space ↗blockstream.info ↗

UTF-8 bytes of the name

Lowercase, canonical spelling, exactly as the spec defines it. One byte off is a different address.

Wrap it in a script

6a is OP_RETURN. Then a push: one length byte when the name is 75 bytes or shorter, 4c plus a length byte otherwise. The root topic, whose name is empty, is the single byte 6a.

SHA-256 the script

P2WSH commits to the plain SHA-256 of the witness script, not the double hash and not HASH160.

Encode as bech32

Witness version 0, the 32-byte hash as the program, prefix bc for mainnet. Bech32, not bech32m, because the version is 0.

Ask your own node

Bitcoin Core will decode the script and tell you the address itself, with no code from us involved. Paste the script hex from step 2 above.

# the script for the topic "pizza"
bitcoin-cli decodescript 6a0570697a7a61

# look at .segwit.address in the answer
{
  "asm": "OP_RETURN 70697a7a61",
  "type": "nulldata",
  "segwit": {
    "address": "bc1qzssvapwlcv4um44xqtqay7h04hkq3latjtrxedj7st5d0kv06mhqsxxzg7",
    "type": "witness_v0_scripthash"
  }
}

If that address matches the one the app shows for the topic, the app is honest about where the burn goes. If it does not, stop using that copy of the app.

Go further: read everything through your node. The app takes any Esplora-style API as its data source, so a node plus an indexer replaces the public explorer entirely. Run your own node has a docker-compose file, what it costs in disk and time, and how to check the app really talks to it.

Check that nothing ever leaves

Because the outputs can never be spent, every sat ever sent to a topic address is still sitting in the UTXO set. That gives a direct check that needs no history and no third party.

# every output ever paid to the topic is still unspent
bitcoin-cli scantxoutset start '["addr(bc1qzssvapw…qsxxzg7)"]'

# total_amount is the burned total, unspents lists every burn
{
  "success": true,
  "total_amount": 0.01200000,
  "unspents": [ … ]
}

On a block explorer the same fact shows as received equal to balance and spent equal to zero, for the whole life of the address. If a topic address ever shows an outgoing transaction, the protocol is broken and this page is wrong. It will not happen, and you do not have to take that on faith.

Recount the votes

The leaderboard is a sum over public data. Any Esplora-compatible API returns the transactions of an address with their outputs decoded, so a recount is a short script. This one talks to mempool.space; swap the base URL for your own instance.

// node recount.js bc1q…   (no dependencies)
const addr = process.argv[2], base = "https://mempool.space/api";
const norm = t => t.trim().toLowerCase().replace(/\s+/g, " ").replace(/[.!?]+$/, "");
const tally = {}; let last = "";
for (;;) {
  const page = await (await fetch(`${base}/address/${addr}/txs/chain/${last}`)).json();
  if (!page.length) break;
  for (const tx of page) {
    const burn = tx.vout.filter(o => o.scriptpubkey_address === addr).reduce((a, o) => a + o.value, 0);
    const op = tx.vout.find(o => o.scriptpubkey_type === "op_return");
    const text = op ? Buffer.from(op.scriptpubkey_asm.split(" ").pop(), "hex").toString("utf8") : "";
    const k = norm(text); tally[k] = (tally[k] || 0) + burn;
  }
  last = page[page.length - 1].txid;
}
console.table(Object.entries(tally).sort((a, b) => b[1] - a[1]));

Compare the numbers with the app. Differences can only come from three places: a different normalisation of the statement text, unconfirmed transactions the app shows and the script skips, or an explorer that is lying to one of you. The spec fixes the first, the mempool explains the second, and your own node settles the third.

Audit the page itself

The app is a handful of static HTML files with no backend: each page is self-contained, and the only other files it serves are the snapshots, static JSON caches of already-scanned topics that the page treats like any explorer answer (spec). View the source. The network requests a page makes go to the explorer endpoint you selected, to its own snapshots, and to the font and QR library; you can watch every one of them in your browser's network tab.

Nothing on a page can move your coins without a wallet. The Advanced block of every burn shows the outputs and an unsigned transaction with no inputs, for any wallet to fund and sign after you have read the address and the OP_RETURN. The optional burner wallet is a small BIP39 wallet whose keys stay in this browser, encrypted with your passphrase, meant to hold only sats you intend to burn; it signs and broadcasts through the same endpoint, and a hardware wallet confirms each burn on its own screen instead. The ballot and the watchlist are conveniences of the same browser: nothing about them reaches the chain.

If you do not trust the copy you are looking at, save it, read it, and open it from your disk. It works the same.

What you cannot verify, and what you should not trust

The tip address is spendable. By design. It belongs to the people running this copy of the app, and a tip is a gift, not a burn. Untick it if you only want the burn.

An explorer can lie or lag. Public APIs are a convenience. Anything that matters should be checked against your node, or at least against two independent explorers.

Unconfirmed is not final. A burn in the mempool can be replaced or dropped. Treat anything under a few confirmations as pending.

Nobody controls a topic. That includes us. Anyone can burn anything into any topic. The page can only bucket and rank, never delete.