Lookups and views
By identifier
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
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
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. 524 of the 558 puzzles are on Bitcoin, and a collection key names its author or its puzzle, not its chain: 4 of the 15 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
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
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
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.