Skip to content

feat(spark): shared wallet abstractions and provider foundation#3746

Open
esaugomez31 wants to merge 34 commits intofeat--account-migration-to-non-custodial-uifrom
feat--spark-foundation-shared-abstractions
Open

feat(spark): shared wallet abstractions and provider foundation#3746
esaugomez31 wants to merge 34 commits intofeat--account-migration-to-non-custodial-uifrom
feat--spark-foundation-shared-abstractions

Conversation

@esaugomez31
Copy link
Copy Markdown
Collaborator

@esaugomez31 esaugomez31 commented Apr 2, 2026

Summary

Implements the foundation and shared wallet abstractions for self-custodial integration. This establishes the provider-agnostic layer that allows custodial and self-custodial accounts to coexist behind shared interfaces.

No UI changes. No Breez SDK dependency. All existing custodial behavior is unchanged.

What this PR does

  • Shared typesWalletState, ActiveWalletState, NormalizedTransaction, 7 payment adapter interfaces, AccountDescriptor, ContactAdapter in app/types/
  • Feature flagsnonCustodialEnabled and stableBalanceEnabled in Firebase Remote Config with cascade rule
  • Persistent state — Schema 6 → 7 migration adding activeAccountId for account selection
  • Custodial adapters — Wraps existing Apollo/GraphQL wallet data and transactions behind the shared interfaces in app/custodial/
  • CustodialWalletProvider — React context that maps HomeAuthed query to ActiveWalletState
  • Routing hooksuseActiveWallet(), usePayments(), useAccountRegistry(), useHasCustodialAccount(), useMonetaryPreferences()
  • SDK logginglogSdkEvent() and connectToSdkLogger() in app/self-custodial/ ready for Breez SDK integration
  • i18n — 6 new namespaces across 28 languages: AccountTypeSelectionScreen, BackupScreen, RestoreScreen, BackupNudge, StableBalance, BackendFeatureGate

Architecture

Screens → useActiveWallet() / usePayments()
              ↓
         Account Registry (activeAccountId)
              ↓                         ↓
    CustodialWalletProvider     SelfCustodialWalletProvider (Epic 2)
    (wraps Apollo/GraphQL)      (wraps Breez SDK)
              ↓                         ↓
         WalletState               WalletState  ← same shape

New files (17)

Directory Files
app/types/ wallet.types.ts, transaction.types.ts, payment.types.ts, contact.types.ts
app/custodial/ adapters/wallet-adapter.ts, adapters/payment-adapter.ts, mappers/transaction-mapper.ts, providers/wallet-provider.tsx
app/hooks/ use-account-registry.ts, use-active-wallet.ts, use-payments.ts, use-has-custodial-account.ts, use-monetary-preferences.ts
app/self-custodial/ logging.ts

Modified files (5)

  • app/config/feature-flags-context.tsx — 2 new flags
  • app/store/persistent-state/state-migrations.ts — schema 7
  • app/hooks/index.ts — barrel exports
  • app/app.tsxCustodialWalletProvider in component tree
  • .storybook/views/story-screen.tsx — schema version bump

@esaugomez31 esaugomez31 changed the title feat: add shared wallet, transaction, payment, and contact types feat: shared wallet abstractions and provider foundation Apr 2, 2026
@esaugomez31 esaugomez31 marked this pull request as ready for review April 2, 2026 15:16
@esaugomez31 esaugomez31 self-assigned this Apr 2, 2026
@esaugomez31 esaugomez31 requested review from Copilot and grimen and removed request for Copilot April 2, 2026 15:20
Copy link
Copy Markdown
Contributor

@grimen grimen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Shared wallet abstractions and provider foundation

Solid foundation PR — clean layering, strong test coverage, low deployment risk. Requesting changes on a few items below; medium/low are suggestions.


🔴 High priority (address before merge)

1. use-active-wallet.ts:17 — rollback effect is fragile and untested
The hasRolledBack one-shot ref won't re-fire if nonCustodialEnabled toggles off→on→off mid-session. Drop the ref (the effect is idempotent once setActiveAccountId lands) or key it off the flag value so it re-arms on transitions. Add tests covering:

  • Rollback fires when the flag is disabled with a custodial fallback present.
  • Rollback does not mutate self-custodial persistent data (spec NFR15).
  • Note the no-fallback branch (spec architecture.md:300-302 — maintenance screen) as a known follow-up, not blocking this PR.

2. payment-adapter.ts:73,86 — empty-string sentinels on error

return { invoice: "", errors: [toPaymentError("Failed to create invoice")] }
return { address: "", errors: [toPaymentError("Failed to get address")] }

Callers that don't check errors first will render a QR code for "". Make invoice/address optional and omit on failure, or return a discriminated union.

3. Migration chain 3→4→5→6→7 is untested end-to-end
Only the direct 6→7 step is tested. Add one test starting from schema 3 that walks the full chain.


🟡 Medium priority (worth doing in this PR)

4. Split CustodialWalletProvider — extract pure mapper
wallet-provider.tsx:38-72 mixes status derivation, wallet filtering, and transaction partitioning in one useMemo. Extract mapHomeAuthedToActiveWalletState(data, {loading, error, isAuthed}) into app/custodial/mappers/. Provider becomes ~15 lines of glue; the mapper is trivially testable without renderHook or mocks.

5. Extract the rollback effect into its own hook
use-active-wallet.ts:19-29 — distinct concern from "return the active wallet state." Pull into useSelfCustodialRollback(...). Pairs naturally with fix #1 and makes the rollback independently testable.

6. transaction-mapper.ts — fee handling and test gaps

  • fee: toMoneyAmount(tx.settlementFee, ...) always sets a fee even when settlementFee === 0. Field is typed fee?, so omit on zero.
  • Add coverage for settlementFee === 0 and BTC-only / USD-only wallet configurations in the provider tests.

7. Move account ID constants out of the hook
CUSTODIAL_DEFAULT_ID / SELF_CUSTODIAL_DEFAULT_ID in use-account-registry.ts:15-16 are system-level identifiers, not hook internals. Move to wallet.types.ts or a dedicated account-ids.ts so tests and other modules don't duplicate string literals.


🟢 Low priority (follow-up OK)

8. Extract createCustodialDescriptor / createSelfCustodialDescriptor factories from useAccountRegistry.
9. Extract markSelected(accounts, activeId) as a pure helper.
10. Move transaction partitioning (wallet-provider.tsx:52-60) into transaction-mapper.ts.
11. Add failed(message) helper in payment-adapter.ts to consolidate repeated failed-result construction.
12. Replace logSdkEvent if-ladder with a dispatch map (self-custodial/logging.ts:22-40).
13. logging.ts:20 — unknown log levels default to Error, sending unknown debug lines to Crashlytics. Default to Info or Debug.
14. Feature flag cascade test — lock the stableBalanceEnablednonCustodialEnabled invariant.


Spec alignment note

Verified suggestions against blink-specs/self-custodial/architecture.md:

  • create* naming convention is preserved — the spec mandates createSelfCustodial* symmetry across all seven payment adapters, so the custodial side keeps its create* prefix even on non-factory exports.
  • Rollback behavior in #1 respects NFR15 (no mutation of self-custodial local data).
  • Shared adapter interfaces in payment.types.ts match the spec's contract definitions 1:1.

Verdict

Approve after #1, #2, #3 are addressed. Medium items are cheap to fold into this PR since the affected files are all new. Low items are fine as follow-ups.

@grimen grimen changed the title feat: shared wallet abstractions and provider foundation feat(spark): shared wallet abstractions and provider foundation Apr 7, 2026
@esaugomez31 esaugomez31 force-pushed the feat--spark-foundation-shared-abstractions branch from 860b015 to 5b2cb7e Compare April 7, 2026 23:13
@esaugomez31 esaugomez31 force-pushed the feat--account-migration-to-non-custodial-ui branch 2 times, most recently from 554835c to 88c7729 Compare April 8, 2026 20:22
@esaugomez31 esaugomez31 force-pushed the feat--spark-foundation-shared-abstractions branch 2 times, most recently from 8368d8e to a759b91 Compare April 8, 2026 22:56
@esaugomez31 esaugomez31 force-pushed the feat--account-migration-to-non-custodial-ui branch from 88c7729 to a043417 Compare April 8, 2026 22:56
@esaugomez31 esaugomez31 changed the base branch from feat--account-migration-to-non-custodial-ui to graphite-base/3746 April 8, 2026 23:05
@esaugomez31 esaugomez31 force-pushed the feat--spark-foundation-shared-abstractions branch from a759b91 to 3fe7201 Compare April 9, 2026 00:28
@esaugomez31 esaugomez31 changed the base branch from graphite-base/3746 to feat--account-migration-to-non-custodial-ui April 9, 2026 00:28
@esaugomez31 esaugomez31 requested a review from grimen April 9, 2026 02:23
grimen
grimen previously approved these changes Apr 9, 2026
Copy link
Copy Markdown
Contributor

@grimen grimen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All review feedback has been addressed — nice work.

Minor note for follow-up: The createCustodial* prefix on payment adapters (createCustodialSendPayment, createCustodialClaimDeposit, etc.) leaks the concrete type into what should be an implementation-agnostic adapter interface. Once the routing layer (via usePayments / useActiveWallet) fully hides adapter selection from consumers, consider dropping the prefix to just createSendPayment, createClaimDeposit, etc. — each scoped by their module path. Not blocking, but worth aligning before the pattern solidifies across more adapters.

status: AccountStatus.Available,
}
const scAccount = {
id: "sc-default",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: scAccount isn't very readable — even in tests, prefer spelling out selfCustodialAccount. Not blocking this PR, but something to be aware of going forward.

@esaugomez31 esaugomez31 force-pushed the feat--spark-foundation-shared-abstractions branch from 3fe7201 to c6482ce Compare May 6, 2026 02:05
@esaugomez31 esaugomez31 force-pushed the feat--account-migration-to-non-custodial-ui branch from f54d8d9 to 76a475b Compare May 6, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants