Exactly-once charging
A charge must land at most once per billing period, even if the worker dies at the worst possible moment. Here is what guarantees it and how both branches were proven.
Two halves of the guarantee
At most one charge per period is a database constraint: UNIQUE (permission_id, period_start), where the period start is derived from the permission rather than chosen, so two workers cannot disagree about it.packages/db/migrations/001_init.sql
At most one on-chain spend per charge is the hard part, because the dangerous window is after eth_sendRawTransactionreturns and before the database learns it happened. The design that closes it:
sign locally -> PERSIST raw tx + hash + nonce -> COMMIT -> broadcastBecause the transaction is signed locally, its hash is known before it is sent. That hash, the nonce, and the raw bytes are committed first. If the process dies afterwards — at any point — recovery has everything it needs to find out what happened, and can re-broadcast the identical bytes rather than signing again. Same nonce, same hash, so at most one can ever be mined.apps/worker/src/charger.js:107
Nonces
Nonces are allocated under a Postgres advisory lock inside the same transaction that persists the signed attempt, so a rolled-back attempt releases its nonce instead of burning it, and no two attempts can share one. On startup the stored counter is reconciled against the chain and the higher value wins. apps/worker/src/nonce.js
Recovery: three branches
On restart, every attempt still in signed or broadcast is resolved: apps/worker/src/recovery.js:14
| Chain says | Decision | Why it is safe |
|---|---|---|
| Receipt exists for the hash | Record the outcome. Never re-send. | It landed. Sending again would be a second spend. |
| No receipt; the nonce is already consumed | Mark the attempt superseded; return the charge to retryable. | Some other transaction took that nonce, so these bytes can never be mined. A fresh attempt on a fresh nonce is the only path. |
| No receipt; the nonce is still free | Re-broadcast the identical raw transaction. | Same hash, same nonce. If it was already in the mempool the node says so; either way only one can land. |
Proven, not argued
Both branches were forced deliberately on Base Sepolia with a fault-injection flag that exits the process hard. RETAINER_CRASH_AFTER_BROADCAST / _BEFORE_BROADCAST
A — killed after the send returned, before the row updated. The attempt sat in the database as signed with no broadcast time, while the transaction was already on the wire. On restart, recovery found the receipt and recorded it. One spend; the audit trail for that charge has no charge.broadcast entry at all, because the process never lived long enough to write one.
- the one transaction (charge #10, nonce 22): 0x8e4ce873249fc57b52485086de99aa4a0b9e341a7165916282153bbf9f68e2df
B — killed after signing, before the send; nonce then taken by a competing transaction. The attempt held nonce 24 durably. A separate transaction was landed on nonce 24. On restart, recovery took the superseded branch — it did not re-broadcast — and the charge succeeded on nonce 25. One spend.
- superseded attempt, never mined:
0x0a078798bcc3d5c5947a4c21687fed79a79332c1521b4b4900d62bfd2e9131a3 - competing transaction that consumed nonce 24: 0x21cd8eb371c6557815715a4b69140c183f5b3981237d76c7724269f01622c32d
- the one spend (charge #11, nonce 25): 0xe0ac2a1eed2bb2a5ea4c0a6ee418d4edb37c2320f488fa6b5eb2026aabb5244d
For each permission, the on-chain count of SpendPermissionUsed events after the drill was exactly one.README — crash recovery