A privacy-first personal spending tracker that connects to your European bank accounts via Enable Banking.
Connect your bank accounts and sync transactions whenever you like. Categorize spending, spot trends, and export encrypted backups — all without your data ever hitting a server you don't control.
- Transactions and accounts are stored in IndexedDB, locally in your browser
- Your
.pemsigning key is imported as a non-extractableCryptoKey— the raw bytes are unrecoverable by JS after import and never leave the device - Backup files are encrypted (AES-GCM) with a key derived from your passphrase (PBKDF2); the passphrase never leaves the device
- Your transaction data is available offline after the first sync
Don't have an Enable Banking account, or just want to try it out first? There's a built-in demo mode that seeds realistic synthetic data with no bank connection needed. You can also bring in real transactions from a CSV export from your bank, or import a full account history from Spiir (including Spiir's own categories, mapped to Lommin's taxonomy).
Lommin is a self-hosted app. You deploy both the frontend (a static SPA) and a CORS proxy (f.ex. a Cloudflare Worker). No shared infrastructure.
- Node.js 20+
- A free Enable Banking account with a
.pemsigning key (for transaction sync) - A proxy that redirects traffic from the frontend to Enable Banking. F.ex. a Cloudflare worker proxy (free tier available).
- Any static hosting provider for the frontend (Netlify, Vercel, Cloudflare Pages, etc.)
- (Optional) A Google Cloud project with OAuth credentials if you want Google Drive backup
The proxy is a single-file Cloudflare Worker that relays browser requests to the Enable Banking API. See proxy/README.md for step-by-step instructions.
cd proxy
wrangler kv namespace create RATE_LIMIT_KV # paste the id into wrangler.toml
# set ALLOWED_ORIGINS in wrangler.toml to your frontend URL
wrangler deployNote the Worker URL (e.g. https://proxy.your-account.workers.dev).
cd frontend
cp .env.example .env
# edit .env:
# VITE_PROXY_URL=https://proxy.your-account.workers.dev
# VITE_GOOGLE_CLIENT_ID=... (optional, for Drive backup)
npm install
npm run build
# deploy frontend/dist/ to your static hostNetlify — connect the repo in the Netlify dashboard or:
netlify deploy --dir frontend/dist --prodThe included netlify.toml configures the build command and publish directory. Set VITE_PROXY_URL (and optionally VITE_GOOGLE_CLIENT_ID) as environment variables in the Netlify dashboard.
After deploying the frontend, add your SPA URL to ALLOWED_ORIGINS in proxy/wrangler.toml and redeploy the proxy.
Register an application in the Enable Banking dashboard. The app shows you the correct redirect, privacy, and terms URLs to use during onboarding — they match your deployment's origin automatically.
To enable encrypted Google Drive backup:
- Create a Google Cloud project and enable the Google Drive API
- Create an OAuth 2.0 client ID (Web application type)
- Add
https://your-app.example.com/oauth/googleas an authorized redirect URI - Set
VITE_GOOGLE_CLIENT_IDin your frontend.envand rebuild
The only server-side piece is a stateless CORS proxy that relays requests between your browser and the Enable Banking API. It is a single TypeScript file — short enough to read and verify yourself.
What it can and cannot see:
| Status | Detail | |
|---|---|---|
Your .pem signing key |
Safe | Never sent to the proxy. JWTs are signed locally with a non-extractable key. |
| Your access token (JWT) | In transit | Passes through in the Authorization header. Short-lived (5 min); the proxy can't mint new ones. |
| Your transaction data | In transit | Passes through in responses while being relayed. |
| Anything stored | Safe | No bodies, tokens, or raw IPs are persisted. Only an opaque SHA-256(ip) counter for rate limiting, with a ~2-minute TTL. |
The real guarantee is the code itself — read it, since you're running your own.
frontend/ React + Vite SPA
proxy/ Cloudflare Worker — stateless CORS relay to api.enablebanking.com
Bug reports, feature ideas, and pull requests are all welcome. If you want to work on something non-trivial, open an issue first so we can talk it through before you put in the effort.
- Found a bug? Open an issue
- Have an idea? Start a discussion
- Want to contribute code? Fork, branch, and open a PR — the smaller and more focused the better
See frontend/README.md and proxy/README.md for local dev setup.