Extract wallet logic into @agicash/wallet-sdk (live stores)#1157
Draft
ditto-agent wants to merge 159 commits into
Draft
Extract wallet logic into @agicash/wallet-sdk (live stores)#1157ditto-agent wants to merge 159 commits into
ditto-agent wants to merge 159 commits into
Conversation
…ore-based) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sharpen goal: SDK owns business logic + background in both; caching is the contested axis (SDK in B, frontend in A) - rename payment:* lifecycle events -> send:*/receive:*; lock all-instance delivery - split event map: shared SdkCoreEventMap + A-only row events (explicit created/updated, not upserted); B exposes core only - resolve transactions: Promise-based in both (no store); current wallet does not live-update history (verified) and SWR-on-visit is preserved in both variants - define resync() as coarse, idempotent catch-up trigger Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Headless @supabase/realtime-js 2.95 under bun: socket open, broadcast round-trip, postgres_changes subscribe — no window, TLS via mkcert CA. No polling fallback needed; SDK-owned background design stands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tern + recipe) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
entry.client.tsx used a relative path `./lib/money` that was not caught by the ~/lib/money → @agicash/money rewrite pass, causing an UNRESOLVED_IMPORT build failure (170 modules → error vs 2821 after fix). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Declare workspace dep, codemod relative imports (not just ~/ alias), and run build as a gate (tsc misses dangling relative imports under Bundler resolution). Note the DOM/window devtools-formatter follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ind regex) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h/money Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…biome-only) fix:all is biome check (lint/format) and never type-checks; tsc is the catch-all for dangling imports (all forms); the build erases import type so it misses type-only dangling imports. This is why money shipped 3 dangling ../money imports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves apps/web-wallet/app/lib/bolt11/ into packages/bolt11/src/, rewires all 10 import sites from ~/lib/bolt11 to @agicash/bolt11, removes light-bolt11-decoder from the app's direct deps (now owned by the package), and adds @agicash/bolt11 as a workspace dep to apps/web-wallet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves ecies/, sha256.ts, and xchacha20poly1305.ts from apps/web-wallet/app/lib into packages/ecies, hoists @noble/ciphers to the workspace catalog, and rewrites all 8 import sites to @agicash/ecies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…package Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exempt first-party @agicash/opensecret from bun minimumReleaseAge so the intentionally-fresh 1.0.0-rc.0 (storage provider) installs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…secret rc.0 rc.0 makes `storage` required on OpenSecretConfig (pluggable StorageProvider). Browser host passes the bundled browserStorage helper. Unblocks the app typecheck after the catalog bump in the previous commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ovider bridge Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s internal database.ts + cashu/spark account-details json-models move into the SDK (app re-exports them via shims). Generated supabase/database.types stays at repo root, imported relatively. Lightning/swap/melt models stay in the app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…able) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…th:session-expired) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r-lane, re-entrant) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ueryFns) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…online filter) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ts store) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Store hot reads Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(no placeholder store seed) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add disposeAll() to all 3 NUT-17 subscription managers (mint-quote, melt-quote, proof-state); forward fire-and-forget from each tracker's dispose() so registry.deactivate() → processor.dispose() → tracker.dispose() → manager.disposeAll() cleanly unsubscribes per- subscription WebSocket callbacks without closing the shared per-mint socket. ProofStateTracker's explicit no-op is replaced with the teardown. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-add two base-domain methods that Variant B's web cut-over needs but were absent from the frozen base (a210e9d). Exact reproduction of Variant A (sdkx/stateless) additions: UserDomain.setDefaultCurrency (Currency import + writeUserRepo.update patch) and CashuSendOps.getSwap (swapRepository.get pass-through with JSDoc). Full TDD: red→green, 197/197 pass, typecheck exit-0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates ~/lib/storage-adapter.ts (verbatim copy of Variant A's lazy localStorage/sessionStorage adapters) and ~/lib/sdk.ts (Variant A copy with 3 swaps: StatelessSdk→StoreSdk, createStatelessSdk→createStoreSdk, import from @agicash/wallet-sdk/store). Adds fire-and-forget initSdk(location.host) kickoff in _protected.tsx after the auth gate (strangler pattern; B-Task-4/5 will await). Gate: typecheck exit 0, 254 tests across all packages, 0 fail. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e/useStoreSelect) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- auth.ts: getAuthSdk = initSdk(location.host); rewire all useAuthActions methods onto sdk.auth.* (signUp/signIn/signOut[+disposeSdk]/beginGoogle/ signInGuest/requestPasswordReset/confirmPasswordReset[positional→object]/ verifyEmail/upgradeGuest); delete useHandleSessionExpiry + private helpers; keep authQueryOptions/isLoggedIn/sessionHintCookie/Sentry-user verbatim - _auth.oauth.$provider.tsx: handleGoogleCallback → sdk.auth.completeOAuth - verify-email.ts: osVerifyEmail → getSdk().auth.verifyEmail - user-hooks.tsx: requestNewVerificationCode → getSdk().auth.requestEmailVerification; convertGuestToFullAccount drops guestAccountStorage.clear() (SDK manages guest state); useUser/userQueryOptions LEFT UNTOUCHED (B-divergence: BW-T7 converts to store) - wallet.tsx: remove useHandleSessionExpiry call (host effect moves to BW-T5) - _protected.tsx: delete ensureUserData + key-derivation block; await initSdk + sdk.user.get() + sdk.user.acceptTerms() for pending terms; setQueryData user Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… bridge (delete leader) <Wallet> becomes the SDK background boundary: background.start()/stop() on mount/unmount, useSDKActivityTracking forwards online/offline/visibility, focus + online trigger sdk.resync(), and auth:session-expired toasts + signs out. The app's leader-election/TaskProcessor is deleted (the SDK owns processing — single leader). useTransactionLifecycleSync (B-NEW) invalidates the kept transaction queries off core lifecycle events since B has no transaction row events. KEPT: useTrackWalletChanges (shrinks over BW-T6..T11, deleted in BW-T13), Sentry.setUser, useSyncThemeWithDefaultCurrency, useTrackAndUpdateSparkAccountBalances. disposeSdk() stays on the auth.ts sign-out boundary (matching Variant A). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert the accounts read surface from the TanStack AccountsCache /
accountsQueryOptions to the SDK sdk.accounts.all (Store<Account[]>) +
sdk.user.current stores via the BW-T3 useStoreSuspense adapter.
- useAccounts now reads both stores, overlays live spark balances, and
runs getExtendedAccounts + filter/sort in a useMemo (returns the array
directly; consumers updated to drop the { data } destructure).
- All single-account derivations (useAccount, useAccountOrDefault,
useDefaultAccount w/ useRef fallback, useBalance, useGet* getters,
useGetCashuAccountByMintUrlAndCurrency) derive off the store snapshot.
- useAccountOrNull sources from the store; lazy-fetches expired accounts
from the residual accountRepository on store miss.
- useAddCashuAccount.mutationFn -> sdk.accounts.add (fanout writes store);
onSuccess cache upsert removed.
- Spark live-balance overlay: new live-spark-balances useSyncExternalStore
module map; useTrackAndUpdateSparkAccountBalances writes to it.
- Delete AccountsCache + useAccountsCache + useAccountChangeHandlers +
accountsQueryOptions; shrink use-track-wallet-changes (account handler +
invalidate removed, file kept). Trim account-service to statics.
- Repoint gather blind-spots off the deleted symbols: claim-cashu-token
-service (+ its route wiring), cashu-send-quote usePendingMeltQuotes,
send-provider, _protected.send loader.
Gate: bun run typecheck (exit 0) + bun run test (0 fail). Sweep empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert useUser + module readers from the TanStack UserCache/userQueryOptions
to the sdk.user.current Store<User|null> via the BW-T3 store hooks, and repoint
the 4 user mutations to sdk.user.*.
- useUser(select?) -> useStoreSelect(getSdk().user.current, ...) with a
throw-on-null guard; call signature + direct return shape preserved so the
~21 selector-form consumers are untouched.
- getUserFromCache()/getUserFromCacheOrThrow() -> sdk.user.current.get().
- useSetDefaultCurrency/useSetDefaultAccount/useUpdateUsername/useAcceptTerms ->
sdk.user.{setDefaultCurrency,setDefaultAccount,updateUsername,acceptTerms};
drop the onSuccess setQueryData pokes (the SDK fanout writes the store).
- Delete UserCache + useUserCache + useUserChangeHandlers + userQueryOptions +
internal useUpdateUser; shrink use-track-wallet-changes (drop user
handler/cache, keep the file).
- _protected: repoint the user seed to sdk.user.current.set(() => user) so the
synchronous accept-terms / verify-email route guards (which read in the
middleware chain before render) keep working; drop the UserCache import.
- claim-cashu-token-service: remove the dangling UserCache setQueryData poke
(BW-T6 carry-forward); the SDK setDefaultAccount/fanout owns the user store.
Gate: bun run typecheck (all packages exit 0) + bun run test (web-wallet 57/0,
wallet-sdk 197/0). Sweep for UserCache/useUserCache/userQueryOptions/
useUserChangeHandlers in apps/web-wallet/app is empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- useContacts/useContact read from sdk.contacts.all via useStoreSelect (suspends until loaded) - useCreateContact/useDeleteContact delegate to sdk.contacts.add/remove; no cache writes - useFindContactCandidates queryFn repointed to sdk.contacts.search - Delete ContactsCache, useContactsCache, useContactChangeHandlers - Delete contact-repository.ts (no remaining app consumers) - Shrink use-track-wallet-changes: remove contactChangeHandlers + contactsCache.invalidate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nvalidation - queryFns for useTransaction / useTransactions / useHasTransactionsPendingAck / useAcknowledgeTransaction all point to getSdk().transactions.* - TransactionsCache reduced to a key-only static holder (exported); instance methods + useTransactionsCache + useTransactionChangeHandlers deleted - use-track-wallet-changes: removes transaction handlers + transactionsCache.invalidate - use-transaction-lifecycle-sync: reconciled from inline literals to TransactionsCache.Key - cashu/spark-receive-quote-hooks: adapted residual invalidateTransaction call to queryClient.invalidateQueries on TransactionsCache.Key directly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Repoint send mutations to sdk.{cashu,spark}.send.* (create-only, copied
from Variant A): createLightningQuote/execute for cashu+spark lightning;
createTokenSend for the cashu token send (returns {token, swap:PENDING}).
Convert the active cashu-send-swap tracker to lifecycle-event liveness:
useCashuSendSwap/useTrackCashuSendSwap read getSdk().cashu.send.getSwap(id)
and refetch on sdk.on('send:completed'|'send:failed') filtered by the swap
id (carried as payload.quoteId). B has no row events; terminal swaps are
evicted from the SDK store. The share route renders the token immediately.
Delete the 4 send caches (UnresolvedCashuSendQuotesCache, CashuSendSwapCache,
UnresolvedCashuSendSwapsCache, UnresolvedSparkSendQuotesCache) + their change
handlers + the dead useProcess*Tasks/useOn*StateChange/usePendingMeltQuotes/
useUnresolved* selectors + proof-state-subscription-manager.ts. Remove the
send change-handlers from use-track-wallet-changes.ts. The 6 send service/repo
files stay (pinned by transfer/transaction-additional-details).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eive
Repoint receive mutations to create-only sdk.{cashu,spark}.receive.{createLightningQuote,execute}
(copy Variant A's contract; drop app cache adds). Convert the active per-id quote trackers to
app useQuery(sdk.*.receive.get) + lifecycle-event liveness (receive:completed / receive:expired
filtered by protocol+quoteId → refetch), since B has no row events. Wire the deep-link ?claimTo
claim onto sdk.cashu.receive.receiveToken (try/catch, DomainError toast, gift-card redirect) and
the interactive claim onto the 6d surface (createTokenClaim / getClaimableToken / getTokenAccounts);
keep the signed-out _public placeholders and re-source token-receive types from @agicash/wallet-sdk.
Delete the 5 receive caches + change-handlers + dead processors + the 3 lib/cashu mint/melt
subscription managers + the orphaned claim service files (claim-cashu-token-service,
receive-cashu-token-quote-service, cashu-token-melt-data) and the now-orphaned
cashu-receive-swap-hooks. Empty the receive change-handlers out of use-track-wallet-changes
(now a no-op; BW-T13 deletes it).
Gate: bun run typecheck (all workspaces exit 0) + bun run test (web-wallet 57 pass/0 fail,
wallet-sdk 197 pass/0 fail). Browser/live verification owed to BW-T15.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Repoint useGetTransferQuote and useInitiateTransfer mutations to getSdk().transfers.createQuote / getSdk().transfers.execute. Drop useUser (userId now SDK-resolved) and useTransferService. Re-source TransferQuote from @agicash/wallet-sdk. transfer-service.ts kept: still pinned by transfer-confirmation.tsx and transfer-store.ts (TransferQuote type + useTransferService). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete use-track-wallet-changes.ts (was already a no-op after BW-T6..T11)
and remove its call + import from wallet.tsx.
- Delete lib/supabase/{supabase-realtime-hooks,supabase-realtime-manager,
supabase-realtime-channel,supabase-realtime-channel-builder,index}.ts —
the SDK's ChangeFeed owns realtime; the app-side transport is gone.
- Strip agicashRealtimeClient construction + window.agicashRealtime from
database.client.ts; agicashDbClient (DB client) stays for BW-T14 residuals.
- Inline SupabaseRealtimeError class into root.tsx to preserve the error
boundary branch (BW-T14 removes it once the realtime error path is gone).
- Final sweep confirms zero remaining references to the deleted symbols.
Gate: typecheck exit-0 (all 8 packages), test 254 pass / 0 fail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Supabase Residual (carried — identical to Variant A / AW-T14)
`agicashDbClient` and `supabase-session.ts` are intentionally kept.
Consumers verified via grep:
- `features/agicash-db/database.client.ts` — defines `agicashDbClient` itself
- `features/agicash-db/supabase-session.ts` — imported by `database.client.ts`
for `getSupabaseSessionToken`
- `features/shared/feature-flags.ts` — `agicashDbClient.rpc('evaluate_feature_flags')`
(feature flags RPC; no SDK equivalent)
- `features/accounts/account-repository.ts` — pinned by `account-hooks.ts`
(`useAccountOrNull` lazy expired-account DB fetch via `useAccountRepository()`)
- `features/receive/cashu-receive-quote-repository.ts` — pinned by
`transaction-additional-details.tsx` (4× `getByTransactionId` calls for
cashu-receive-quote + cashu-receive-swap details)
- `features/receive/cashu-receive-swap-repository.ts` — same
- `features/send/cashu-send-quote-repository.ts` — pinned by
`transaction-additional-details.tsx`
- `features/send/cashu-send-swap-repository.ts` — pinned by
`transaction-additional-details.tsx` + `transaction-hooks.ts`
(`useReverseTransaction` via `cashuSendSwapRepository.getByTransactionId`)
- `features/user/user-repository.ts` — WAS residual; deleted (see below)
`opensecret.configure()`, Breez WASM prefetch, and Sentry init in
`entry.client.tsx` are all kept (auth canary + live dependencies).
`_protected.tsx` has no `supabaseSessionTokenQuery` prefetch (SDK self-warms).
## root.tsx: SupabaseRealtimeError removed
Removed the inlined `SupabaseRealtimeError` class (BW-T13 interim) and its
`instanceof SupabaseRealtimeError` ErrorBoundary branch. The SDK never throws
a realtime error to the boundary — connection failures degrade via the
`connection:state` event. The remaining branches (NotFoundError, DOMException/
SecurityError, WebAssemblyUnavailableError, generic Error) are intact.
## Dead-file sweep (11 deleted + 2 type-imports re-sourced)
Deleted (all grep-verified app-wide before deletion):
- `features/user/guest-account-storage.ts` — BW-T4 dropped its last use
- `features/transactions/transaction-repository.ts` — app-orphaned; the
residual is the CASHU repos via `getByTransactionId`, not this tx repo
- `features/transfer/transfer-service.ts` — `TransferQuote` type re-sourced
to `@agicash/wallet-sdk` in `transfer-confirmation.tsx` + `transfer-store.ts`
- `features/user/user-service.ts` — orphaned; all user writes now via `getSdk().user.*`
- `features/user/user-repository.ts` — orphaned; only consumer was `user-service.ts`
- `features/receive/cashu-receive-quote-service.ts` — orphaned when
`transfer-service.ts` deleted (was its only caller)
- `features/receive/spark-receive-quote-service.ts` — same
- `features/receive/spark-receive-quote-repository.ts` — only consumer was
`spark-receive-quote-service.ts`
- `features/receive/spark-receive-quote-core.ts` — only consumer was
`spark-receive-quote-service.ts` / `spark-receive-quote-repository.ts`
- `features/send/cashu-send-quote-service.ts` — orphaned when
`transfer-service.ts` deleted; `CashuLightningQuote` type re-sourced
to `@agicash/wallet-sdk` in `send-store.ts`
- `features/send/spark-send-quote-service.ts` — same; `SparkLightningQuote`
type re-sourced to `@agicash/wallet-sdk` in `send-store.ts`
- `features/send/spark-send-quote-repository.ts` — only consumer was
`spark-send-quote-service.ts`
Kept with pins documented:
- `features/send/cashu-send-swap-service.ts` — active (transaction-hooks,
send-confirmation, cashu-send-swap-hooks, send-store)
- `features/receive/cashu-receive-swap-service.ts` — active (5 consumers)
- `features/receive/cashu-receive-quote-core.ts` — pinned by
`cashu-receive-quote-repository.ts` residual
Gate: typecheck exit-0, 289 tests (197 SDK + 57 web-wallet + 35 cashu) / 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Empirical eval of the two complete variants (A=sdkx/stateless, B=sdkx/store). Result is a structural split: every B win traces to its CONTRACT (SDK owns hot-read freshness via Store<T> → app keeps 1 key-holder + 3 hooks vs A's 10 cache classes + 30 wire hooks), every B loss to its ENGINE (patched query-core → readability/headless/node-bundle costs). They are separable behind the frozen internal/engine seam → the eval is the strongest evidence for the spec's deferred Variant C (B's contract on a hand-rolled engine). Dim 4 (behavior parity) owed on live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Local browser verification (guest session) clears the highest-value B-specific risks: boot+hydrate, guest sign-in end-to-end, the BW-T7 sync-route-guard fix, NO empty-store flash (accounts store populated + default resolved — the A ensureLoaded-bug code path, clean in B), zero console errors, app-QueryClient reads (rates + transactions). Money-path / multi-instance / Lightning checks remain owed (need funded wallets + a 2nd instance). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extracts all wallet business logic + background processing into
@agicash/wallet-sdkso it can run headless (MCP / node), not just inside the React app: Cashu + Spark send/receive, accounts, transactions, auth, the six payment state-machine processors, and leader election.Store-based approach: the SDK keeps hot reads fresh as live
Store<T>views (get/subscribe/toPromise), so no frontend re-implements a cache. The web app reads them viauseStore/useStoreSuspense/useStoreSelectand drops its in-app cache + realtime layer entirely; mutations route throughsdk.*.Engine: a hidden, patched
@tanstack/query-core(aMutationObserver-scope task runner + the resident stores), fully confined tointernal/engine/behind an engine-neutralStore<T>(enforced by lint + a test). Also ships the shared lib extraction (@agicash/money · bolt11 · ecies · cashu · utils) and a server-mode SDK for the Lightning-address routes.Status: headless gate green (typecheck + unit tests); browser-verified through guest sign-in + wallet load (no empty-store flash). Live money-path verification (Lightning send/receive,
/lnurl-test) still owed → draft.🤖 Generated with Claude Code