Every puzzle, one record.
332 public crypto bounties and puzzles in 10 collections, 907.88 BTC of it still unclaimed. Each one is a typed record: address, key material, prize, what happened on chain. One library behind the CLI, an MCP server and the Pi and OMP extensions, with live balances from the explorers.
Data as code, one file per puzzle
There's no JSON to edit and no build step that generates data. Each puzzle is a TypeScript file with one literal in it, and the type checker reads it before any test does. The panel walks through 14 records from every collection, key material included, the way they sit on disk.
- A record is a PuzzleSpec literal handed to a factory for its chain: bitcoinPuzzle, decredPuzzle, arweavePuzzle
- Addresses, keys and transactions come from builders, so an absent field is absent, never null
- Status is written down, not derived. A claim transaction plus a published key still means solved
import { bitcoinPuzzle, compressed, hex, p2pkh, Status } from "@agntn/puzzles"; export const b1000Puzzle1 = bitcoinPuzzle({ id: "b1000/1", address: p2pkh("1BgGZ9tcN4…26SAMH", "751e76e819…433bd6"), sourceUrl: "https://bitcointalk.org/index.php?topic=521…", startedAt: "2013-01-09 11:59:15", status: Status.Solved, prize: 0.001, pubkey: compressed("0279be667e…f81798"), key: hex("0000000000…000001", 1).wif("KwDiBf89Qg…VHnoWn"), transactions: [ /* 2 recorded */ ],});A key that derives its address, or a plain no
A published key is a claim, so the library checks it: derive the address, compare. The data gate runs the same check on every record, so a wrong hex digit fails the build instead of shipping. Here it runs on the current sample with the code the CLI ships.
- verifyPuzzle reads the one secret a record exposes: hex, WIF, BIP38 payload, seed phrase or mini key
- A solved puzzle with its key published derives the stored address, in your browser, secp256k1 and all
- No key, an encrypted key, a mini key: unavailable is a verdict, not an exception
puzzleb1000/1
secp256k1 · in your browser
secretOf(puzzle.keyData()){ kind: "hex" }
a raw private key, verification derives the address from it
puzzle.hasPubkey()true
compressed 0279be667ef9dc…5b16f81798
verifyPuzzle(puzzle){ verified: true, derivedAddress: "1BgGZ9tcN4…SZ26SAMH" }
the derived address equals the stored one
A manifest of keys, records on first use
Importing the package evaluates no puzzle records. The registry is ten keys and ten import() calls. A bundler splits each collection into its own chunk. Only ever ask about b1000? You never download the other nine. This panel is that mechanism running: the walk pulls collections in one at a time.
- collectionKeys() and hasCollection() answer from the manifest without loading anything
- get('b1000/71') imports the b1000 module and nothing else; all() loads every collection once
- registerCollection({ key, load }) adds yours, lazily too, and the aggregate views refresh
await get("b1000/1")
0 of 10 modules
- arweave manifest
- b1000 loading
- ballet manifest
- bitaps manifest
- bitimage manifest
- gsmg manifest
- hash_collision manifest
- rushwallet manifest
- warp manifest
- zden manifest
Ten collections, five chains, one page per puzzle
Each collection is a class with an author and its puzzle list, published on its own entry as @agntn/puzzles/collections/<key>. Every puzzle has a page with its record, its transactions, its key material and its live balance. The numbers here come from the library at build time.
- Arweave bounties12 puzzles · 4 open · 1900 AR, 1 ETHTiamat's weave puzzles. Prizes in AR and ETH. Four still open.12 · 41900 AR, 1 ETH
- Bitcoin puzzle transaction256 puzzles · 77 open · 903.015076 BTC256 addresses with keys of 1 to 256 bits. The one everybody scans.256 · 77903.015076 BTC
- Ballet wallets3 puzzles · 2 open · 2.00007358 BTCThree BIP38 encrypted keys printed on physical wallets.3 · 22.00007358 BTC
- Bitaps mnemonic challenge1 puzzles · 1 open · 1.00016404 BTCA 3 of 5 secret sharing scheme with two shares published.1 · 11.00016404 BTC
- Bitimage2 puzzles · 1 open · 0.010019 BTCSeeds hashed out of photographs. One solved, one waits on a passphrase.2 · 10.010019 BTC
- GSMG.io puzzle1 puzzles · 1 open · 1.25364181 BTCA multi phase image puzzle. The prize has been halved twice.1 · 11.25364181 BTC
- Hash collision bounties6 puzzles · 4 open · 0.59365 BTCPeter Todd's P2SH scripts that pay for a collision. SHA-1 fell in 2017.6 · 40.59365 BTC
- RushWallet contest30 puzzles · 1 openThirty brainwallets from 2014. Almost all cracked since.30 · 1-
- WarpWallet challenges6 puzzles · 0 openKeybase's scrypt brainwallet. Four solved, two expired with the keys published.6 · 0-
- Zden's puzzles15 puzzles · 2 open · 0.00933188 BTCFifteen visual puzzles on four chains, most of them solved.15 · 20.00933188 BTC
- YoursA class extending NamedCollection or NumericCollection, registered with a loader so it stays lazy like the built-ins.registerCollection({ key, load })
Six tools, three hosts, one executor each
puzzles mcp serves the tools over stdio, the Pi and OMP extensions render them in the terminal. All three call the same functions, so they answer identically and a fix lands once. Only puzzles_balance reaches out to a block explorer, and it says so in its annotations.
- puzzles_stats, puzzles_collections, puzzles_show, puzzles_list, puzzles_verify, puzzles_balance
- The text carries the whole answer: address, status, prize, key range, explorer link
- Limits live in one facts table and the executors enforce them, so a host that skips schema validation hits the same wall
toolpuzzles_show
MCP · Pi · OMP
input
{
"id": "b1000/1"
}output · content[0].text
b1000/1 solved 0.001 BTC 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
chain: bitcoin address kind: p2pkh
public key: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
private key known: yes
explorer: https://blockstream.info/address/1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
source: https://bitcointalk.org/index.php?topic=5218972
key range: 1..1 (hex)Extend Collection, register a loader
A collection outside the package is the same shape as one inside it: a class extending NamedCollection or NumericCollection with its puzzle list. Register a loader and the registry treats it like a built-in, including the part where nothing loads until someone asks.
- A static key, an author from party(), and puzzles built with the same factories
- registerCollection({ key, load }) keeps yours lazy; registerCollection(instance) is fine too
- get, all, stats and the tools see it on the next call, and the same instance twice is a no-op
import { NamedCollection, bitcoinPuzzle, hex, p2pkh, party, registerCollection } from "@agntn/puzzles"; export const minePuzzleFirst = bitcoinPuzzle({ id: "mine/first", address: p2pkh("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"), sourceUrl: "https://example.com/first", startedAt: "2026-01-01 00:00:00", key: hex("00…01", 1),}); export class MineCollection extends NamedCollection { static readonly key = "mine"; static readonly author = party("you"); static readonly puzzles = [minePuzzleFirst]; constructor() { super(MineCollection.key, MineCollection.author, MineCollection.puzzles); }} // Lazy, like the built-ins: the module loads on the first get("mine/first").registerCollection({ key: "mine", load: () => import("./mine").then((m) => new m.MineCollection()) });Start with one command
Pre-1.0, so pin exact versions. The records are public data about public puzzles. Every key in here was public before it got here. A balance is what the explorer said five minutes ago at most.