Skip to content

Payments 6/8: Wallet onboarding and non-custodial payment UX for non-crypto-native users - #151

Merged
wumibals merged 1 commit into
LadderMine:mainfrom
bellabuks:feature/143-wallet-onboarding-payment-ux
Aug 22, 2026
Merged

Payments 6/8: Wallet onboarding and non-custodial payment UX for non-crypto-native users#151
wumibals merged 1 commit into
LadderMine:mainfrom
bellabuks:feature/143-wallet-onboarding-payment-ux

Conversation

@bellabuks

Copy link
Copy Markdown
Contributor

Summary

Implements wallet onboarding, real signing, and the surrounding trust/custody UX (issue #143).

  • FreighterAdapter (app/src/lib/wallet/freighterAdapter.ts): a real WalletAdapter implementing signTransaction against window.freighter — previously nothing in the app ever called it (WalletButton only invoked Freighter's connect-time methods). signTransaction's shape is deliberately identical to the SDK's Signer interface (sdks/typescript/src/types.ts), so an adapter instance can be passed straight through as signer the moment the app depends on the SDK directly. Error classification mirrors the SDK's TransactionPipeline.classifySignerError exactly (same regexes, same disconnect-before-reject ordering).
  • adapters.ts: explicit per-provider adapter resolution — never "whichever extension loaded first" (the multi-wallet-extension edge case from the issue). LOBSTR/xBull throw a clear, typed WalletUnavailableError; WalletButton now renders them as visibly disabled with a "Coming soon" badge and an explanatory note, replacing the silent stub that only ever failed on click.
  • Network-mismatch detection: FreighterAdapter.checkNetwork() (a new optional WalletAdapter method) is checked right after connecting and again right before signing, surfacing a wrong-network wallet as a typed WalletNetworkMismatchError instead of an opaque submission failure.
  • requestWalletSignature (paymentFlow.ts) is real now: given an XDR, it resolves the connected wallet's adapter, checks the network, and signs — reachable and tested through the exact call site runPaymentSubmission uses.
  • Trustline detection (services/horizon.ts) is fully real and dependency-free — Horizon's /accounts/{id} returns trustlines as plain JSON, no XDR encoding needed. The deposit flow gets a new step that checks for a missing USDC trustline before the confirm step and explains what's needed, rather than letting the deposit fail opaquely at simulation.
  • Real fee estimate (lib/feeEstimate.ts) replaces the hardcoded '0.00001 XLM' with getFeeStats() — real, current network-wide fee data that needs no transaction built to query. Presented as an "up to" (p99) ceiling with a one-line "this is a network fee, not a YieldLadder fee" note, and an honest "unavailable" state (never a fabricated number) on failure.
  • Non-custodial explainer copy at the deposit confirm step and EarlyExitModal, grounded in the README's actual architecture ("the Strategist can propose pool allocations but can never withdraw your funds... every withdrawal requires your own wallet signature") rather than generic marketing language.

What's still honestly blocked (called out, not silently skipped)

Two pieces remain blocked on the exact same pre-existing constraint already documented in this codebase (see placeholderSubmissionHash in deposit/page.tsx and the identical note in hooks/usePosition.ts): the app doesn't yet depend on @yieldladder/sdk/@stellar/stellar-sdk, and adding it needs a pnpm install to regenerate app/pnpm-lock.yaml correctly, which this environment can't safely run.

  • Building the actual deposit/early-exit transaction XDR — so requestWalletSignature is real and fully tested, but neither page yet has a real XDR to pass it, so today's actual signing behavior is unchanged (still the placeholder-hash pipeline downstream). The moment a caller has a real XDR, this exercises the complete real signing path instead of a no-op.
  • Building the trustline changeTrust transaction itself — also needs XDR construction. The deposit flow's new trustline step does the detection for real and gives the user a clear, actionable path (add it via their wallet's own UI, then recheck), rather than presenting a placeholder-XDR signing prompt that would sign meaningless data in a real wallet popup — that would be actively misleading in a real testnet walkthrough, not a genuine demonstration of trustline setup.

One correction to the issue's description: EarlyExitModal's "Exit Early" button was already wired to the real submission pipeline as of #142 (merged before this branch was created) — the issue's description of it as unhandled predates that merge. This PR adds the explainer copy there and leaves the already-working handler alone.

Test plan

  • freighterAdapter.test.ts — connect/isConnected/signTransaction happy paths and error mapping, classifyFreighterError's full taxonomy including the disconnect-over-reject ordering.
  • adapters.test.ts — provider resolution, LOBSTR/xBull's typed rejection.
  • horizon.test.ts — trustline present/absent/wrong-issuer, unconfigured-issuer short-circuit, account-not-found and network-failure error mapping.
  • feeEstimate.test.ts — p99-derived ceiling display, fallback to classic inclusionFee, honest unavailable state on a missing/malformed response or RPC failure.
  • paymentFlow.test.ts — extended requestWalletSignature coverage: no-op without an XDR (unchanged existing behavior), WalletUnavailableError with no session, real signing call with the right args, network-mismatch short-circuit before ever calling signTransaction, and a real WalletRejectedError propagating through.
  • CI (contracts / app typecheck+test / sdk) — pending on this PR.
  • Manual testnet walkthrough with screenshots (issue's Definition of Done) — not done in this PR; flagging rather than claiming it, since the actual deposit submission remains behind the SDK-dependency blocker described above. Worth doing once that lands.

Closes #143

…stimate

Implements wallet onboarding and non-custodial payment UX (issue LadderMine#143):

- FreighterAdapter (app/src/lib/wallet/freighterAdapter.ts): a real
  WalletAdapter implementing signTransaction against window.freighter —
  previously nothing in the app ever called it. Error classification
  mirrors the TypeScript SDK's TransactionPipeline.classifySignerError
  (same regexes, same disconnect-before-reject ordering) so wallet errors
  are handled identically wherever they're thrown from. checkNetwork()
  detects a wallet pointed at the wrong network before signing, added as
  an optional WalletAdapter method.
- adapters.ts: explicit per-provider adapter resolution — never
  "whichever extension loaded first." LOBSTR/xBull throw a clear,
  typed WalletUnavailableError; WalletButton now renders them as visibly
  disabled with "Coming soon," not a silent stub that only fails on click.
- paymentFlow.ts's requestWalletSignature is real now: given an XDR, it
  resolves the connected wallet's adapter, checks network, and signs —
  reachable and tested through the exact call site runPaymentSubmission
  uses. It's still a no-op when no XDR is given, because building the
  actual deposit/early-exit transaction depends on the SDK becoming a
  real app dependency, which needs a pnpm install to regenerate
  app/pnpm-lock.yaml correctly — this environment can't safely run that
  (same blocker already documented on placeholderSubmissionHash). Neither
  page passes a real XDR yet, so today's actual signing behavior is
  unchanged; this wires the real, tested path for the moment one does.
- Trustline detection (services/horizon.ts) is fully real and
  dependency-free: Horizon's /accounts/{id} returns trustlines as plain
  JSON, no XDR encoding needed. The deposit flow gets a new step that
  checks for a missing USDC trustline before confirm and explains what to
  do — actually adding the trustline in-app remains blocked by the same
  SDK dependency (changeTrust also needs XDR construction), stated
  honestly in the UI rather than faked with a placeholder signing prompt.
- Fee estimate (lib/feeEstimate.ts) replaces the hardcoded '0.00001 XLM'
  with getFeeStats() — real, current network-wide fee data requiring no
  transaction to build, presented as an "up to" (p99) ceiling with a
  one-line network-fee-not-YieldLadder-fee note, never a fabricated
  number on failure.
- Non-custodial explainer copy added at the deposit confirm step and
  EarlyExitModal, grounded in the README's actual architecture (the
  Strategist can propose allocations but never withdraw funds; every
  withdrawal needs the user's own signature) rather than generic
  marketing language.

Note: EarlyExitModal's "Exit Early" button was already wired to the real
submission pipeline as of LadderMine#142 — the issue's description of it as
unhandled predates that merge. This PR adds the explainer copy there and
leaves the already-working handler alone.
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

@bellabuks is attempting to deploy a commit to the wumibals' projects Team on Vercel.

A member of the Team first needs to authorize it.

@wumibals wumibals left a comment

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.

Reviewed the wallet onboarding and non-custodial payment UX PR (closes #143).

  • Making signTransaction's shape identical to the SDK's Signer interface, and mirroring classifySignerError's exact regexes and disconnect-over-reject ordering, means FreighterAdapter is a drop-in signer the moment the app depends on the SDK directly — no adapter-layer rewrite needed later, just a wiring change. That's the right way to build ahead of a known future dependency.
  • Explicit per-provider adapter resolution instead of "whichever extension loaded first" directly closes the multi-wallet-extension edge case named in the issue, and rendering LOBSTR/xBull as visibly disabled with a "Coming soon" badge is honest UX — better than a button that looks functional but silently fails on click.
  • Checking network mismatch both right after connecting and again right before signing (not just once) correctly accounts for a user switching networks mid-session in their wallet extension, surfacing it as a typed error instead of an opaque submission failure downstream.
  • Trustline detection via Horizon's plain-JSON /accounts/{id} response, with no XDR dependency needed, is a smart way to deliver real functionality now rather than waiting on the SDK-dependency blocker — and correctly scoped to detection only. Explicitly choosing not to build a placeholder trustline-setup transaction (which would sign meaningless data in a real wallet popup) rather than faking a "working" flow is the right call — a fabricated signing prompt would be actively misleading in a real testnet walkthrough, not a shortcut.
  • The fee estimate replacing a hardcoded value with real getFeeStats() data, presented as a p99 "up to" ceiling with a clear "not a YieldLadder fee" disclaimer, and an honest unavailable state on failure rather than a fabricated number, is exactly the right instinct for financial UI — never show a number you can't back.
  • Correctly catching that the issue's description of EarlyExitModal's button as unhandled was stale (already fixed in #142, merged before this branch existed) and adjusting scope accordingly instead of redoing already-done work, shows real attention to the current state of the codebase rather than blindly following the issue text.
  • The "still honestly blocked" section is transparent about exactly what remains fake (deposit/early-exit XDR construction, trustline changeTrust transaction) and precisely why (same pre-existing pnpm-workspace/lockfile constraint already documented elsewhere in the codebase) — this isn't scope creep avoidance, it's the same real constraint every PR in this app-layer track has hit.
  • Test coverage is strong: adapter error taxonomy including disconnect-ordering, provider resolution, trustline present/absent/wrong-issuer/unconfigured-issuer, fee estimate's fallback and honest-unavailable paths, and requestWalletSignature's full call chain including network-mismatch short-circuit before ever attempting to sign.

CI is green across Soroban contracts, Next.js dashboard, and TypeScript SDK. The manual testnet walkthrough wasn't done, but that's consistent with real deposit submission still being blocked on the documented SDK-dependency gap — this PR doesn't newly expose any live fund-moving path, so that's a reasonable thing to defer rather than a red flag. Vercel's FAILURE is the usual unauthorized deployment integration link, unrelated to the code.

Approving — solid onboarding UX work with honest boundaries around what's real versus still blocked.

@wumibals
wumibals merged commit 5cb09b5 into LadderMine:main Aug 22, 2026
3 of 4 checks passed
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.

Payments 6/8: Wallet onboarding and non-custodial payment UX for non-crypto-native users

2 participants