SNAG docs, for developers
The signing route (api/mint-auth.js) and the keeper (scripts/snag-keeper.mjs) share one library, api/_snag-mint-lib.js, so they can never drift apart. The route answers a wallet that asks for its own piece over HTTP; the keeper does the same authorisation with no HTTP at all, for every buy it finds, and submits the mint itself.
POST { "txHash": "0x..." } to api/mint-auth.js. It refuses at the first failure, in this order, each with its own status code:
| Step | Status | Message |
|---|---|---|
| Method and JSON shape | 405 / 400 | POST only / Send a JSON object |
| txHash shape | 400 | txHash must be 0x and 64 hex characters |
| Config present | 503 | minting not connected |
| Per-IP budget | 503 / 429 | busy, try again shortly / too many authorisations from this address, wait a few minutes |
| Receipt reachable | 502 | chain not reachable |
| Transaction exists | 404 | transaction not found |
| Transaction succeeded | 400 | that transaction failed |
| Two confirmations | 409 | buy is too new, try again in a moment |
| Collection readable, not paused, right chain | 502 / 503 | collection not readable / minting is paused / wrong chain |
| A qualifying pool-to-buyer transfer | 400 | buy under the minimum / no buy from the pool in that transaction |
| Not already used | 409 | that buy already has its piece |
| Domain and digest match the contract | 502 | collection domain mismatch |
| Success | 200 | buyer, buyRef, amountIn, deadline, signature, collection |
Nothing is cleaned up on the caller's behalf. A malformed field is refused, never guessed at.
Six authorisations per client per ten minutes, counted by the first of x-vercel-forwarded-for, x-forwarded-for or x-real-ip. The store is bounded: expired windows are dropped first, and when every slot is held by a live caller a new caller is told the service is busy rather than evicting someone else's window.
Anyone holding SIGNER_KEY can authorise a mint for any address. That is the honest shape of this design: the chain proves the buy, but a server attests to it. The contract can rotate the signer (setSigner) and can be paused, and the owner is a two-step transfer that cannot be renounced, so a leaked key is recoverable. It is still a key, and it should live nowhere but the Vercel environment and the keeper's own shell.
node scripts/snag-keeper.mjs [--once] [--from-block N] [--dry-run]| Flag | What it does |
|---|---|
--once | Runs a single pass and exits, instead of looping. |
--from-block N | Starts scanning from block N instead of the saved cursor. |
--dry-run | Logs what it would mint without submitting anything. |
It needs SIGNER_KEY (to build the same authorisation the route would) and KEEPER_KEY (to pay gas), both from the environment only. Its place in the chain is saved to .keeper-cursor.json beside this documentation, holding a block number and nothing else; that file belongs in .gitignore next to the other local state.
.keeper-cursor.json, or from --from-block if given, so nothing needs to be replayed by hand.