Verify

Check it yourself, do not take our word

Every universe on this site is a pure function of a Bitcoin block hash, and the function is the open-source BNBBANG engine. Verifying a Bitcoin universe has one step more than verifying a BNB universe: the NFT lives on BNB Smart Chain, but the block lives on Bitcoin, so you confirm the block on Bitcoin and everything else on BSC. Five steps, no account, no server involved.

Why the contract says verified = false

At mint time MirrorUniverse compares the submitted hash with blockhash(blockNumber) on BSC and stores the result in the verified field. BSC cannot look at Bitcoin, so that comparison fails for every Bitcoin block and verified is false for every Bitcoin universe. The contract records it and does not revert. This flag tells you nothing about a Bitcoin universe either way; step 2 below is the check that replaces it, and anyone can run it.

Step 1. Read the NFT on BSC

Call universeOf(id) on the MirrorUniverse contract at 0x1661086ee19cc8ef938e98ad9058e74fa870aa78 (BNB Smart Chain, chainId 56). BscScan's "Read Contract" tab works; so does any RPC client. The struct comes back in declaration order:

FieldMeaning for a Bitcoin universe
blockHashThe Bitcoin block hash, 32 bytes, the singularity
blockNumberThe Bitcoin block height
mintedAt, minterWhen it was minted on BSC and by whom
outcomeIndex into the contract's twelve outcomes, same order as the engine
verifiedfalse, always, for a Bitcoin block (see above)
rarity0 to 4 = S, A, B, C, D

cardOf(id) returns the cardHash the server signed, and tokenOfHash(blockHash) returns the token id for a hash (0 means nobody has minted it). Keep blockHash and blockNumber for the next step.

Step 2. Confirm the block on Bitcoin

Take the pair to Bitcoin. Either of these is enough:

# by hash: the page shows the height
https://mempool.space/block/<blockHash>

# by height: returns the hash as plain text
curl https://mempool.space/api/block-height/<blockNumber>

The hash at that height must equal the blockHash stored in the NFT. If it does, the NFT is a Bitcoin universe and the hash was on Bitcoin's main chain when you looked. If it does not, the NFT is not a Bitcoin universe; check the same pair on BSC and it will be a BNB universe. Any explorer or node gives the same answer, for example https://blockstream.info/api/block-height/<blockNumber> or bitcoin-cli getblockhash <blockNumber> on your own node. Do not judge by leading zeros: early Bitcoin blocks have only eight, and a small share of BNB blocks also start with eight.

The site itself serves the same data, cached, at /api/btc/block/<height|hash>, but the point of this step is to ask Bitcoin directly.

Step 3. Recompute the constants from the hash

The seed is the block hash with no transformation. Each parameter draws one slot with keccak256 over the hash followed by a single slot byte; the tier comes from slot 255:

u_i  = uint256(keccak256(blockHash ‖ uint8(i))) mod 1e9
tier = uint256(keccak256(blockHash ‖ uint8(255))) mod 1000

This is the BNBBANG derivation, unchanged; it lives in bnbhash.js in the engine folder of bnbbang-engine and exports derive(hash). The snippet below recomputes the first slots and the tier for the Bitcoin genesis block with nothing but ethers, so you can confirm the formula independently of the engine:

// npm i ethers   (v6)
const { keccak256, solidityPacked } = require('ethers');

// Bitcoin block #0, 2009-01-03
const hash = '0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f';

const slot = (i, m) => BigInt(keccak256(solidityPacked(['bytes32', 'uint8'], [hash, i]))) % BigInt(m);

for (let i = 0; i < 4; i++) console.log('u_' + i, slot(i, 1_000_000_000));
console.log('tier', slot(255, 1000));

// The full vector, in the engine's parameter order, from the engine itself:
//   const { derive } = require('./engine/bnbhash.js');   // inside a bnbbang-engine checkout
//   const d = derive(hash);  console.log(d.uInt, d.tier);

The values must match what derive(hash) prints. Note that the nineteen leading zeros of a modern hash (or the ten of the genesis block) make no difference here: keccak256 over the hash plus a byte is uniform whatever the input looks like.

Step 4. Run the engine and compare

git clone https://github.com/q3579338/bnbbang-engine
cd bnbbang-engine
node tools/recompute.js 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

recompute.js rebuilds the 23 parameters, the outcome, the grade and the cardHash from the hash alone. The derivation is integer-only, so the result is identical on every platform. Zero dependencies, Node 18 or later. Compare the outcome and the grade with the card on this site and with outcome and rarity from step 1; they must agree, and a badge on the card (genesis, halving, difficulty period, famous) must not change any of them.

Step 5. Recompute the cardHash

The cardHash is the fingerprint the server signs and the contract stores in cardOf(id). It is computed exactly as on BNBBANG, with derivation version 3:

cardHash = keccak256(abi.encode(
  bytes32  blockHash,
  uint32[22] uInt,        // the 22 parameter slots from step 3, engine order
  uint32   uG,            // slot 200: gravitational coupling
  uint32   uF1, uF2,      // slots 201, 202: the two reference-frame draws
  uint8    outcome,
  uint8    rarity,
  uint32   derivationVersion = 3
))

The one-command version of steps 3 to 5, which reads the NFT from BSC, recomputes everything from its blockHash and diffs parameters, outcome, grade and cardHash byte for byte:

node tools/verify-onchain.js <tokenId>

A universe that was rescued (constants nudged by burning BANG) is recomputed from the block hash plus the operation log written into the transaction, so the server's archive is not needed. The only thing verify-onchain.js does not check is that the hash is a Bitcoin block; that is step 2, and it needs Bitcoin, not BSC.

The engine repository ships 292 engine self-checks and 118 on-chain comparison tests. node engine/test.js runs the first set locally.

What the signature proves, and what it does not

Minting requires a server signature: the contract exposes only bangSigned(). The server fetches the hash for the requested height from Bitcoin itself (it does not trust the hash the client sends), refuses to sign below six confirmations or for a reserved block before its opening time, recomputes the parameters and stamps them. The signer address is readable on-chain via signer() and is listed below; anyone can check that an NFT's signature recovers to that address. The protocol, with a runnable verifier and real vectors, is in docs/signing-protocol.md.

The signature does not let the server invent universes: the signed parameters must equal what recompute.js produces from the hash, and that is exactly what verify-onchain.js checks. It also does not let the server invent Bitcoin blocks: the hash and height are stored in the NFT in the clear, and step 2 catches any pair Bitcoin does not recognise.

Contracts on BSC mainnet

chainId 56, deployed 2026-08-22, shared with bnbbang.com. No proxy, no upgrade path, no selfdestruct, no delegatecall, no pause. BTCBANG deploys nothing of its own.

Live checks: /api/health reports the chain id and the signer the server is using; they must match the contract. /api/btc/tip reports the Bitcoin height and hash the server currently sees and the number of confirmations it requires before signing. /api/market/status shows how far the market index is behind the chain.

Four things that are not trustless