Repository navigation
feat(wallet-sdk): accounts slice (step 6) - #1167
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Automated cross-model review — accounts slice (step 6)Scope: step-6 slice diff Verdict: well-built, faithful, well-tested slice. The projection-vs-domain split is applied consistently at every flipped call site, the fat-cache integrity design holds under adversarial attack (the MED —
|
|
MED addressed in 0eda60b — The five LOW items are held for the maintainer's disposition, per the review's framing — none are folded into this fix commit. |
Re-review — MED fix (
|
|
Post-review addition (maintainer-requested): the web now sources its key queries from the SDK's session keys instead of deriving its own copies — e0ea372 ( Net: the ~5 duplicate enclave derivations per session bootstrap drop to zero; one derivation authority; raw private-key bytes leave the query cache as a side effect. One declared deviation from the discussed sketch (smaller, same direction): Verified at the keeper gate: full chain re-run on the frozen tree (fix:all clean, 9/9 packages typecheck, tests exit 0 — wallet-sdk 99, web 36). |
|
On Why it currently is the way it is. A cashu account's balance is derived state — What changes under the expose-full-domain-types direction (the direction #1166 has now taken for the receive/send equivalents): the projection rationale collapses, and accounts can align cleanly — declare The call is yours + petar's, not mine — it reopens B1, which petar ruled explicitly for this slice. Two coherent end-states:
Sized and ready to execute either way on your word. |
|
Rebased onto the domain-types contract (d5d2f18 landing in #1166: domain What changed vs the previous head:
Verified at the keeper gate on the rebuilt head: fix:all clean, all packages typecheck, tests exit 0 (wallet-sdk 105, web 36); repo-wide grep confirms zero projection references remain. Independent delta review running. |
b5fb64f to
4bef09b
Compare
4bef09b to
d9a1fa2
Compare
d9a1fa2 to
0a34e6c
Compare
|
Rebased onto master following the #1166 merge and retargeted the PR (base:
Next: sweeping all #1166 review comments for reasoning applicable to this slice, per @petar; will report findings here. |
#1166 review-comment sweep — applicability to this PRPer @petar's ask: enumerated all #1166 commentary (55/55 review threads, 192 inline comments, 3 issue comments — pagination exhausted, counts cross-checked against the REST comment total) and audited this PR's full diff against every ruling that generalizes beyond the auth slice. Applied (2 new commits)
Verification on the pushed head: lint + typecheck clean, wallet-sdk 105/105, web-wallet 36/36. Already satisfied (verified against the diff, no change needed)
Held as questions (parity vs. ruling — your call, no code changed)
|
| const refreshSession = useCallback( | ||
| async (redirectTo?: string) => { | ||
| // invalidateAuthQueries drops the previous user's derived keys (a login can | ||
| // switch users without a prior sign-out), so no separate eviction here. |
…and provisioning Extracts the accounts slice of the wallet into @agicash/wallet-sdk: the web app reads accounts, session keys, user, and provisioning through the SDK contract instead of owning that logic. - accounts namespace (sdk.accounts.*) backed by an in-SDK repository, with a /temporary bridge for repositories not yet migrated. - session-scoped key plumbing: derivation memos fenced by an AbortController scope and revocable on session end; web key queries call the derivation leaves directly. - user provisioning internalized on the auth lifecycle, fingerprint-guarded, fired on a replay-latest auth.session-started event the host seeds its caches from; acceptTerms decoupled from provision. - session-lifecycle hardening: typed SessionEndedError / DisposedError, terminal dispose, explicit guarded Encryption facade. - accounts.list() and the event return bare Account[]; getExtendedAccounts exported as a caller-side selector for isDefault (see #1170). Squashes #1167 (includes #1168 and #1171). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75a10a8 to
8203356
Compare
…and provisioning Extracts the accounts slice of the wallet into @agicash/wallet-sdk: the web app reads accounts, session keys, user, and provisioning through the SDK contract instead of owning that logic. - accounts namespace (sdk.accounts.*) backed by an in-SDK repository, with a /temporary bridge for repositories not yet migrated. - session-scoped key plumbing: derivation memos fenced by an AbortController scope and revocable on session end; web key queries call the derivation leaves directly. - user provisioning internalized on the auth lifecycle, fingerprint-guarded, fired on a replay-latest auth.session-started event the host seeds its caches from; acceptTerms decoupled from provision. - session-lifecycle hardening: typed SessionEndedError / DisposedError, terminal dispose, explicit guarded Encryption facade. - accounts.list() and the event return bare Account[]; getExtendedAccounts exported as a caller-side selector for isDefault (see #1170). Squashes #1167 (includes #1168 and #1171). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8203356 to
cfefd30
Compare
Step 6 of the wallet-SDK migration (
docs/superpowers/specs/2026-06-24-wallet-sdk-no-cache-production-design.md): wrap the accounts domain inAccountsApiand flip the web's accounts data layer ontosdk.accounts.*. Plan with the full Decision Record (B1–B7, maintainer-resolved) and gate record:docs/superpowers/plans/2026-07-13-wallet-sdk-accounts-slice.md.Stacked on
sdk/auth-slice(#1166). After #1166 merges: rebase onto master, retarget, re-verify (two-green-PRs rule).What lands
session-keys.ts): memoized, generation-fenced getters over Open Secret (encryption keypair, cashu seed, spark mnemonic, locking xpub, spark identity pubkey), cleared inonSessionEnded— a different user can never be served the previous user's keys.sdk.accounts(get/list/cashu.add) over one shared domain→projection mapper;AddCashuAccountParamspinned ({ name, mintUrl, currency, purpose });AgicashSdkPickgrows'accounts'.['accounts']cache holds projection-typed objects fetched viasdk.accounts.list(); hidden domain fields ride along at runtime until step 18 (type-level strip now, physical then). The shared mapper is the only cache entry point (queryFn, realtime, add onSuccess, ensure seed, claim upserts). Reality-class:sdk.accounts.*is TYPE-honest / RUNTIME-fat-until-18, intended and time-boxed; assumes web-internal consumers only during the window./temporarybridge v2: internal-repo accessor +toDomainAccount()checked cast (throwsMissingDomainFieldsErrornaming missing fields — never a bare cast) + mapper re-export. All carry step-18 removal notes.sdk.user.ensure(): the_protected.tsxbootstrap ported verbatim (key derivation, default accounts, Zod-aware retry, session-fenced memo); returns{ user, accounts }projection-typed. Timestamp params are replayed acceptance times from pending-terms storage (maintainer-confirmed intent, JSDoc'd). Web glue keeps master's structure exactly: short-circuit, prefetch warm-up, conditional seeding.Account/CashuAccount/SparkAccount/ExtendedAccountare now the projections (domain types import from/temporary); display reads.balanceoff the cache; money flows stay domain end-to-end through sanctioned unwrap seams.Unwrap seams (every hidden-field read passes
toDomainAccount())account-hooks: the four getter hooks unwrap internally;useDefaultAccount/useAccountOrDefaultreturn domain.buy-input/send-input/receive-input: unwrap at the selector option build;account-selectorrenders domain viagetAccountBalance(master verbatim).send-providergetAccounts; routes_protected.buy.checkout/receive.cashu/receive.spark/transfer.$destinationAccountId/send; claim route.account-proofs,transaction-additional-details,use-track-spark-account-balances(spark listener) at their seam.receive-cashu-token-hooks: unwraps forgetSourceAndDestinationAccounts; maps the domain source account back through the shared mapper for display items.Declared (questions and transitional items, not silent changes)
accounts.get(id)is not session-gated (master's repository posture — RLS scopes it);list/addgate on the session foruserId,user.*gates every verb. Parity kept; consistency call open to review.withRetry/delayported into the SDK lib; web copies remain for unmigrated consumers (dies with their slices).account-repository-hooksrelocated tofeatures/receive/(not deleted): its only consumers are unmigrated receive repos constructingAccountRepositorysynchronously; reshaping them is step 8–16 scope.account-service-hooksdeleted.session-keys.tsports master's hardcoded MAINNET for the spark identity pubkey (master's own TODO).sparkDebugLogstays a named/temporaryexception (maintainer ruling); dies at 18 with its call site.Verification
fix:allclean · workspace typecheck green (all 9 packages incl. web) · tests exit 0: wallet-sdk 96 (25 new: projection completeness type+runtime, mapper single-entry, checked-cast thin-object throw, ensure memo/retry, key fencing across session end), web 36, money 14, bolt11 9, ecies 18, cashu 35. Chain re-run independently at the keeper gate on the frozen tree.🤖 Generated with Claude Code