Docs menu

SNAG docs, for developers

Keeper

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.

Status

What the route does, in order

POST { "txHash": "0x..." } to api/mint-auth.js. It refuses at the first failure, in this order, each with its own status code:

StepStatusMessage
Method and JSON shape405 / 400POST only / Send a JSON object
txHash shape400txHash must be 0x and 64 hex characters
Config present503minting not connected
Per-IP budget503 / 429busy, try again shortly / too many authorisations from this address, wait a few minutes
Receipt reachable502chain not reachable
Transaction exists404transaction not found
Transaction succeeded400that transaction failed
Two confirmations409buy is too new, try again in a moment
Collection readable, not paused, right chain502 / 503collection not readable / minting is paused / wrong chain
A qualifying pool-to-buyer transfer400buy under the minimum / no buy from the pool in that transaction
Not already used409that buy already has its piece
Domain and digest match the contract502collection domain mismatch
Success200buyer, buyRef, amountIn, deadline, signature, collection

Nothing is cleaned up on the caller's behalf. A malformed field is refused, never guessed at.

Rate limits

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.

The signer key is the trust root

Limit

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.

Running the keeper

node scripts/snag-keeper.mjs [--once] [--from-block N] [--dry-run]
FlagWhat it does
--onceRuns a single pass and exits, instead of looping.
--from-block NStarts scanning from block N instead of the saved cursor.
--dry-runLogs 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.

Failure modes and recovery