Paisa#
Your money, on your phone. Offline-first personal finance for Android.
Bank SMS? Parsed on-device (~140 banks via a vendored sarim2000/pennywiseai-tracker parser-core).
Download
| GitHub Release | tashifkhan/Paisa · tashifkhan/Paisa |
| F-Droid repo | add https://tashif.codes/fdroid/repo → install Paisa |
| All releases | tashifkhan/Paisa |
Stack#
| UI | Jetpack Compose, Material 3 |
| Shape | :app + :parser-core, ViewModel + Repository |
| Storage | Room (SQLite), stays on the device |
| SMS | bulk scan + live receiver, hash dedup |
| Optional AI | BYOK statement import (OpenAI-compat / Gemini / Anthropic) |
| Language | Kotlin · min SDK 24 · target 35 |
What’s in the box#
- Home — balance, filters, recent txs
- Analytics — charts that actually chart something
- Budgets — monthly caps by category
- Accounts — wallets / banks (reorder, default, merge)
- More — SMS import, AI statements, themes, merchant rules, app lock
Plus onboarding, transaction detail, and a FAB that means “I spent money, log it.”
How SMS lands in the ledger#
- More → SMS import → grant Read/Receive SMS
- Bulk scan walks the inbox through
BankParserFactory, dedups by hash - Live receiver catches new bank alerts as they arrive
- Weird financial-looking SMS that no parser claims go to
unrecognized_sms - Soft-delete keeps the hash so a rescan won’t resurrect deleted rows
Categories on import: learned merchant rules first → keyword map → Others.
Optional: AI statement import#
SMS never leaves the phone. For PDF/CSV/image statements you can paste your own API key (More → AI provider) and import via More → Import statement. Keys live in EncryptedSharedPreferences; only what you send to your endpoint leaves the device.
How to run#
Need JDK 17, Android Studio (or CLI + SDK), and for SMS a physical phone.
git clone <your-repo-url> Paisa
cd Paisa
# once — Studio also writes this when you open the project
echo "sdk.dir=$HOME/Library/Android/sdk" > local.properties # macOS
# echo "sdk.dir=$HOME/Android/Sdk" > local.properties # Linux
./gradlew :app:installDebug
# adb shell am start -n codes.tashif.paisa/.MainActivity
Or open the repo root in Android Studio → run app.
Stuck?
SDK location not found→local.propertieswithsdk.dir=...- Gradle hates your JDK → set Gradle JDK to 17 in Studio
- Signature clash →
adb uninstall codes.tashif.paisa - SMS does nothing → Permissions → SMS, and use a real inbox
./gradlew :app:assembleDebug
./gradlew :parser-core:compileKotlin
./gradlew clean
Release & F-Droid#
Releases are cut from master via GitHub Actions → Release.
That workflow:
Bumps
versionName/versionCodeinapp/build.gradle.ktsBuilds a signed release APK
Tags
vX.Y.Z, publishes a GitHub Release withPaisa-vX.Y.Z.apk+ SHA-256Optionally dispatches the F-Droid publisher on
tashif.codesso the APK lands in:https://tashif.codes/fdroid/repo
Secrets (tashifkhan/Paisa)#
| Secret | Purpose |
|---|---|
KEYSTORE_BASE64 |
base64 of the APK upload keystore (.jks / .p12) |
KEYSTORE_PASSWORD |
keystore password |
KEY_ALIAS |
key alias |
KEY_PASSWORD |
key password |
TASHIF_CODES_WORKFLOW_TOKEN |
fine-grained PAT with Actions write on tashifkhan/tashif.codes |
Create an upload keystore once and back it up — lost keys mean users cannot update:
keytool -genkeypair \
-v \
-keystore paisa-upload.jks \
-storetype JKS \
-alias paisa \
-keyalg RSA \
-keysize 2048 \
-validity 10000
base64 -i paisa-upload.jks | pbcopy # → KEYSTORE_BASE64
Secrets (tashifkhan/tashif.codes)#
Repo-signing key for the F-Droid index (separate from the APK keystore):
FDROID_KEYSTORE_BASE64,FDROID_KEYSTORE_PASS,FDROID_KEY_ALIAS,FDROID_KEY_PASS
See tashifkhan/tashif.codes on the site repo.
Manual F-Droid re-publish#
Actions → Publish to F-Droid repo (or run publish-fdroid.yml on tashif.codes with source_repository=tashifkhan/Paisa).
Layout#
app/src/main/java/codes/tashif/paisa/
├── MainActivity.kt
├── data/ # Room, repo, ViewModel
├── sms/ # scan, live receiver, category map
├── ai/ # BYOK statement import
├── security/ # biometric / credential lock
└── ui/ # theme, components, screens
parser-core/ # bank SMS parsers (AGPL, from PennyWise)
Roadmap#
Done / nearly done#
- SMS import (bulk + live + dedup)
- BYOK AI statement import (Your AI)
- Accounts / categories / budgets CRUD
- Analytics charts + date filters
- Umm better MD3 charts tho
- Search + transaction filters
- App lock / biometrics
- Export (CSV / PDF)
- Recurring transactions UI
- MAIN THING: web PWA without rewriting in JS (iOS support thus also there - but no SMS parsing coz web & also iOS is a bitch doesn’t let you read SMS)
MAIN THING’s plan -#
Good news: we don’t need to throw Kotlin away and rebuild Paisa in TypeScript (coZ been there don’t wanna go back). Kotlin Multiplatform + Compose Multiplatform can compile a lot of this straight to WebAssembly and ship it as a PWA.
1. Port the UI — Compose → Compose Multiplatform#
Same declarative UI, different Gradle target.
- Swap Android-only
androidx.composebits fororg.jetbrains.composewhere needed - Add a KMP
wasmJstarget so the UI becomes a Wasm binary in the browser - Anything Android-shaped (
Context, Intents, AndroidViewModellifecycle) goes behindexpect/actual
2. Port the DB — Room Multiplatform + OPFS#
Offline-first only works if the web still has a real local store.
- Room KMP (2.6+) instead of Android-only Room
- Web: Wasm SQLite driver, not the Android factory
- Persist under the browser’s Origin Private File System (OPFS) so SQLite stays fast and local, closer to “file on disk” than “hope localStorage is fine”
3. PWA shell — installable + offline boot#
Once Wasm runs in a tab, wrap it like any web app:
index.htmlloads the compiled JS + Wasmmanifest.json— name, icons, theme,display: standalone(home-screen install)- Service worker (e.g. Workbox) caches shell + Wasm + fonts so cold start works with airplane mode on
4. Exports without SAF / Intents#
Android file pickers don’t exist in the browser. For backups and CSV/JSON dumps, use the File System Access API so the user still picks where the file lands — same local-first vibe, different plumbing.
(SMS import stays Android-only, obviously. Web gets manual entry + statement import + export.) so does iOS
License#
AGPL-3.0 — see LICENSE and COPYRIGHT.
Copyright (c) 2026 Tashif Ahmad Khan
parser-core/ comes from sarim2000/pennywiseai-tracker (also AGPL). Because that code ships with Paisa, the combined work is AGPL-3.0. If you modify and offer this as a network service, you need to give users the corresponding source.