CIPHR docs, for developers
The signing route (api/mint-auth.js) checks a buy and signs an authorisation for it, using the shared library api/_snag-mint-lib.js. CIPHR does not run a keeper. Its collection only accepts a mint from the buyer named in the authorisation, because the buyer's own wallet has to attach the seal, so the buyer's own wallet is the one that calls the route, then 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.
On the base platform a keeper watches the pool, asks the route for an authorisation on the buyer's behalf, and submits the mint itself, so the buyer never pays gas. CIPHR requires the buyer wallet to submit mintForBuy with an enclave-signed seal. The buyer pays network gas. A keeper cannot substitute for the buyer or bypass the enrolled enclave key.
Both are calls the holder makes directly on the collection, whenever they choose, again from their own wallet and at their own gas cost.
reseal(id, commitment, ciphertext). Replaces the sealed visor with a freshly generated one. Needed after a transfer, since an unrevealed seal does not carry over to a new holder; also usable any time before a reveal.The buy signing route proves eligibility. Sealing, resealing and revealing also require the Phala CVM service, holder authentication and an enrolled enclave signature. A standalone wallet signature cannot replace the enclave proof. Production deployment and attestation remain unverified.