Each owns its own schema, its own invariants and its own failure modes. They talk through generated contracts, and a lint rule fails the build if one imports another's internals. The result is that a change to sweep economics cannot break payout idempotency, because there is no path between them.
Sealed key material, derivation, signing policy, velocity caps and the two-actor path for cold-to-hot movement.
The window is over individual signing amounts, not a bucket that resets on the hour — a tumbling bucket allows nearly double the cap across its boundary. This is the only context permitted to import a cryptographic library, enforced by a lint rule rather than by review.
Watched addresses, watcher cursors, transfers, confirmations, re-org events and unknown transfers.
Idempotence lives in the schema rather than in a memory of what has been processed, so a watcher that crashes and re-scans a range costs time and nothing else. A re-orged deposit becomes a terminal state; the row is never deleted, because reconciliation has to be able to see that it existed.
The payout state machine, idempotency records, assemblies, fee estimates, broadcast reservations and attempts.
The reservation is taken by an insert that either succeeds or violates the key — there is no window between checking and acting, because the check is the act. The signed payload digest is recorded with it, so a retry re-sends that exact payload rather than signing a new one that could also confirm.
Positions per asset and tier, sweep candidates, batches, float thresholds and skip records.
Positions are updated from confirmed on-chain totals rather than from intent, so a broadcast that never confirms does not quietly move the books. Skips are rows, not silence — "nothing happened" is never an unexplained state.
Position snapshots, operator-reported positions, findings and their resolutions.
Three-way rather than two: chain against engine catches detection bugs, engine against your books catches integration bugs, and only having all three catches the case where two of them agree and are both wrong.
Subscriptions, the outbox, deliveries, attempts and dead letters.
Commit-then-send has a window in which the deposit is confirmed and the notification was never sent, and nothing in the system can later notice. A relay loop claims work with FOR UPDATE SKIP LOCKED so several workers share the queue without duplicating or blocking.
The canonical string, timestamp validation, the replay guard and authentication outcomes.
An unknown operator is verified against a placeholder of the same shape rather than returning early, so response timing does not enumerate the operator list. The context is given the ability to check a signature and never the key that checks it — credentials stay in the host process.
Chain configuration, RPC endpoints, block headers, fee observations and the broadcast log.
Fee observations are recorded over time, which is what makes low-fee scheduling and the sweep fee ceiling something other than a guess.
Architectural rules that live in a wiki decay within a quarter. These fail the build.
| Rule | What it stops |
|---|---|
| Context boundary lint | One context importing another's internals. Only generated contracts cross, and a host may import only src/module. |
| Crypto import restriction | Any context except key custody importing node:crypto signing primitives, ethers, viem, bitcoinjs-lib or similar. |
| Key-bearing declaration rule | A variable named for a secret, seed, mnemonic or private key existing outside custody — or existing inside it without the KeyMaterial type. |
| Money safety lint | A float literal or numeric operation on a money-named identifier, anywhere in a context or in the money package. |
| Source layout rule | Logic in a constants file, types in an application file, or anything at all at the source root except a re-export barrel. |
| Migration lint | A destructive statement outside a contract migration. Expand-then-contract, or the build fails. |
| Planted violation suite | The lint rules themselves silently breaking — deliberate violations are committed and the suite asserts each is still caught. |
The host dies between signing and broadcasting. On restart, the payout is SIGNED and a reservation exists with a payload digest. The retry re-sends that payload rather than signing a new one — because two validly signed transactions for the same payout can both confirm, and the chain has no opinion about which one you meant.
A client times out and retries with a fresh idempotency key. There is nothing the engine can do about that — the two requests are, as far as anything can tell, two different payments. This is why the key is a required argument in the SDK rather than an option, and why the documentation keeps saying it.
Every guarantee above holds under concurrent writers, and the tests that prove it are the ones that found the real bugs.
// 100 concurrent submissions of one idempotency key. // Caught a real bug: ON CONFLICT (payout_id) passed serially and failed here, // because the table carries two unique constraints and the conflict arrived // on the other one — leaving the transaction aborted mid-request. await Promise.all(Array.from({ length: 100 }, () => client.payouts.submit(sameRequest, 'withdrawal-0001'))); assert.equal(await countPayouts(), 1); // The process is killed between signing and broadcasting, then restarted. await killAfterSigning(); await restartAndDrain(); assert.equal(await countBroadcasts(), 1); // Two instances race the same signing cap from opposite ends. // Without the advisory lock this passes locally and fails in production. await Promise.all([instanceA.signUpToCap(), instanceB.signUpToCap()]); assert.ok(await totalSigned() <= cap);
Every claim on this page is a constraint you can find in a migration and a test you can run.