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:
| Field | Meaning for a Bitcoin universe |
|---|---|
blockHash | The Bitcoin block hash, 32 bytes, the singularity |
blockNumber | The Bitcoin block height |
mintedAt, minter | When it was minted on BSC and by whom |
outcome | Index into the contract's twelve outcomes, same order as the engine |
verified | false, always, for a Bitcoin block (see above) |
rarity | 0 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.
| Contract | Address |
|---|---|
| BangToken (BEP-20) | 0xa5306b022f24b920aeafbff9f0dba1a391c4442d |
| MirrorUniverse (NFT) | 0x1661086ee19cc8ef938e98ad9058e74fa870aa78 |
| MirrorMarket | 0x5f1def4874613b64bac2272424f2041e1359b001 |
| BangNames | 0x6a732c3dd5555ee6c60c380e4e53b9a1f9cb17d6 |
| BangNames2 | 0x36e5d8358b9d97ab41ac9daf1ee962f4ec6001b0 |
| MirrorCrafted | 0x12ce81dbfbfae6614ae98a64a223a9369b43d30a |
| BangVesting (team 100M) | 0xb3e1af9f2b57f9ed6d73a75a895f66f757e6e0e1 |
| BangPromo (listing fees 100M; the proposed launch-fund source) | 0xaddd4a144b7371afc37cb590fa5570a0f2e72179 |
| Referral treasury (200M) | 0xfaec0579f6f15ec7e5048b0e11e273a6e40641c6 |
Signer (on-chain signer()) | 0x81F355093ef706bA5a6e74e9F286C28A479D33Ee |
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
- Minting needs the server stamp. Contract-level requirement, not a frontend choice; the signer is on-chain and the stamped parameters are recomputable.
- The server decides when a Bitcoin block is mintable. Six confirmations, and the five reserved blocks (genesis and the four halvings) only after an announced opening time. It cannot change what a block's universe looks like, only whether it signs today.
- Referral rebates are paid after human review. Tier-1 10%, tier-2 5% of the invitee's mint reward; sybil patterns are not paid. Each on-chain grant carries a reason.
- A few owner knobs exist, all bounded. The three free-mint switches can only be tightened; rescue skim defaults to 0 (cap 20%); market fees cap at 10%; per-mint issuance sits in 100–2000.