docs: document wallet integrity boundaries

This commit is contained in:
2026-08-22 16:56:50 +02:00
parent 76d59d8d08
commit 72f387f6e5
2 changed files with 50 additions and 22 deletions
+32 -18
View File
@@ -25,25 +25,25 @@ e2e/ # Playwright (own AGENTS.md)
## WHERE TO LOOK ## WHERE TO LOOK
| Task | Location | Notes | | Task | Location | Notes |
|------|----------|-------| | ------------------- | ------------------------------------------------------ | ---------------------------------------------- |
| Any fund movement | `src/lnurlcash/ops/` | engine returns changesets, never mutates state | | Any fund movement | `src/lnurlcash/ops/` | engine returns changesets, never mutates state |
| Add a settings page | `src/pages/` + `router/routes.ts` + SettingsPage group | back-button header pattern | | Add a settings page | `src/pages/` + `router/routes.ts` + SettingsPage group | back-button header pattern |
| Native feature | `src/capabilities/` | never import plugins elsewhere | | Native feature | `src/capabilities/` | never import plugins elsewhere |
| Change wallet state | stores call ops + `addBearers`/`markSpent` | add fresh notes BEFORE marking spent | | Change wallet state | stores call ops + `addBearers`/`markSpent` | add fresh notes BEFORE marking spent |
| Mint fee math | `src/lnurlcash/fees.ts` | gross vs net direction matters | | Mint fee math | `src/lnurlcash/fees.ts` | gross vs net direction matters |
## CODE MAP ## CODE MAP
| Module | Role | | Module | Role |
|--------|------| | -------------------------- | --------------------------------------------------------------------------------------------------------- |
| `lnurlcash/ops.ts` | façade: carve/mint/pay/receiveBearer/transfer | | `lnurlcash/ops.ts` | façade: carve/mint/pay/receiveBearer/transfer |
| `lnurlcash/storage/` | AES-GCM bearers+activity, backup (merge entry point), settings | | `lnurlcash/storage/` | AES-GCM bearers+activity, owner-bound credentials and trusted mints, backup (merge entry point), settings |
| `lnurlcash/keys.ts` | BIP39 + LUD-05 linking key; password wrap | | `lnurlcash/keys.ts` | BIP39 + LUD-05 linking key; password wrap |
| `lnurlcash/passkeys.ts` | WebAuthn PRF wrap of the SAME linking key | | `lnurlcash/passkeys.ts` | WebAuthn PRF wrap of the SAME linking key |
| `lnurlcash/nostrBackup.ts` | kind-30078 NIP-44 backup + restore via applyBackup | | `lnurlcash/nostrBackup.ts` | kind-30078 NIP-44 backup + restore via applyBackup |
| `lnurlcash/nwc/` | NIP-47 wallet service (per-connection budget) | | `lnurlcash/nwc/` | NIP-47 wallet service (per-connection budget) |
| `stores/wallet.ts` | state none/locked/unlocked; linking key in memory only while unlocked | | `stores/wallet.ts` | serialized owner lifecycle; state none/locked/unlocked; linking key in memory only while unlocked |
## CONVENTIONS ## CONVENTIONS
@@ -79,8 +79,22 @@ npm run cap:sync # build + cap sync android
- NixOS: use the flake dev shell (nodejs_22 + chromium for e2e). - NixOS: use the flake dev shell (nodejs_22 + chromium for e2e).
`sass-embedded` is aliased to pure-JS `sass` via npm overrides. `sass-embedded` is aliased to pure-JS `sass` via npm overrides.
- lnurlcash-kit comes from `github:TheCryptoDonkey/lnurlcash-kit` pinned to - `lnurlcash-kit@0.1.1` and `lnurlcash-conformance@0.1.1` are exact npm
a commit — the `prepare` script (our merged PR#1) builds dist on install. registry packages. Keep their manifest pins, registry tarball URLs, and lock
integrity values intact.
- Credential and authorization records are bound to the canonical owner ID
derived from the linking key. Ordinary writes require an exact match with the
persisted saved-key owner. Ownerless legacy records migrate only after a
proven unlock, never from a restored file claim or passkey-first attempt.
- Wallet create, unlock, lock, restore, and forget transitions are serialized.
Forget locks first, drains NWC, clears runtime keys and owner namespaces,
then removes the saved key only after biometric deletion succeeds.
- Storage events are wakeups, not authoritative payloads. Re-read matching
storage on an event, including `key === null`, before converging state or
invalidating a stale owner tab. Web Locks serialize supported browsers but
do not make another tab's localStorage cache current; trusted mints reconcile
through a durable IndexedDB commit mirror before success. `withStorageLock`
falls back to local execution without a cross-tab guarantee.
- tsconfig deliberately relaxed (`exactOptionalPropertyTypes` etc. off) to - tsconfig deliberately relaxed (`exactOptionalPropertyTypes` etc. off) to
keep the protocol core untouched; `src/lnurlcash` has an eslint override. keep the protocol core untouched; `src/lnurlcash` has an eslint override.
- Gitea remote dropped; origin = GitHub. Gitea mirror = pull-mirror on the - Gitea remote dropped; origin = GitHub. Gitea mirror = pull-mirror on the
+18 -4
View File
@@ -22,8 +22,9 @@ lnurlcash-kit directly.
``` ```
ops.ts / ops/ # flows: carve (exact-amount), mint, pay, receiveBearer, ops.ts / ops/ # flows: carve (exact-amount), mint, pay, receiveBearer,
# transfer (inter-mint); ops.ts is the façade # transfer (inter-mint); ops.ts is the façade
storage/ # encrypted bearers + activity log, settings, backup, storage/ # encrypted bearers + activity log, owner-bound NWC,
# nwcConnections, passkeySlots; storage.ts is the façade # passkey and trusted-mint records, settings, backup;
# storage.ts is the façade
keys.ts # BIP39, LUD-05 linking key derivation, password wrap keys.ts # BIP39, LUD-05 linking key derivation, password wrap
passkeys.ts + passkeyWrap.ts # WebAuthn PRF wrap (same linking key) passkeys.ts + passkeyWrap.ts # WebAuthn PRF wrap (same linking key)
nostrBackup.ts + nostr/ # kind-30078 backup, NIP-44 self-encryption nostrBackup.ts + nostr/ # kind-30078 backup, NIP-44 self-encryption
@@ -38,8 +39,21 @@ test-utils.ts # mock mint harness used by *.test.ts
- Style: NO semicolons, 2-space indent, single quotes, `{braced}` imports - Style: NO semicolons, 2-space indent, single quotes, `{braced}` imports
without inner spaces — deliberately different from the rest of the app without inner spaces — deliberately different from the rest of the app
(eslint override); keep the tested core diffable against its lineage. (eslint override); keep the tested core diffable against its lineage.
- Storage: localStorage keys `sattle_*`; strict shape validation on read, - Storage: localStorage keys `sattle_*`; strict shape validation on read.
malformed entries dropped; read-modify-write under `withStorageLock`. Credential, NWC, passkey, and trusted-mint records belong to the canonical
saved-key owner. Normal writes require that exact persisted owner; migration
of ownerless legacy records has its own proof-gated API.
- Concurrency: read-modify-write uses `withStorageLock` where Web Locks are
available, but lock handoff is not a localStorage visibility barrier. The
trusted-mint repository reconciles from a durable IndexedDB commit mirror
before one successful localStorage write and before resolving. Its fallback
is local execution only, with no cross-tab serialization guarantee. Storage
events are wakeups, so listeners re-read current storage instead of trusting
`event.newValue`, including on clears.
- Lifecycle: wallet transitions serialize create, restore, unlock, lock, and
forget. Activation completes proven-owner migration before exposing unlocked
state. Forget locks, drains NWC, clears runtime and owner-bound state, then
removes the saved key after biometric deletion succeeds.
- Network: kit calls only; injectable transport/options so tests never - Network: kit calls only; injectable transport/options so tests never
touch the network. No WebSocket at import time (lazy `import()`). touch the network. No WebSocket at import time (lazy `import()`).
- Every module header comment explains the WHY, including failure models. - Every module header comment explains the WHY, including failure models.