Work · Personal · Kotlin Multiplatform · 2026
Hisaab: a personal finance app
The budget is filled in from payment SMS, and the data stays on your devices unless you choose to sync it.
- Company
- Personal project
- When
- 2026 – now
- Status
- In progress, early stage
- My part
- Solo. Design, code and tests.
In plain terms
Hisaab reads the payment messages you already get from bKash, Nagad, Rocket and your bank, and turns them into a budget without any typing. Everything is encrypted on your phone, and the AI runs on the phone too, unless you choose a cloud model and agree to send masked data.
How it works
Hisaab is a Kotlin Multiplatform app with a Compose Multiplatform UI, so Android and iOS share one codebase. It captures transactions from the SMS of six Bangladeshi providers (bKash, Nagad, Rocket, City Bank, BRAC Bank and DBBL), including amounts written in Bangla numerals. On Android it can also read payment notifications, once you switch that on and grant access.
You can add spending by voice too. It’s push-to-talk, and the recognizer runs in Bangla using the Bangladeshi Bangla locale (bn-BD). Or you can let it read bank emails over IMAP, including a backfill of the last 12 months that asks the mail server for financial mail only.
iOS doesn’t let apps read SMS, so there’s a Mac companion app. When your iPhone forwards its texts to your Mac, the companion reads them there, encrypts each payment SMS and relays it to your phone, which parses it like any other SMS.
The ledger behind it covers accounts and credit cards, with each card’s limit, statement day and due day, so the app can show what’s outstanding and when the next payment is due. A transfer between your own accounts is saved as a matching pair of entries, and money you lend or borrow is tracked per person. The interface is a dark design system I built for the app, with text contrast checked against WCAG AA and animations that respect the phone’s reduce-motion setting.
There’s a conversational agent too. It can read your ledger and change it: set a budget, recategorise, or split a transaction. Its changes appear on a review card you can edit, and nothing is saved until you tap Apply. Then the whole set is saved together or not at all.
The public repo is behind my local build. The Mac companion and the email capture are in my local build but aren’t in the public repo yet, and the public main branch doesn’t have the voice screen or the Apple on-device model either.
Key decisions
Local first, encrypted by default
The database is encrypted with SQLCipher. Keys come from libsodium (Argon2id for key derivation, XChaCha20-Poly1305 for encryption), you get a BIP39 recovery phrase, and a biometric lock guards the app.
On-device AI first
One provider interface hides where the model runs. On Android it’s Gemma through MediaPipe. On iOS it’s Apple’s Foundation Models through a Swift bridge, which needs no API key. A cloud model is used instead only if you’ve set one up or Apple’s model isn’t available. If you want a stronger model you can bring your own Claude, Gemini or OpenAI key. Personal data is masked before anything leaves the phone, and only after you’ve agreed to it.
The relay only sees encrypted data
The Mac companion needs a server in the middle, so I made that server blind. The Mac and the phone each derive the relay key from your recovery phrase, and the Mac never stores the phrase. The server only ever holds a nonce and ciphertext, each row is readable by its owner only, and the phone deletes each row once it has taken it in.
The same safety rules as at work
The agent can change your money data, so it gets the same treatment as the bank assistant I build at work. It proposes; you approve. Changes are saved all at once or not at all, and nothing goes to a cloud model without consent.
What made it hard
Every provider formats its SMS differently, and some write amounts in Bangla digits, so parsing is per provider and covered by tests.
Getting SMS off an iPhone at all, which is what the Mac companion is for. It keeps a cursor of the last message it sent, and the cursor moves only after an upload succeeds, so a dropped connection can’t skip a message.
Big mailboxes. A full inbox holds far more than payment mail, so the email backfill pages through results and filters on the mail server instead of downloading everything first.
Keeping it working while it changes fast. My local build has 438 tests across parsing, the ledger, the agent and the crypto.