---
title: "Lookups and views"
description: "get and requirePuzzle by identifier. selectPuzzles by collection and status. stats and the dataset snapshot with its deterministic data version."
canonical_url: "https://puzzles.agntn.dev/guide/lookups"
---
# Lookups and views

> get and requirePuzzle by identifier. selectPuzzles by collection and status. stats and the dataset snapshot with its deterministic data version.

## By identifier

```ts
import { get, requirePuzzle } from "@agntn/puzzles";

await get("bits/71"); // the puzzle, loading bits only
await get("gsmg"); // a singleton, the key is the id
await get("bits/999"); // undefined
await requirePuzzle("bits/999"); // throws PuzzleNotFoundError
```

`get` reads the collection key off the identifier, loads that collection, and asks it. A bad collection segment loads nothing, and a non-string value from untyped JavaScript is a miss instead of a `TypeError`. `peter_todd/sha1` works, because the alias resolves before the load.

## Inside a collection

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

bits.get(71); // NumericCollection takes numbers, or "71", or "bits/71"
bits.get("0x47"); // undefined, the spelling has to be the identifier's own
bits.require(71); // throws when missing
zden.get("decred-janus"); // NamedCollection takes the name, or the full id
zden.requireId("zden/decred-janus"); // the full id on any collection

bits.all(); // every puzzle, in list order
bits.solved(); // status === "solved"
bits.unsolved(); // status === "unsolved"
bits.withPubkey(); // a public key is known
bits.count(); // how many puzzles
bits.solvedCount(); // how many with status "solved"
bits.unsolvedCount(); // how many with status "unsolved"
```

Pick `get` when a miss is normal. Pick `require` when a miss is a bug. A query resolves only in the spelling its identifier uses: `" 71 "`, `"71.0"` and `"7e1"` are misses on `bits`, whatever `Number()` makes of them, and a `NamedCollection` handed anything but a string answers `undefined` rather than throwing. `all()` is a frozen array built once. `solved()`, `unsolved()` and `withPubkey()` filter it on every call, so keep the result if you loop.

## Across collections

```ts
import { all, Chain, selectPuzzles, Status } from "@agntn/puzzles";

await all(); // every puzzle, in manifest order, then list order
await selectPuzzles({ collection: "bits", status: Status.Unsolved }); // bits alone, loading nothing else
await selectPuzzles({ status: Status.Solved, withPubkey: true }); // every solved puzzle with a known public key
await selectPuzzles({ status: Status.Unsolved, withPubkey: false }); // unsolved, and the key never showed up
await selectPuzzles({ chain: Chain.Ethereum }); // every Ethereum record, wherever it lives
await selectPuzzles({ address: "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH" }); // bits/1
await selectPuzzles({ technique: "atbash" }); // every puzzle with an Atbash step, in any collection
```

`selectPuzzles` with a collection loads that collection alone. Without one it loads everything.

`withPubkey: true` keeps the puzzles whose public key is known. That's what a scanner wants: a public key turns a hash search into a discrete log search.

`withPubkey: false` is the other half, the puzzles still keeping their key to themselves. Leave it out and you get both. Who wants the shy ones? Anyone waiting for a spend to give a key away, because `puzzles watch --no-with-pubkey` asks the chain about exactly those.

`address` answers the question an identifier cannot: you saw an address somewhere and want to know whether the dataset already knows it. It compares on each record's own chain, through `sameAddress`. So an EIP-55 checksummed Ethereum address and an uppercased bech32 one both match. Base58 does not fold, because a recased base58 string is a different address.

`chain` is the filter an identifier cannot stand in for. <dataset-count chain="bitcoin">



</dataset-count>

 of the <dataset-count>



</dataset-count>

 puzzles are on Bitcoin, and a collection key names its author or its puzzle, not its chain: <dataset-count chain="ethereum" collection="weave">



</dataset-count>

 of the <dataset-count chain="ethereum">



</dataset-count>

 Ethereum records live in the collection called `arweave`.

`technique` finds puzzles built the same way, wherever they sit. It reads the collection's techniques, the record's and every stage's. So `technique: "beaufort"` lands on `gsmg` for one phase out of seven. TypeScript takes only names from `Technique` here. The CLI and `puzzles_list` throw `InvalidArgumentError` on any other. Why not just answer with nothing? Because an empty list would look like nobody ever used it.

## Statistics

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

await stats();
// {
//   total, solved, unsolved, claimed, swept, expired, with_pubkey,
//   total_prize: { AR, ETH, DAI, BTC, LTC, DCR },
//   unsolved_prize: { AR, ETH, BTC },
//   techniques: { atbash, "masked-key-range", ... },
// }

await collectionSummaries(); // [{ key, author, total, claimed, expired, solved, swept, unsolved }, ...]
```

`techniques` counts puzzles, not tags. A quizchain block with MD5 and Atbash adds one to each. Techniques nobody used are left out. `authors()` and `getAuthor(key)` carry the same counts per author, which is the closest the dataset gets to a habit.

Prizes are summed per currency in whole units, the way the records write them, rounded to eight places. Nothing here is a balance. A `swept` puzzle's prize is what sat on the address when it was swept. An `expired` one's went back to the author.

## The dataset snapshot

```ts
import { dataset, datasetCollections, dataVersion } from "@agntn/puzzles";

await dataVersion(); // the first 12 hex characters of SHA-256 over the serialized collections, new with every record change
await datasetCollections(); // [{ name, author, puzzles: [PuzzleData, ...] }, ...]
await dataset(); // { version, data_version, collections }
```

`dataVersion()` changes when a record changes and stays the same across runtimes. Cache a snapshot, compare it later, no build step. No timestamp in it, no environment data. The snapshot comes back frozen through, envelope, rows and records included, so decorating a serialized record is a `TypeError` in strict mode rather than a rewrite of the export for every other reader. `puzzles export` prints `dataset()`. That's what the JSON consumers of the old dataset read now.

## Explorer links

```ts
const puzzle = await requirePuzzle("zden/decred-janus");

puzzle.explorerUrl(); // "https://dcrdata.decred.org/address/DsRaAja82UvgnqYaBHYFuyCKURFX2rCyEJ8"
puzzle.claimExplorerUrl(); // the claim transaction, when there's one
```

Explorer bases come from `@agntn/chains`: blockstream.info for Bitcoin, blockchair.com for Bitcoin Cash and Dogecoin, explorer.e.cash for eCash, litecoinspace.org for Litecoin, etherscan.io, basescan.org, dcrdata.decred.org and viewblock.io for Arweave. Need a link for something that isn't a puzzle? `addressExplorerUrl(chain, address)` and `transactionExplorerUrl(chain, txid)` are exported too.


## Sitemap

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