---
title: "Getting Started"
description: "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."
canonical_url: "https://puzzles.agntn.dev/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.

<warning title="Pre-1.0">

The API, the CLI flags and the data model can still move. Pin exact versions if you build on it now.

</warning>

<caution title="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.

</caution>

## 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 `puzzle()`. 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

```bash
pnpm add @agntn/puzzles
```

Node.js 26 or newer for the CLI. The library is neutral ESM on the Fetch API, so browsers and edge workers run it too. The [playground](https://puzzles.agntn.dev/playground) is it, running in your tab.

## First call

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

collectionKeys(); // ["80-bit", "bits", ...] straight from the manifest, nothing loaded
hasCollection("peter_todd"); // true, a historical alias for hash-collision

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

puzzle?.address().value; // "1PWo3JeB9jrGwfHDNpdGK54CRas7fsVzXU"
puzzle?.status(); // "unsolved"
puzzle?.prize(); // 7.10195986
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 <dataset-count of="singletons">



</dataset-count>

 singletons, such as `gsmg`, `genesis` and `80-bit`, are just the key.

Importing the package evaluates no puzzle records. The registry is a manifest of <dataset-count of="collections">



</dataset-count>

 keys. The aggregate views are asynchronous because they load on first use:

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

(await all()).length; // every puzzle, every collection loaded once
(await stats()).unsolved; // how many are still waiting for a key
```

## One collection

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

```ts
import { bits } from "@agntn/puzzles/collections/bits";

bits.count(); // every puzzle in bits
bits.require(90).address().value; // NumericCollection takes the number
bits.unsolved().length; // the addresses nobody has cracked
bits.author.name; // "saatoshi_rising"
```

## What is on a puzzle

<table>
<thead>
  <tr>
    <th>
      Method
    </th>
    
    <th>
      Answers
    </th>
  </tr>
</thead>

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

## 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](https://puzzles.agntn.dev/guide/balances) page.

## Next

- [Puzzle records](https://puzzles.agntn.dev/guide/records): the `PuzzleSpec` literal, the builders, and why nothing is null.
- [Registry](https://puzzles.agntn.dev/guide/registry): the manifest, lazy loading, aliases, `registerCollection`.
- [Lookups and views](https://puzzles.agntn.dev/guide/lookups): `get`, `selectPuzzles`, `stats`, `dataset`, `dataVersion`.
- [Verification](https://puzzles.agntn.dev/guide/verification): a published key against its address.
- [Balances](https://puzzles.agntn.dev/guide/balances): the one network call.
- [CLI](https://puzzles.agntn.dev/guide/cli): the `puzzles` command.
- [Agents](https://puzzles.agntn.dev/guide/agents): fifteen tools over MCP, Pi and OMP.
- [Custom collections](https://puzzles.agntn.dev/guide/custom): your puzzles behind the same registry.
- [Playground](https://puzzles.agntn.dev/playground): the library, in the page.


## Sitemap

See the full [sitemap](https://puzzles.agntn.dev/sitemap.md) for all pages.
