Fintech rule F-01: record a transaction before any balance change

2 min readApril 29, 2026#fintech#convex#audit#escrow#patterns

Rule F-01 — every balance change starts with a transaction

On Pixel-Mart, every financial mutation follows one rule. Just one, but non-negotiable.

F-01:

Any balance change MUST create a transaction in the same mutation, before the store's patch.

// Mandatory order — never the reverse
await ctx.db.insert("transactions", {
  storeId,
  type: "credit",
  direction: "credit",
  amount: releaseAmount,
  status: "completed",
  balanceBefore: store.balance,
  balanceAfter: store.balance + releaseAmount,
});
 
// AFTER the insert — never before
await ctx.db.patch(storeId, { balance: store.balance + releaseAmount });

Why the order is critical

In Convex, mutations are transactional. If one of the operations fails, everything rolls back.

If you patch the balance first and the transaction insert fails afterward — the balance changed with no trace. Impossible to audit.

If you insert the transaction first and the patch fails — both operations roll back together. Consistent state.

The transaction is the source of truth. The balance is its computed projection.


Why it's worth it in production

PR #250 on Pixel-Mart confirmed it.

A payout webhook bug had potentially left seller balances incorrect. The fix could be automated because every legitimate mutation had respected F-01.

The audit formula:

expected_balance =
  + sum(type=credit, direction=credit, status=completed)
  - sum(type=payout, direction=debit,  status=pending|completed)

This formula is only computable if transactions exist for every balance change. Without F-01 — no audit possible.


The rule is simple. It separates an app that can self-correct from one that silently accumulates accounting debt.