Self-hosted, decentralized personal finance & accounting. You are the server.
Quick start · Download · Features · Screenshots · How it works · Docs · Parity · Roadmap
MIT OR Apache-2.0 · Rust 1.85+ · Tauri 2 · SQLite · offline-first
The shipped desktop app — Dashboard with net balance, monthly spend, budget remaining, locally-computed nudges, and recent activity. All screenshots show demo data (full tour).
| You are the server No SaaS backend, no aggregator in the middle. Everything runs on your machine or a box you control. |
Write-only secrets Bank & mailbox credentials live in your OS keychain. Set, rotate, revoke, use — never view. |
Share smarts, not data Community knowledge travels as signed packs — over a folder, a USB stick, a git remote or HTTPS, with no registry in the middle. Benchmark statistics are designed on the same principle; reading one is local, and contributing is not built. Never your transactions. |
SlipScan is aiming at what Vault22 / 22seven does for personal finance and what Xero does for small-business accounting — bank transactions, receipts, budgets, categorised spending, double-entry ledger, reconciliation, tax — with one fundamental difference: there is no central server. A Rust core over a plain SQLite file, wrapped in a Tauri desktop app. It is a standalone product: no account, no cloud, no telemetry, and it never depends on any hosted service.
How much of that is actually built? PARITY.md scores all 24 capabilities on both axes — Built / Partial / Not built, each with a file-level citation. The short version: the personal-finance loop is real (accounts, categorisation that learns, budgets, household attribution); the accounting side now has invoicing, quotes and a fixed-asset register — there is still no distinct bill/accounts-payable entity, no aged payables, and no payroll. Nothing in that document is rounded up.
It is also global by default: nothing country-specific is hardcoded — chart-of-accounts seeds, tax rates and return labels, bank CSV presets, and merchant packs all ship as region profiles (data you pick, contract). South Africa is the first region profile; a generic profile covers any country from day one.
Your data lives on your machine, your bank and mailbox credentials stay in your OS keychain, and the only thing the community shares is knowledge — signed classification packs today, with differentially-private benchmark statistics designed but not yet implemented — never data.
| 💰 Personal finance (Vault22 / 22seven class) | 📒 Accounting (Xero class) |
|---|---|
|
|
Infrastructure you can trust
- Get paid by reference (Payments) — watch an EFT reference code, and when the matching payment lands in your books (from any source) SlipScan fires an HMAC-signed webhook to endpoints you register. Signing secrets are vault-held and shown exactly once; payloads carry the reference and amount, never account numbers; deliveries retry until your box has network. Inbox in, webhook out, no central infrastructure (guide)
- Movable data folder, your own backup — your books and documents live in one folder you can see, relocate from Settings or
slipscan data move(verified copy + atomic switch), and back up by syncing it with your own cloud (iCloud / Dropbox / Syncthing / NAS). SlipScan ships no backup service, and the keychain key never travels with the folder (data & backup) - Ingestion from your own mailbox — always your accounts, never our infrastructure; generic IMAP, Gmail, and Microsoft Graph all sync one-shot from
slipscan mail-synctoday (--loginruns the provider's own OAuth grant into the vault); the push loop (IMAP IDLE) is built but not yet wired to a surface, and Graph push is unsupported by design outside self-host mode - Bank alert emails become transactions — "your card was used for R 184.50 at…" is parsed into a statement line and imported through the same path a CSV statement uses, so dedupe, your own categorisation corrections, and the Payments detection hook all apply. The formats are data, not code: they ship as signed
mailrulespacks, so the community maintains each bank's rules without a bank-specific line in the product — SlipScan ships none of its own. Deliberately conservative: a rule that matches but cannot read a field cleanly declines and reports why, because a wrongly-parsed transaction is worse than an unparsed one. CLI today (slipscan mail-sync --alerts --account <account>); no desktop UI yet (guide) - Packs travel over anything, because the signature is what you trust — a pack is the same signed bytes whether it came off a USB stick, a folder your household already syncs, a git remote, or an HTTPS URL, and all four end at the same ed25519 check. There is no registry and no default source: the source list starts empty, so a fresh install makes zero pack network calls until you name a source yourself, and a publisher's fingerprint is shown for you to compare before their first pack is accepted (format, signing and distribution)
- Open-source, local bank-scraper framework — the trait, the statement pipeline and the region CSV preset catalog ship, and downloaded statements import with
slipscan import --preset. No adapter talks to a bank yet: the only non-test implementation reads files, so no credential has ever been handed to one (framework) - Write-only credential vault rooted in the OS keychain — secrets can be set, rotated, revoked, and used, never viewed (threat model)
- Opt-in multi-currency FX via OpenRate — self-hosted, provenance-graded rates. Decimal-only rate math (floats never touch money), a local rate cache, and every conversion recording the exact rate, quality grade, and as-of age it used — surfaced on the CLI (
slipscan fx), the HTTP server, and the desktop Settings screen. No report converts anything yet — reports return one row per currency, and the Reports screen tells you outright that nothing there is converted (Phase 4.7). No endpoint configured means zero FX network calls (contract) - Headless self-host server mode for an always-on box (guide)
- Device sync will use one shared, specified merge engine — not a private
CRDT invented here.
slipscan-syncexpresses SlipScan's replicated state in the DMTAP Sync algebra (substrate/SYNC.md③): editable rows as last-writer-wins registers, the posted ledger as an add-only set, so concurrent edits converge the way double-entry requires and a journal is never clobbered. Money crosses asi64minor units — an exact integer, because the algebra forbids floats and so does SlipScan. As a native Rust product it takes the shared engine as a plain crate dependency. Status: the merge mapping, per-device identity, and a signed operation log all exist; the transport does not, so nothing syncs between devices today (roadmap) - Every change is recorded as an operation you can verify on its own. A
SQLite trigger on each replicated table records the write — so a cascading
delete or a future importer is caught too, not only the code paths somebody
remembered to instrument — and
slipscan sync sealsigns each one with this device's key. Lift one operation out of the log and it still verifies, with no connection and no server involved: that is what makes a replicated change trustworthy on its own merits rather than because of how it travelled. The log is append-only and the database refuses to edit it. It records; it sends nothing — there is no transport, and a fresh install makes no outbound call (nodes) - Devices know each other without accounts — a device's identity is an
ed25519 keypair it generates itself, and the public half is the id: no
email, no password, no username, no login, no server that decides who you
are. Pairing pins each peer's key at the one moment you redeem an invite,
compares fingerprints as nine readable words rather than hex, and treats a
changed key as a refusal instead of a silent re-pin. Pairing is a
slipscan devicejob today — there is no Devices screen. Over your own HTTP server only the public half is served (identity, peers, revoking a lost device); key material and claim tokens deliberately are not (nodes)
Note
Status: 0.1.0, the first release, under active development. The Rust core, CLI, extraction, ingestion, packs, and server crates are implemented. This release includes: bank-alert emails → transactions, pack distribution over file/folder/git/HTTPS, per-device identity and pairing, the measured PARITY.md matrices, and inventory and trade — locations, contacts, the product catalogue, the stock ledger, purchasing, and sales with invoicing — now with the desktop screens to reach them. Trade also reaches the ledger at last: a goods receipt, a confirmed sale and a payment each post double-entry journals, so this is no longer an inventory app sharing a binary with an accounting app. Every financial report now takes a date range, which is what makes a VAT return for a filing period expressible at all, and period close (close check / run / reopen) locks one. The migration chain was also folded into a single baseline — sixteen files, each table created once in its final shape — which is safe precisely because nothing has shipped and no database exists to migrate. The one exception is 0016, which rebuilds journals (create, copy, drop, rename) because SQLite cannot widen a CHECK constraint in place; a test asserts the resulting schema still matches the baseline byte for byte, so the rebuild cannot quietly drop a trigger or an index. Separately, the repo's own JavaScript is now TypeScript; the Rust crates are untouched by that and stay Rust. Still open — live bank adapters, the remaining nudge tiers, benchmark contribution, and device sync itself (there is identity and a signed oplog, but no transport) — tracked phase-by-phase in ROADMAP.md, and the honest per-item notes there are the ones to read.
These are the shipped desktop app, running with demo data. The full annotated set — every screen, plus light mode — is in docs/SCREENSHOTS.md.
Installers for every desktop platform are on the latest release:
| Platform | Grab |
|---|---|
| macOS | .dmg |
| Windows | .msi or -setup.exe |
| Linux | .AppImage or .deb |
Standalone CLI binaries ship alongside the installers in the same release. Builds are unsigned for now — first launch: macOS right-click → Open; Windows SmartScreen → "More info" → "Run anyway".
Prerequisites (Rust stable, Node 20+, Tauri system deps) are listed in docs/GETTING-STARTED.md.
git clone https://github.com/vul-os/slipscan
cd slipscan
# Desktop app
cd apps/desktop && npm install && npm run tauri dev
# Core library + CLI (headless)
cargo build --workspace
cargo run -p slipscan-cli -- init --name "Personal" --kind personal
cargo run -p slipscan-cli -- --help # import, watch, extract, mail-sync, recon, report, fx, tax,
# account, member, attribute, split, pack, pay, vault, device,
# data, serve, listslipscan serve binds 127.0.0.1 unless you explicitly pass --lan — see docs/SELFHOST.md.
Everything runs on your machine. Sources feed one Rust core, the core owns a plain SQLite database holding your books, and the desktop app is a thin shell over the same services. The only network endpoints in the picture are ones you configured — your bank, your mailbox, your LLM provider, and (opt-in, for multi-currency) your own OpenRate instance for provenance-graded FX rates:
%%{init: {'theme':'base','themeVariables':{'fontFamily':'ui-monospace, SFMono-Regular, Menlo, monospace','primaryColor':'#334155','primaryBorderColor':'#94a3b8','primaryTextColor':'#e2e8f0','lineColor':'#0d9488','edgeLabelBackground':'#1e293b','clusterBorder':'#3f8f86','clusterBkg':'transparent'}}}%%
flowchart LR
classDef entry fill:#1e293b,stroke:#64748b,color:#e2e8f0
classDef subject fill:#0f766e,stroke:#5eead4,color:#f0fdfa
classDef downstream fill:#334155,stroke:#94a3b8,color:#e2e8f0
subgraph machine["your machine"]
direction LR
subgraph sources["sources"]
bank["Bank scrapers<br/>(open-source, your session)"]
mail["Email inbound<br/>(your IMAP / Gmail / Graph / Proton)<br/>receipts & bank alerts"]
files["Slips & files<br/>(file picker + a watched drop folder;<br/>drag-drop not built)"]
end
core["Rust core<br/>(slipscan-core services:<br/>categorise, budget, ledger, recon)"]
db[("SQLite<br/>your books, one file")]
app["Tauri desktop app<br/>(Svelte 5, thin IPC)"]
bank --> core
mail --> core
files --> core
core <--> db
app <-->|"IPC"| core
end
openrate["OpenRate<br/>(your self-hosted FX instance —<br/>opt-in, no URL = no FX calls)"]
openrate -.->|"rates + provenance,<br/>cached locally"| core
class bank,mail,files entry
class core subject
class db,app,openrate downstream
Between machines there is no hub — every node is a self-hosted peer. The only thing that crosses the network today is a signed pack (taxonomies, classification rules, bank-alert mail rules, benchmark statistics — ed25519-verified on install), carried by whichever transport you chose: a folder, a stick, a git remote, an HTTPS URL. The dashed edges are the designed and unbuilt half — differentially-private aggregates, category-level statistics noised on-device before they leave it, with community-run aggregators untrusted by design. No contribution code, noise generation or anonymous transport exists in the tree, so no aggregate has ever left a machine. Transactions, merchants, and credentials never appear on any edge:
%%{init: {'theme':'base','themeVariables':{'fontFamily':'ui-monospace, SFMono-Regular, Menlo, monospace','primaryColor':'#334155','primaryBorderColor':'#94a3b8','primaryTextColor':'#e2e8f0','lineColor':'#0d9488','edgeLabelBackground':'#1e293b','clusterBorder':'#3f8f86','clusterBkg':'transparent'}}}%%
flowchart TB
classDef entry fill:#1e293b,stroke:#64748b,color:#e2e8f0
classDef subject fill:#0f766e,stroke:#5eead4,color:#f0fdfa
classDef downstream fill:#334155,stroke:#94a3b8,color:#e2e8f0
a["Alice's node<br/>(desktop or self-host server)"]
b["Ben's node"]
c["Chris's node"]
m["Pack maintainer<br/>(any node, signs releases)"]
agg["Community aggregator<br/>(anyone can run one — untrusted)"]
a <-->|"signed packs<br/>(folder / git / https)"| b
b <-->|"signed packs"| c
a <-->|"signed packs"| c
m -->|"signed classification &<br/>benchmark packs"| a
m --> b
a -.->|"opt-in: DP-noised aggregates,<br/>anonymous transport"| agg
c -.->|"opt-in"| agg
agg -->|"aggregate statistics"| m
class a,b,c entry
class m subject
class agg downstream
Reading benchmark packs is perfectly private — comparison happens locally, and that half is built. Contributing will be off by default, anonymous, and lossy by design; it is not implemented, and docs/BENCHMARKS.md writes the bar down so it cannot quietly slip.
Settings live in SQLite, secrets live in the OS keychain, and there is no required config file — the full model, data locations, and every setting key are in docs/CONFIGURATION.md.
| Document | What it covers |
|---|---|
| GETTING-STARTED.md | Clone to first book: build, import, capture a slip, connect a mailbox, pick an LLM provider |
| ARCHITECTURE.md | The binding contract: layout, tech decisions, domain model, vault spec, non-negotiables |
| CONFIGURATION.md | Settings model, data locations, environment |
| API.md | One service surface, two transports — Tauri IPC and the /api/v1 HTTP server |
| EMAIL.md | Email ingestion: IMAP IDLE, Gmail, Microsoft Graph, Proton Bridge — your accounts, no middleman; and bank alerts → transactions |
| PAYMENTS.md | Payments — reference watches and signed webhooks: watch a payment reference, get a signed webhook when the EFT lands; setup, receiver verification, delivery and retry semantics |
| BANK-ADAPTERS.md | The local, open-source bank-scraper framework and how to write an adapter |
| PACKS.md | Signed packs — classification, benchmark, and bank-alert mailrules kinds: format, signing, verification, distribution |
| NODES.md | Device identity and pairing with no accounts: ed25519 device ids, word fingerprints, key pinning — and exactly what does not sync yet |
| BENCHMARKS.md | Nudges and anonymous peer benchmarks: local DP, cohorts, honest limits |
| SELFHOST.md | Running the core headless on a NAS / home server |
| THREAT-MODEL.md | What protects your credentials, what an attacker gets, residual risks |
| SCREENSHOTS.md | Annotated tour of every screen in the shipped app |
| FAQ.md | Straight answers to the questions everyone asks |
| PARITY.md | Feature parity vs Xero and Vault22 / 22seven, measured: 24 capabilities scored Built / Partial / Not built, every row cited to a file |
Also: ROADMAP.md (phases, with honest partial-status notes), SECURITY.md (vulnerability reporting), CHANGELOG.md.
# Rust workspace
cargo build --workspace
cargo test --workspace
cargo fmt --all -- --check
cargo clippy --workspace --all-targets
# Desktop app
cd apps/desktop
npm install
npm run check # svelte-check
npm run tauri dev # run against the real coreThe workspace denies unsafe_code; money is i64 minor units, never floats; secrets never appear in logs, Debug impls, or IPC responses. Read docs/ARCHITECTURE.md before changing anything structural — it is the contract.
Contributions are welcome — bank adapters, mailbox providers, and classification packs especially. See CONTRIBUTING.md, and docs/BANK-ADAPTERS.md for the adapter checklist.
The mark in brand/ is the source of truth. Every icon this repo
ships — favicon, PWA and app icons, the mark in the README and on the site — is
rendered from brand/logo.svg rather than redrawn, so there is one approved
drawing and no second copy to drift.
Copy it outward, never edit a derived copy, and never edit brand/ to match
something downstream.
MIT OR Apache-2.0 — © VulOS. SlipScan is a VulOS project; source and issues at github.com/vul-os/slipscan.
![]()
vulos — open by design









