Guide

Getting Started

Install the package. Load one puzzle. Read its address and its key range. Every puzzle is a record and every answer comes from a method on it.
Pre-1.0. The API, the CLI flags and the data model can still move. Pin exact versions if you build on it now.
Public data about public puzzles. Every key in here was already public, usually posted by the solver. A record tells you what's known. It doesn't tell you whether the address still holds anything. That's a balance call, the only one that leaves the process.

Why this exists

The Bitcoin puzzle transaction. The WarpWallet challenges. Peter Todd's hash collision bounties. Zden's image puzzles. Each one has a thread somewhere with the addresses, the prizes, who solved what and when. And every scanner, tracker and bot re-types that data from the thread. Slightly differently each time.

So it lives here once, as code. A puzzle is a TypeScript record built by a factory for its chain. A collection is a list of them. The type checker reads the data before a test does. The library, the CLI, the MCP server and the Pi and OMP extensions read one registry. Fix a record once and it lands everywhere.

Install

pnpm add @agntn/puzzles

Node.js 24 or newer for the CLI. The library is neutral ESM on the Fetch API, so browsers and edge workers run it too. The playground is it, running in your tab.

First call

first.ts
import { get, collectionKeys, hasCollection } from "@agntn/puzzles";

collectionKeys(); // ["arweave", "b1000", ...] straight from the manifest, nothing loaded
hasCollection("peter_todd"); // true, a historical alias for hash_collision

const puzzle = await get("b1000/71"); // imports the b1000 module and nothing else

puzzle?.address().value; // "1PWo3JeB9jrGwfHDNpdGK54CRas7fsVzXU"
puzzle?.status(); // "unsolved"
puzzle?.prize(); // 7.100226
puzzle?.prizeCurrency(); // "BTC"
puzzle?.keyRange(); // [2n ** 70n, 2n ** 71n - 1n]
puzzle?.hasPubkey(); // false, the address has never spent

get(id) loads the one collection the identifier names. Anything it doesn't know comes back as undefined. Want an exception instead? requirePuzzle(id) throws PuzzleNotFoundError. Identifiers are collection/name. The two singletons, gsmg and bitaps, are just the key.

Importing the package evaluates no puzzle records. The registry is a manifest of ten keys. The aggregate views are asynchronous because they load on first use:

import { all, stats } from "@agntn/puzzles";

(await all()).length; // 332, every collection loaded once
(await stats()).unsolved; // 93

One collection

A collection is its own entry with its class and its canonical instance. Once you have it, everything on it is synchronous:

import { b1000 } from "@agntn/puzzles/collections/b1000";

b1000.count(); // 256
b1000.require(90).address().value; // NumericCollection takes the number
b1000.unsolved().length; // 77
b1000.author.name; // "saatoshi_rising"

What is on a puzzle

MethodAnswers
id(), collection(), name()b1000/71, b1000, 71
chain(), address()The chain key and { value, kind, hash160?, redeem_script? }
status()unsolved, solved, claimed, swept or expired. Written down, never derived
prize(), currency(), prizeCurrency()The amount, the currency when it isn't the native token, and the one to print
pubkey(), hasPubkey()The public key once the address exposed one
key(), keyData(), hasPrivateKey()The key builder, its serialized record, and whether any private key representation is known
keyRange()[2^(bits-1), 2^bits - 1] when the record declares a search width
transactions(), fundingTransaction(), claimTransaction()What happened on chain, in order
startedAt(), solvedAt(), solveTime(), formattedSolveTime()When, and how long it stood: 11y 6mo 16d 14h 13m
solver(), assets(), sourceUrl()Who, what files, and where the puzzle was announced
explorerUrl(), claimExplorerUrl(), assetUrl()Links, built from @agntn/chains explorer bases
toJSON()The serialized record with every absent field left out
balance(options)The live balance through @agntn/explorers, the one method that touches a network

Errors

Everything thrown here descends from PuzzlesError. Catch one type, read fields, skip the message parsing. PuzzleNotFoundError is requirePuzzle on an id nobody has. UnknownCollectionError is requireCollection on a key outside the manifest. InvalidArgumentError is a tool argument outside its limits. Balances have their own family under BalanceError, on the balances page.

Next

@agntn/puzzles·MIT license· Public data about public puzzles. Balances come from the chains' explorers through the worker, cached for five minutes. Every key here was public before it landed in a record.