When I first built Cuan, it was a traditional React SPA talking to a remote REST API. Every time I logged a transaction on the train or checked my budget in an underground coffee shop, I had to stare at a loading spinner. The latency wasn't just annoying—it completely broke the flow of quickly jotting down an expense.
I wanted an app that felt as fast as Apple Notes, as secure as 1Password, and as fluid as Linear. So I threw out the playbook and rebuilt Cuan from the ground up as a local-first, offline-first, zero-knowledge personal finance application.

1. The Browser is the Database
The most radical shift in Cuan is moving the primary database directly into the browser client.
Instead of treating the frontend as a dumb view layer that constantly queries a remote backend, Cuan relies on PGlite—a WebAssembly (WASM) build of PostgreSQL—running entirely inside your browser's Origin Private File System (OPFS).
graph TD
UI[React + Vite PWA] --> Repo[Repository Layer]
Repo --> DB[PGlite in OPFS]
DB --> Sync[Background Sync Queue]
Sync --> Encrypt[XChaCha20-Poly1305 / AES-GCM]
Encrypt -- Encrypted Blobs --> API[Gin Go Backend]
API --> PG[PostgreSQL 18 Cloud]
API --> S3[RustFS Storage]
Why PGlite over IndexedDB?
By running full PostgreSQL inside WebAssembly:
- Complex Aggregations: We can run window functions, date-matched multi-currency calculations, and complex JOINs across accounts, categories, and budgets in sub-20ms execution times.
- SQL Consistency: No custom query abstractions or IndexedDB quirks—every query uses standard SQL.
- Local Source of Truth: The local PGlite instance is the primary database. The cloud backend is merely a passive, zero-knowledge replication target.
2. Interface & Multi-Currency Engine
Cuan is designed for high-density financial management without the clutter.

Key Capabilities:
- Native Multi-Currency: Tracks transactions in original transaction currencies and dynamically computes converted balances and trendlines against your base currency.
- Granular Budget Enforcement: Set daily, weekly, and monthly budget limits per account or expense category with real-time feedback.
- Multi-Account Vaults: Keep cash, savings, investments, and credit lines organized with automatic running-balance calculations.

3. Zero-Knowledge Cryptography
Because Cuan handles highly sensitive financial records, the server should never be able to read your financial data.
sequenceDiagram
participant User as Client App (PGlite)
participant Worker as Encryption Worker
participant Server as Go Backend (Gin)
participant DB as Cloud DB (Postgres 18)
User->>User: Create Vault & 12-Word Recovery Phrase
User->>Worker: Derive Encryption Key (Argon2id + HKDF)
User->>Worker: Log Transaction ($5 Coffee)
Worker->>Worker: Encrypt payload with XChaCha20-Poly1305
Worker->>Server: POST /sync (Encrypted Blob)
Server->>DB: Store Raw Encrypted Blob
Cryptographic Stack:
- Master Key Derivation: When creating a vault, a 12-word BIP39 seed phrase is generated locally. We derive 256-bit symmetric encryption keys using Argon2id and HKDF.
- End-to-End Blob Encryption: Every transaction, balance, and account name is encrypted locally using
XChaCha20-Poly1305(and AES-GCM) before leaving your browser. - Encrypted Attachments: Receipt images are encrypted in the browser prior to being stored in a self-hosted, S3-compatible RustFS bucket.
Even if our cloud servers or database are fully breached, an attacker only sees indecipherable high-entropy ciphertext.
4. Seamless Background Sync Engine
Since all reads and writes execute against the local PGlite database, the user interface updates instantly without network roundtrips.

How Sync Works under the Hood:
- Immediate Local Persistence: Adding a transaction immediately flushes to the local OPFS storage layer.
- Background Queue: A web worker queues the change event and initiates an encrypted background sync when an active network interface is detected.
- Conflict Resolution: Multi-device state is harmonized across up to 3 devices (Free Tier) or 10 devices (Pro Tier) using a Last-Write-Wins (LWW) conflict resolution model.
5. Trade-Offs & Lessons Learned
Building a local-first application in the browser comes with real engineering challenges:
- Browser Storage Eviction: Browsers can occasionally mark non-persistent storage for eviction under heavy disk pressure. Cuan requests explicit
navigator.storage.persist()access upon vault initialization. - Browser Engines: OPFS behavior varies across browser engines. Cuan is optimized and rigorously tested on Chromium-based engines like Brave and Chrome across Desktop and Mobile.
- Schema Migrations: Schema migrations must execute locally inside PGlite before syncing remote state changes.
⚠️ Known Incompatibility: Samsung Internet Browser
Cuan does not support Samsung Internet Browser on mobile. During testing, we discovered a critical number formatting bug when using Samsung Internet:
- A transaction saved as 2,000 on Chrome would appear as 20.00 in Samsung Internet.
- Conversely, entering 2,000 on Samsung Internet would sync as 200,000 on Chrome.
This is caused by Samsung Internet's older Chromium engine base, which handles number locale formatting and decimal precision differently from standard Chromium. The discrepancy occurs deep in how the browser serializes and parses numeric input values — not in Cuan's application logic.
This is a known browser-level issue and will not be patched on Cuan's end. If you use Samsung Internet as your primary browser, please switch to Brave or Chrome for a correct Cuan experience.
Summary & Specs
| Feature | Cuan Architecture |
|---|---|
| Primary DB | PostgreSQL via PGlite (WASM) in Browser OPFS |
| Latency | < 20ms local query execution |
| Security | Zero-Knowledge (Argon2id, HKDF, XChaCha20-Poly1305) |
| Backend | Go (Gin, sqlc, Goose) + PostgreSQL 18 + RustFS S3 |
| Offline Support | 100% Offline-First |
| Tested Engines | Brave, Chrome (Desktop & Mobile) |
| Known Incompatible | Samsung Internet Browser (number formatting bug — old Chromium) |
Cuan isn't just another budget app—it's a completely private, ultra-fast financial engine for users who value data ownership.
👉 Try it out: cuan.yudopr.dev