work / tether
Tether
A lend-and-track app: every object gets a scannable tag, and lending is a scan between two phones.
- Years
- 2026
- Status
- paused
What it is
Tether tracks who has your things. Each object gets a QR tag. A scan offers borrow, return or pass-on, a pass-on gives the owner ten minutes to veto it, and the chain of custody stays visible from the owner's phone.
Read more
The API is Bun with zero npm dependencies: Bun.serve, bun:sqlite, argon2id through Bun.password, Google ID tokens verified with WebCrypto, compiled to a single executable. A transfer is one compare-and-swap statement, UPDATE devices SET holder_id = ? WHERE id = ? AND holder_id = <the holder you saw>, and it only wins if exactly one row changed.
Who has it, how many hops, the journey: all of them are folds over an append-only custody log, with an idempotency key absorbing retries. In the recorded load test, twenty concurrent claims on one object produced one transfer, three “already yours”, sixteen 409s and exactly one custody event. A rerun on 12 October 2026 split the losers four and fifteen; the one transfer and the one event held.
Stack
- TypeScript
- Bun
- bun:sqlite
- Expo
- React Native
Counted
- 0API npm dependencies
apps/api/package.json - 1 transfer20 concurrent claims
1 custody event; README run and a 2026-10-12 rerun - 21commits
git rev-list
How it works
The system as a machine on a bench. Hover or use the arrow keys to drive it.
The path through it
- ScanPhone A scans the tag in the Expo app.
- ClaimA ~90-line router hands the request to one compare-and-swap UPDATE.
- Win or 409changes === 1 wins; anyone holding a stale view gets a 409.
- LogOne append to custody_events; a unique idempotency key absorbs retries.
- Show the chainHolder, hops and journey are folds over the log.