All work

Fin-trak

A local-first ledger that reads Libyan bank statements on the device.

Role
Solo
Status
Work in progress
When
2026
Stack
  • Vue 3
  • TypeScript
  • Pinia
  • SQLite-WASM
  • XChaCha20-Poly1305
  • pdf.js
  • React Native
  • NestJS
  • PostgreSQL
Links
fintrak.ly

Libyan banks expose no public API, so Fin-trak parses statements from 12 of them on the device and builds a double-entry ledger. Sync is optional and encrypted, so the server never sees a description, an account number or a counterparty.

Context

Personal finance apps assume a bank feed. Libyan banks offer a PDF statement and nothing else, and the people who would use the app are rightly unwilling to upload their statements to someone’s server. The product only works if the parsing and the ledger both happen on the device.

Decisions

Everything financial stays local unless it leaves encrypted. The ledger is SQLite compiled to WebAssembly, persisted in the browser’s origin private file system. Sync across web, iOS and Android is optional and encrypted on the device with XChaCha20-Poly1305 before upload, so the server holds ciphertext and never sees a description, an account number or a counterparty.

Double entry, because it makes imports self-checking. Every account is an asset, liability, income, expense or equity account and every event is a balanced transaction, so savings, loans, debts and spending categories are one primitive. An imported statement is reconciled against its own running balance column, which means a misread column cannot pass silently.

Money as integer minor units, with the Libyan dinar at a thousand per unit. Floating point has no place in a ledger.

The parser, the ledger and the money math are pure packages that touch no host environment. Each one typechecks without the DOM library, so the rule is enforced rather than remembered. The web app and the command-line parser fill two seams, the database and the PDF engine, and a React Native app reuses everything else unchanged.

Consequences

Adding a bank is an adapter file with column matchers, checked against a real statement from the command line without opening the browser. The mobile app inherits the entire ledger for free. The price is that every framework convenience that reaches for the window object is off limits inside the packages.