Retainer/DocsBASE SEPOLIA

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:

apps/worker/src/charger.js — the ordering
sign locally  ->  PERSIST raw tx + hash + nonce  ->  COMMIT  ->  broadcast

Because 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 saysDecisionWhy it is safe
Receipt exists for the hashRecord the outcome. Never re-send.It landed. Sending again would be a second spend.
No receipt; the nonce is already consumedMark 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 freeRe-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.

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.

For each permission, the on-chain count of SpendPermissionUsed events after the drill was exactly one.README — crash recovery

What this does not guarantee
It guarantees at most one spend per charge and at most one charge per period. It does not guarantee a charge succeeds — that is what the failure modes are for — and it assumes a single executor key. Running two workers against one executor is serialised by the advisory lock; running two executors is not a supported configuration.