A Splitwise-style expense splitter — groups record shared expenses, Splitcore works out who owes whom, and settlements clear the balances.
The one architectural rule: there is exactly one implementation of the money math, and both the server and the client run that same compiled Go code. The server calls it as a Go package; the Flutter app calls it over FFI. Split amounts can never disagree between client and server, because there is no second implementation to disagree with.
splitcore/ Pure Go, stdlib only. money / settle / balance + cgo FFI shim.
server/ Go + PocketBase. Schema, access rules, validation hooks, balance recompute.
splitcore_sdk/ Dart. FFI bindings + PocketBase client. The only API the app may use.
app/ Flutter. UI only — no money math, no direct PocketBase access.
docs/ Architecture, data model, data flow, API reference, decisions.
Requires Go 1.26.4, Flutter (Dart 3.9+), Python 3 (ABI smoke
test), and a C toolchain (gcc) for cgo.
git clone git@github.com:abdulroufsidhu/splitcore.git
cd splitcore
make check # builds the native library and runs every test suiteThen, in two terminals:
make server # PocketBase on http://0.0.0.0:8090 (admin UI at /_/)
make app # builds libsplitcore.so and launches the Flutter appOn first run, open http://127.0.0.1:8090/_/ to create the superuser
account; migrations create all six collections automatically under
go run. Then sign up in the app, create a group, add an expense, and
settle up.
| Target | Does |
|---|---|
make check |
fmt check + vet + analyze + every test. What CI runs. |
make native |
Build libsplitcore.so for this Linux host |
make bundle-native |
Cross-compile Android ABIs and copy into the Flutter runner |
make server |
Run PocketBase on 0.0.0.0:8090 |
make app |
Run the Flutter app against a local server |
make test |
Go + Dart SDK + Flutter tests |
make fmt |
Format Go and Dart in place |
make clean |
Delete build outputs |
splitcore/build/ holds one script per target — Linux, Android (4 ABIs),
iOS, macOS, Windows — plus a ctypes smoke test that exercises the C ABI
with no Dart or Go in the loop. See
splitcore/build/BUILD.md for host
requirements and cross-compilation gotchas.
See server/README.md for collections, access rules,
the staleness endpoint, and the balances-cache contract.
For a container: docker compose up -d (see Deployment
below).
cd app && flutter run # talks to https://splitcore.orgolink.chThat deployed server is the built-in default. make app overrides it with
--dart-define=POCKETBASE_URL=http://127.0.0.1:8090 for local work; pass
POCKETBASE_URL=http://10.0.2.2:8090 (emulator) or a LAN address (physical
device) to reach a server elsewhere. See app/README.md.
make test # everything
make test-go # Go unit tests + FFI ABI smoke test
make test-sdk # Dart SDK tests (spawns a real PocketBase subprocess)
make test-app # Flutter widget testsThe SDK's integration tests start the actual server on an ephemeral port with a temp data directory — they test the real wire contract, not a mock.
docker compose up -d
curl http://localhost:8090/api/healthSee docs/deployment.md for HTTPS termination,
backups, and restore.
| Doc | Covers |
|---|---|
| docs/architecture.md | Component boundaries and why they are where they are |
| docs/data-model.md | Collections, fields, relations |
| docs/data-flow.md | A request's path from tap to balance |
| docs/api-reference.md | SDK and HTTP surface |
| docs/decisions.md | Architectural decisions and their tradeoffs |
| docs/development.md | Day-to-day workflow |
| docs/deployment.md | Containers, HTTPS, backups |
- Now: module/repo consistency, CI, native packaging, containerized server.
- Next: SDK correctness — parameterized filters, pagination, atomic expense writes, expense editing, password reset and email verification.
- Then: app maturity — offline reads, error and retry states, accessibility, localization, search, export.
MIT — see LICENSE.