Skip to content

refactor: unify custodial and self-custodial under adapter pattern #3777

Draft
esaugomez31 wants to merge 6 commits into
mainfrom
refactor--complete-adapter-pattern
Draft

refactor: unify custodial and self-custodial under adapter pattern #3777
esaugomez31 wants to merge 6 commits into
mainfrom
refactor--complete-adapter-pattern

Conversation

@esaugomez31
Copy link
Copy Markdown
Collaborator

@esaugomez31 esaugomez31 commented May 20, 2026

Overview

This PR completes the adapter pattern across the mobile app so that custodial and self-custodial accounts share the same code paths at the screen and hook layer. Each flow exposes a single interface through usePayments(), and the mode-specific implementations (Apollo for custodial, Breez SDK for self-custodial) live behind that interface.

Conversion flow — applied

Adapter unification

ConvertAdapter is now an object that exposes a two-step getQuote then execute pipeline. Both the custodial and self-custodial implementations conform to this interface, so screens and hooks no longer branch on isSelfCustodial to decide which mutation or SDK call to run.

Custodial conversion

createCustodialConvert is now a real implementation that performs the BTC and USD intra-ledger swap through Apollo mutations. The previous implementation returned a hard-coded "not supported" failure.

Self-custodial conversion

The SDK-driven quote and execute logic moved from app/self-custodial/bridge/convert.ts to app/self-custodial/adapters/payment.ts (createSelfCustodialConvert). The bridge now only exposes the low-level SDK primitives that the adapter composes (prepareConversion, recordConvertError, PreparedConversion).

Provider-agnostic hooks

  • useSelfCustodialConversion to useConversionExecution
  • useSelfCustodialConversionGuard to useConversionDustGuard
  • useNonCustodialConversionLimits to useConversionLimits (moved to app/hooks/; returns no-op limits for custodial and real limits for self-custodial)

These hooks no longer take an enabled flag, and the screens no longer wire mode-specific instances. They consume the adapter directly.

Auto-convert with dependency injection

executeAutoConvert now receives a ConvertAdapter as a parameter instead of importing the self-custodial implementation directly. useAutoConvertListener obtains it from usePayments() and passes it down. The executor is now decoupled from the concrete implementation.

Shared payment failure helper

Three identical failed(message) declarations across adapter files have been collapsed into a single failedPayment exported from app/types/payment.ts.

Fee row regression fix

The conversion confirmation screen was hiding the fee row while the quote was loading. The row now stays visible with the activity indicator during the quoting phase, and only hides when the quote resolves with a zero fee (the new custodial intra-ledger case).

Polish

  • Inline ternaries and repeated mode checks extracted into named variables.
  • Local sleep redeclaration replaced with the existing app/utils/sleep helper.
  • Import order normalized in touched files.

Receive flow — applied

Adapter unification

ReceiveLightningAdapter and ReceiveOnchainAdapter are now contracts that take a walletCurrency parameter and return a unified result shape. Both the custodial and self-custodial implementations conform to these interfaces, so the screen and hook layers no longer branch on isSelfCustodial or read Apollo mutations directly.

Custodial receive

createCustodialReceiveLightning and createCustodialReceiveOnchain are real implementations that wrap the Apollo mutations (lnInvoiceCreate, lnNoAmountInvoiceCreate, lnUsdInvoiceCreate, onChainAddressCurrent). Lightning routing is driven by walletCurrency so a USD-wallet receiver routes to lnUsdInvoiceCreate with the USD walletId, BTC routes to lnInvoiceCreate/lnNoAmountInvoiceCreate with the BTC walletId, and onchain routes to the matching walletId.

Self-custodial receive

The SDK-driven Bolt11 and on-chain logic moved from app/self-custodial/bridge/receive.ts to app/self-custodial/adapters/payment.ts (createSelfCustodialReceiveLightning / createSelfCustodialReceiveOnchain). The bridge now exposes only the low-level SDK primitives that the adapter composes (prepareReceiveBolt11, prepareReceiveOnchain, recordReceiveError).

Provider-agnostic hooks

  • useOnChainAddress consumes receiveOnchain from usePayments() and takes walletCurrency as a required parameter. The Apollo gql template and useOnChainAddressCurrentMutation reference are gone from this file.
  • useOnchainResolver drops the isSelfCustodial branch and the BTC/USD walletId lookup; it just forwards onchainWalletCurrency from the carousel to the adapter.
  • The custodial usePaymentRequest and the self-custodial useSelfCustodialPaymentRequest both read receiveLightning (and receiveOnchain where needed) from usePayments() instead of constructing or importing adapters themselves.

Dispatcher consolidation

use-payments.ts extracts buildCustodialPayments(walletIds, mutations) and buildSelfCustodialPayments(sdk) helpers. The Apollo mutation hooks for the receive path (useLnInvoiceCreateMutation, useLnNoAmountInvoiceCreateMutation, useLnUsdInvoiceCreateMutation, useOnChainAddressCurrentMutation) and their gql templates now live alongside the convert and send mutations in this single dispatcher.

Shared receive error helper

failedReceive(message) and extractApolloErrorMessage(apolloErrors, payloadErrors, fallback) are exported from app/types/payment.ts and reused by both adapter implementations. The custodial adapter no longer hand-rolls the apolloErrorMessage ?? payloadErrors[0]?.message ?? fallback pattern at each call site.

State machine preserved

useInvoiceLifecycle's Idle → Loading → Created → Paid / Error / Expired transitions and the regenerate path remain byte-equivalent to pre-refactor. Internal type-narrowing was extracted into a getLightningInvoiceData helper, and the PaymentRequest field exposed by the self-custodial hook was renamed from pr to paymentRequest. The self-custodial hook keeps its crashlytics observability with amount and currency context on adapter failures, no-invoice payloads, and addPendingAutoConvert rejections, and propagates each to PaymentRequestState.Error.

Polish

  • generateQuote was split into buildOnchainInfo / buildLightningInfo / buildPayCodeInfo dispatched by buildInfoForType, removing the original 130-line if/else chain.
  • Constants extracted: DEFAULT_MEMO, LNURL_BECH32_LIMIT, LNURL_RENDER_DELAY_MS.
  • Internal pr parameter renamed to request inside payment-request.ts for readability.
  • formatPayCodeAmountQuery and encodeLnurl extracted as named helpers.
  • PaymentRequestInformation's applicationErrors and gqlErrors fields collapsed into a single errors: PaymentError[].

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@esaugomez31 esaugomez31 changed the title refactor(conversion-flow): apply adapter pattern across custodial and self-custodial refactor: unify custodial and self-custodial under adapter pattern May 20, 2026
@esaugomez31 esaugomez31 marked this pull request as ready for review May 20, 2026 17:58
@esaugomez31 esaugomez31 self-assigned this May 20, 2026
@esaugomez31 esaugomez31 marked this pull request as draft May 20, 2026 17:59
@esaugomez31 esaugomez31 force-pushed the refactor--complete-adapter-pattern branch from f48343a to 9b9c4af Compare May 27, 2026 16:33
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.

1 participant