Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
Built for UK sole traders. Every receipt categorised, every business mile claimed, invoices in a minute, and you always know the tax to set aside.
FinanceTypeScript · JavaScriptGitHub @JamieEricHamiltonOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
SoleTax engineering showcase: HMRC MTD filing server, tax engine, CI/CD and security model
Translation pending
Loading the full README.
Read the full README ↗SoleTax is a Making Tax Digital (MTD) compliance app for UK sole traders: automatic mileage tracking, AI receipt capture, jobs and invoicing, a running tax estimate, and direct quarterly filing to HMRC. It is an iOS app (React Native / Expo, on the App Store since September 2026) with a web portal, both backed by Supabase (Postgres, Auth, Storage, Deno edge functions) on Cloudflare. It is built and run by one person. This repository is a scrubbed, read-only extract of the private production repo so that an engineer can read the parts that matter - the HMRC filing server, the tax engine and its test suites, the CI/CD workflows and the security model - without the production repo being made public. Everything here is real code from production; what was removed, and why, is listed at the end.
Jump to: Architecture · HMRC integration · CI/CD · Security model · Testing and validation · Observability · Known gaps · How this was built · What is in this repo
npm ci
npm run check # tsc + eslint + the tax engine and HMRC server rule suites (~1,000 assertions)
flowchart LR
subgraph Clients
APP[iOS app<br/>React Native + Expo<br/>SQLite/MMKV offline-first]
PORTAL[Web portal<br/>Vite + React<br/>Cloudflare Pages]
end
subgraph Supabase["Supabase (eu-west-1)"]
AUTH[Auth<br/>anon · OTP · Apple · Google]
PG[(Postgres<br/>RLS on every table)]
STORE[(Storage<br/>receipts, invoices)]
subgraph EF["Deno edge functions"]
EXTRACT[claude-extract]
SUBMIT[hmrc-submit]
CB[hmrc-oauth-callback]
MISC[support-agent · send-invoice<br/>delete-account · mapkit-token · …]
end
end
INGRESS[HMRC ingress<br/>Caddy on a single host<br/>captures client port]
HMRC[HMRC MTD APIs<br/>Business Details · Obligations<br/>SE Business · Calculations]
ANTH[Anthropic API]
EMAIL[Resend]
R2[(Cloudflare R2<br/>encrypted backups)]
GHA[GitHub Actions<br/>24 workflows]
CLOCK[Cloudflare Worker<br/>fleet clock]
APP -- own-row CRUD via anon key --> PG
APP --> AUTH
APP -- images --> STORE
PORTAL -- same --> PG
APP & PORTAL -- JWT --> EXTRACT & SUBMIT & MISC
APP & PORTAL -- filing actions --> INGRESS -- signed envelope --> SUBMIT
SUBMIT -- OAuth + FPH headers --> HMRC
HMRC -- redirect --> CB
EXTRACT --> ANTH
MISC --> EMAIL
GHA -- nightly pg_dump + rclone --> R2
CLOCK -- workflow_dispatch --> GHA
GHA -- RLS proof, health, invariants --> PG
How the pieces connect:
service_role key exists only inside those functions and a CI check fails if it ever
appears in a client bundle.hmrc-submit holds the OAuth client secrets,
the user's encrypted HMRC tokens and National Insurance number, assembles HMRC's
fraud-prevention headers and writes one audit row per attempt. Filing requests are
dialled through a small Caddy host rather than the Supabase hostname so the client's
public TCP port can be observed (HMRC requires it; see below).Source: supabase/functions/hmrc-submit/index.ts, supabase/functions/hmrc-oauth-callback/index.ts,
supabase/functions/_shared/hmrc*.ts. Rules pinned by scripts/test-hmrc-server.ts (120 checks).
app/portal ──connect──▶ hmrc-submit mints state = HMAC-SHA256(uid, env, surface, exp 10 min)
returns HMRC authorize URL
user ──authorises on HMRC──▶ HMRC redirects to hmrc-oauth-callback?code&state
hmrc-oauth-callback: verify HMAC → exchange code with client secret (same env the state was
minted for) → AES-256-GCM encrypt access+refresh tokens → upsert
hmrc_connections(user_id, environment, …) → 302 back to app deep link
or portal /tax
state is the whole security model: unforgeable
without HMRC_TOKEN_KEY, time-boxed, and it binds the resulting tokens to exactly one
user id, one environment and one return surface.hmrc-submit with an action field:
status, connect, disconnect, saveNino, businesses, selectBusiness,
obligations, submit, calculate, plus two sandbox-only actions (validate,
createTestBusiness) that the server refuses in production.This is the beginning of the README. Read the rest at the original. Read the full README ↗