Banking Compliance · Rust Ledger Engineering

Arik Dutta

Tier-1 banking · 12 yrs · KYC/AML and workflow transformation

Arik spent 12 years in tier-1 banking, leading compliance and workflow transformation programs. He built Rust Finance, a full-stack retail banking app on Axum, Tokio and SQLx against Postgres, around the properties that matter in a regulated domain: transfers that cannot race, a ledger that can prove every balance, and a database that rejects invalid state on its own.

The app ships with role-based access, PDF invoices, savings goals, a Tauri v2 shell for desktop and mobile, and a tracing layer that persists server errors as bug reports. Alongside it, he built CSharp-to-Rust-Assistant, an LLM translation pipeline where the Rust compiler, not the model, decides what is done.

12 yrs
Tier-1 banking, compliance and workflow transformation
KYC/AML
Compliance programs led before moving into engineering
6-10
Engineers and analysts led on banking transformation teams
EMBA
Executive MBA, business and engineering in one profile
The challenge

A banking app has to be correct, not just working

Most demo banking apps store a balance column and update it in place. That works until two transfers hit the same account at once, or a failed write leaves money debited but never credited. Arik wanted the app to hold the guarantees a compliance team would ask for: atomic transfers, an auditable trail, and balances that can be checked against their history.

That meant pushing correctness down into Postgres itself, with locks, constraints and triggers, and keeping the Rust layer typed end to end so every failure mode is an explicit variant.

  • •Concurrent transfers must never overdraw an account
  • •Every balance must be explainable from ledger entries
  • •The API must not reveal which accounts exist
The outcome

Rust Finance: a ledger that can prove its balances

Arik shipped Rust Finance behind Docker, Nginx and GitHub Actions CI/CD: row-locked transfers, a double-entry ledger in exact decimals, and a drift check that reconciles cached balances against it.

His CSharp-to-Rust-Assistant adds the other half of the profile: an LLM pipeline gated by cargo check, aimed at teams migrating legacy .NET code to Rust.

  • ✓Transfers serialized by FOR UPDATE locks in one transaction
  • ✓Double-entry ledger with SQL-level drift reconciliation
  • ✓Compiler-verified C# to Rust translation loop
[ Transfers ]

Money moves inside one transaction, under row locks

A transfer is a single Postgres transaction that takes SELECT ... FOR UPDATE locks on the source and destination accounts before reading a balance. Two concurrent transfers out of the same account serialize on the lock instead of both passing the funds check.

Failures are a typed TransferError (InsufficientFunds, AccountNotFound, RecipientNotFound, SameAccount, Db), so the handler maps each one to a precise response. An account owned by someone else returns AccountNotFound, never a permission error, so the API cannot be used to probe which account numbers exist.

[ Ledger ]

Balances are derived from a double-entry ledger, then reconciled

Every transfer writes a transaction row plus a CREDIT and a DEBIT entry in ledger_entries, stored as NUMERIC(19,4) and handled as rust_decimal on the Rust side. The ledger is the source of truth, an account_balances view computes balances from it.

The accounts table still caches a balance for fast reads, so Arik added a check_balance_drift() SQL function that compares the cached value with the ledger-computed one. Drift becomes a query you can run and alert on, the same reconciliation discipline a bank applies at end of day.

[ Invariants ]

The database refuses bad state, not just the handlers

Roles, account types, transaction types and statuses are guarded by CHECK constraints, emails must be lowercase, and a partial unique index enforces uniqueness only across active accounts. A buggy handler or a manual SQL session hits the same wall.

updated_at is set by a trigger rather than in application code, because a query that forgets to touch it would otherwise fail silently. Each domain has #[sqlx::test] integration tests running against real migrations, and ts-rs exports the Rust types to the TanStack frontend so the contract cannot drift.

[ AI tooling ]

An LLM translator that the compiler grades

CSharp-to-Rust-Assistant parses C# with tree-sitter one declaration at a time, lets an LLM translate each unit, and runs cargo check on the output. Compiler errors are fed back to the model until the code compiles, so nothing is trusted on the model's word.

Translating per class, struct, enum or interface keeps each prompt small and each failure local. The repair loop is capped at MAX_REPAIRS so a unit that never converges stops instead of burning tokens, and a built-in mock translator lets the whole pipeline run offline without an API key.

The journey

One clear path through the build

Drag to explore · Select a milestone to read the full story

Max Wells

Mentor perspective

Arik brought something most career changers lack: he knew what a regulator would ask before writing a line. He chose a double-entry ledger over a mutable balance, put invariants in the database instead of trusting handlers, and built a reconciliation check nobody asked for. That instinct for auditability is exactly what fintech and regulated Rust teams hire for.

Max Wells

Rustify mentor

Want results like Arik's?

Max WellsAvailable for calls

Max Wells

Founder · Rustify

Book your Rust career diagnostic

30 minutes, 1:1 with Max. An honest read on where you are and whether Bootcamp or Mentorship fits, before you apply.

Only a few diagnostic slots left this week.

Applications are reviewed carefully.

← Back to case studies

More client journeys

See another path into Rust

Ready to Land a $80k+ Rust Job in the US or Europe?