Monetarium — Buyback Campaign & Claim Intake
- Cloudflare Workers
- D1
- Astro 7
- TypeScript
- Resend
- Cloudflare Access
- Vitest
- Claude Code
A node-acquisition campaign for the Monetarium chain, part of the same client engagement as the block explorer and the marketing site. One goal — more independent nodes — with a buyback as the instrument: the project buys back up to half of the miner’s reward for the blocks an address mined inside a defined 8,640-block range, at $0.10 per VAR, paid in USDT.
The campaign is the client’s — his idea, his direction, and every rule the terms publish is a decision he took. What I owned is the work between a decision and a live campaign: the analysis each option was decided on, the participant-facing terms and claim form written to those decisions, the campaign’s pages on the marketing site, and the intake that receives and files claims — two Cloudflare Workers over one D1 database. Sole engineer on it, start to finish.
Live: monetarium.world/promo · terms
Engagement shape
- Client: undisclosed — the same engagement as the block explorer and the public marketing site.
- Who decided what: the campaign is the client’s, and each of its rules is a decision he took — several of them on a recommendation of mine, carrying the arithmetic behind the option. Everything downstream of the decisions was one engineer’s scope, solo: the analysis, the published terms, the pages, the intake Worker and the operator’s register.
- Constraint that shaped everything: the money is real and the settlement is a person’s job, so the software’s only responsibility is that nothing arrives and is then silently lost.
The economics are derived, not estimated
Every chain figure in the campaign was read out of the node source rather than carried over from Decred by assumption — the chain’s 50/50 PoW/PoS subsidy split is hardcoded and diverges from upstream, which is exactly the kind of inherited constant that silently invalidates a financial promise.
- The campaign’s ceiling is a consequence, not a budget. 8,640 blocks × 32 VAR of miner reward × 50% × $0.10 gives $13,824 — the most that can physically be bought at that price. Published as a ceiling rather than a fund, so no participant’s payout can be reduced by anyone else’s, and there is no proportional-reduction rule anywhere in the terms.
- The participant computes their own number. The figure comes off the
explorer’s block-range view — the
Miner Coinbase Rewardcolumn, the same page we read — so nothing is left to our discretion and every claim is checkable from the outside before it is filed. - The rest of the mechanic is a small table of hard numbers: a per-address cap of 27,000 VAR, a $10 minimum per claim, a 7-day transfer window, then one payout batch within 7 days, plus a late track for coins that come back from tickets after the main window closes.
The claim intake
A Worker on monetarium.world/api/claim, backed by D1.
- Three validation layers that stack. A — field validation in the Worker: base58check on the Monetarium mining address, a TRC-20 USDT address, a 64-hex txid, the compact signature’s exact shape. B — transfer facts: one GET at the explorer’s transaction API answers four things at once (the amount paid to the campaign address, whether it paid us at all, the block height, the coin type). C — verification of the signed message.
- B and C flag; they never gate. Every fact column is nullable, and NULL is load-bearing: it means nobody looked, not failed. A dependency being down must never refuse a participant. The intake’s job is to file the claim and record what was known at the time; refusal belongs to a person at settlement.
- A txid pays once — and the index is deliberately non-unique. A UNIQUE constraint would break the repair the terms promise, where a participant fixes a claim by re-filing on the same txid. The collision is a read-time group query instead of a column.
- The window lives in config, and an unset value is visible rather than silent. The intake and the operator’s daily message parse the same three dates through one function, so a typo cannot close the window in one place and leave it open in the other.
- Receipts, and exactly one message a day. Participants get a receipt from
support@; the operator gets one daily message, sent ten minutes after the UTC day it reports has ended. Sending inside the day it counts would report a partial day, and a busy day would read near-zero by construction — inverting the property the counts exist to give. - Rate limiting before the body is read, through the Worker’s own binding rather than a WAF rule — so retiring the campaign retires the limiter with it, instead of leaving an account resource someone has to remember to delete.
- A 422 hands the form back with everything the participant typed. A refusal that precedes the body has nothing to hand back, so it returns empty fields rather than omitting them: the page that comes back is the same page.
Delegation instead of reimplementation
Signature verification was not reimplemented. The chain’s explorer already
deploys /api/verify-message, so the intake posts the triple, reads one field and
holds no opinion of its own; transfer facts come from the same deployment’s
transaction API. Node, explorer and site are used as one system rather than
duplicated across three repositories.
The subtlety worth keeping is in the error taxonomy: we could not evaluate (a timeout, a non-200, an unparseable body) is kept strictly separate from the submitted bytes recover no key. Collapsing the two would throw away the only signal separating our outage from an unverifiable submission — and settlement treats them in opposite directions.
What was cut before any code was written
A simplification pass ran over the design before implementation, with one test: does this scale with traffic, or with the number of ways a claim can vanish quietly? Out went a staging environment and its second D1, a retry cron with exponential backoff, bounce-webhook ingestion and a per-claim internal notice; the operator digest went from hourly to one message a day.
Nothing that protects against a silently lost claim was touched. Each cut is recorded as a rejected alternative inside the section it belongs to rather than deleted, so the option and the reason for dropping it are read together.
The operator’s side
Settlement is done by a person, so the register is a second Worker on its own
subdomain behind Cloudflare Access — and it verifies the Access JWT itself
rather than trusting the gate in front of it: anything that reached the route
around the gate could mutate financial evidence. Unset configuration fails
closed, a register that opens because a deploy forgot two variables being
precisely the failure that check exists to make impossible. The token’s verified
email claim is carried through as the author of every write, so a change to the
register always has a name on it.
Tests
23 test files across the two Workers, run in workerd against a
Miniflare-backed D1 with the real migrations applied — the settlement dry run is a
SQL test, and against a mocked D1 it would only prove the mock. Every refusal path
is enumerated from the source’s own refusal lists, so a path added without a test
surfaces as a missing case rather than as silence.