Lookups and views
By identifier
import { get, requirePuzzle } from "@agntn/puzzles";
await get("b1000/71"); // the puzzle, loading b1000 only
await get("gsmg"); // a singleton, the key is the id
await get("b1000/999"); // undefined
await requirePuzzle("b1000/999"); // throws PuzzleNotFoundError
get reads the collection key off the identifier, loads that collection, and asks it. A bad collection segment loads nothing. peter_todd/sha1 works, because the alias resolves before the load.
Inside a collection
import { b1000 } from "@agntn/puzzles/collections/b1000";
import { zden } from "@agntn/puzzles/collections/zden";
b1000.get(71); // NumericCollection takes numbers, or "71", or "b1000/71"
b1000.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
b1000.all(); // every puzzle, in list order
b1000.solved(); // status === "solved"
b1000.unsolved(); // status === "unsolved"
b1000.withPubkey(); // a public key is known
b1000.count(); // 256
b1000.solvedCount(); // 83
b1000.unsolvedCount(); // 77
Pick get when a miss is normal. Pick require when a miss is a bug. 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, selectPuzzles, Status } from "@agntn/puzzles";
await all(); // 332 puzzles in manifest order, then list order
await selectPuzzles({ collection: "b1000", status: Status.Unsolved }); // 77
await selectPuzzles({ status: Status.Solved, withPubkey: true }); // every solved puzzle with a known public key
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.
Statistics
import { collectionSummaries, stats } from "@agntn/puzzles";
await stats();
// {
// total: 332, solved: 130, unsolved: 93, claimed: 11, swept: 96, expired: 2,
// with_pubkey: 236,
// total_prize: { AR: 5550, ETH: 14.1337, DAI: 100, BTC: 1058.06884913, LTC: 230.8255, DCR: 460 },
// unsolved_prize: { AR: 1900, ETH: 1, BTC: 907.88195631 },
// }
await collectionSummaries(); // [{ key, author, total, solved, unsolved }, ...]
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(); // "c8c77264fe07", the first 12 hex characters of SHA-256 over the serialized collections
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. 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, litecoinspace.org for Litecoin, etherscan.io, 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.
Registry
A manifest of ten keys and ten lazy imports. Keys and aliases answer at once. Records load on the first lookup for their collection. How to register yours.
Verification
verifyPuzzle derives the address from the one secret a record exposes and compares. Expected failures are values. What can and can't be checked.