Skip to content

38. SEP-7 pay URI + deep link - #518

Merged
Miracle656 merged 2 commits into
Miracle656:mainfrom
ibochivincent-lang:feat/sep7-pay-deep-link
Jul 30, 2026
Merged

38. SEP-7 pay URI + deep link#518
Miracle656 merged 2 commits into
Miracle656:mainfrom
ibochivincent-lang:feat/sep7-pay-deep-link

Conversation

@ibochivincent-lang

Copy link
Copy Markdown
Contributor

Summary

  • Adds frontend/mobile/lib/sep7.ts, ported from frontend/wallet/lib/sep7.ts (Sep7Parsed, looksLikeStellarAddress, parseQrValue, buildSep7PayUri), plus parseDeepLink which additionally accepts this app's own veil://pay?... scheme alongside the standard web+stellar:pay?... SEP-7 URI (see sdk/src/sep7.ts for the fuller validated implementation the web wallet's send flow uses).
  • Wires a deep-link listener into frontend/mobile/app/_layout.tsx (Linking.getInitialURL for cold start + Linking.addEventListener('url', ...) for warm start) that parses inbound links and, when a destination is present, routes to /send with the destination/amount/asset/memo as route params.
  • Registers "web+stellar" as an additional URL scheme in app.json (alongside the existing "veil" scheme) so the OS opens such links in this app.
  • Adds a minimal frontend/mobile/app/send.tsx placeholder that reads the pre-filled params and renders them, so the deep link's effect is visible — the interactive recipient/amount form + passkey submit are separate follow-up issues (backlog feat(wallet): tap a transaction in Activity tab to view full details #36, feat(wallet): add one-tap Friendbot faucet button for zero-balance testnet wallets #37).
  • Updates the mobile README's structure notes.

closes #466

Test plan

  • Manually traced parseDeepLink against web+stellar:pay?destination=...&amount=...&asset_code=...&asset_issuer=...&memo=..., veil://pay?destination=..., and an unrelated veil://settings link (correctly returns null so it falls through to normal expo-router handling) — all resolve as expected.
  • npm install && npm run typecheck / on-device deep link test — not run: this sandbox has no Node.js installed, so I could not execute the toolchain or a real device/simulator test. Please verify before merging, and consider adding a jest suite mirroring frontend/wallet/tests/sep7.fuzz.test.ts once tooling is available (mobile currently has no test runner configured).

Adds lib/sep7.ts (ported from frontend/wallet/lib/sep7.ts) to parse both
web+stellar:pay?... URIs and this app's own veil://pay?... deep link
scheme, and wires a Linking listener into the root layout that routes a
matched link to /send with the destination/amount/asset/memo pre-filled.
Registers "web+stellar" as an additional URL scheme in app.json so the OS
opens such links in this app.

A minimal send.tsx placeholder renders whatever got pre-filled; the
interactive recipient/amount form and submit flow are separate follow-up
issues.
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

@ibochivincent-lang is attempting to deploy a commit to the miracle656's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 27, 2026

Copy link
Copy Markdown

@ibochivincent-lang Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

You flagged that you couldn't run the toolchain, so — same as on your #516I ran it:

$ git merge origin/main    # clean, no conflicts
$ npm run typecheck        # tsc --noEmit
TYPECHECK_EXIT=0

Clean. And thank you again for marking the unrun checks unticked rather than assuming.

The parsing work is careful. parseDeepLink returning null for an unrelated link like veil://settings, so it falls through to normal expo-router handling instead of swallowing it, is the correct instinct and easy to get wrong.

There's a large overlap problem though, and it isn't your fault — I handed out two issues covering the same surface.

Overlap with #508

#508 (Closes #498, deep-linking config) is open and implements inbound deep-link resolution too, via lib/deepLinks.ts. It already handles SEP-7:

export const SEP7_SCHEME = 'web+stellar';
const SEP7_PARAM_MAP: Record<string, string> = { ... };
if (scheme === SEP7_SCHEME) { ... }
// resolveDeepLink('web+stellar:pay?destination=GABC') // '/pay?to=GABC&uri=…'

Two differences matter for deciding which survives:

  1. #508 uses app/+native-intent.ts, expo-router's official interception hook, rather than a manual Linking.getInitialURL() + addEventListener('url') pair in _layout.tsx. The manual approach has to hand-manage the cold-start/warm-resume split and is prone to double-handling the initial URL; +native-intent is given the URL by the router and returns a route.
  2. #508 ships 246 lines of tests (deepLinks.test.ts, appConfig.test.ts). This PR has none — which you noted, correctly, since mobile has no runner configured yet.

So I'm keeping #508's resolution path. That's a call about which mechanism to standardise on, not a judgement on your code.

What I'd like from this PR

Your lib/sep7.ts has two things #508 does not have, and I want both:

export function parseQrValue(value: string): Sep7Parsed | { destination: string } | null
export function buildSep7PayUri(opts: { ... }): string

#508 only resolves inbound links. It has no way to construct a SEP-7 URI — which the receive/request flow needs in order to generate a payment request — and no QR parsing. That's real, non-duplicated value.

Please narrow this PR to:

  • Keep parseQrValue, buildSep7PayUri, looksLikeStellarAddress, and the Sep7Parsed type.
  • Drop parseDeepLink / parseSep7Uri / parseVeilLinkUri#508's resolveDeepLink covers these.
  • Drop the Linking listener in app/_layout.tsx+native-intent.ts supersedes it, and running both means the link gets handled twice.
  • Drop the app.json change. #508 removes app.json entirely in favour of app.config.ts, and already registers your scheme there: scheme: [DEEP_LINK_SCHEME, SEP7_SCHEME] with SEP7_SCHEME = 'web+stellar'.
  • Drop app/send.tsx. #507 (navigation shell) provides the route stubs, and #508 routes SEP-7 links to /pay rather than /send — worth syncing with @Olorunfemi20 on which target is right, since your reading (/send prefilled) is arguably the better UX.

That leaves a focused PR: the SEP-7 construction and QR-parsing helpers that the send and receive flows will both need.

On tests

You're right that there's no runner today. #508/#510 add a Mobile — typecheck & test CI job, and #524/#525 add jest.config.js. Once one of those lands, buildSep7PayUri and parseQrValue are ideal unit-test targets — pure functions, string in, string out. Mirroring frontend/wallet/tests/sep7.fuzz.test.ts as you suggested would be very welcome as a follow-up.

Sorry for the overlapping scope — that's mine to fix, and I'll be more careful splitting the mobile backlog.

The inbound-routing half of Miracle656#466 landed with Miracle656#508: lib/deepLinks.ts already
maps web+stellar:pay?... onto /pay with the SEP-7 parameters translated, and
app/+native-intent.ts drives it identically on cold start and warm resume.
Reduce this branch to the SEP-7 payload work that is genuinely missing:

- Drop app.json (main uses app.config.ts), the Linking handler added to
  app/_layout.tsx (duplicates +native-intent.ts), and the root app/send.tsx
  (/send is owned by the (tabs) group).
- Trim lib/sep7.ts to the parts deepLinks.ts does not cover: parseSep7Uri,
  parseQrValue, looksLikeStellarAddress and buildSep7PayUri. Remove
  parseVeilLinkUri/parseDeepLink so there is one owner for inbound routing.
- Replace the private destination-only builder in (tabs)/receive.tsx with the
  shared one, so requesting a specific amount is a parameter away.
- Add lib/__tests__/sep7.test.ts (14 cases) covering parsing, the QR bare
  address path, and a build/parse round-trip including memo encoding.

tsc clean; jest 10 suites / 174 tests; expo lint clean.
@Miracle656

Copy link
Copy Markdown
Owner

Merging, though scoped down quite a bit — half of #466 landed underneath this branch while it was open.

What was already done. #508 shipped lib/deepLinks.ts and app/+native-intent.ts, which between them already resolve web+stellar:pay?destination=…&amount=… onto /pay with the SEP-7 parameters translated (destinationto, asset_codeasset, and the raw URI preserved as uri), identically on cold start and warm resume. /pay then redirects into the send screen with those values, and (tabs)/send.tsx renders them. So the acceptance criterion — a web+stellar:pay?... / veil:// link prefills the send form — is satisfied on main today, and is covered by tests in lib/__tests__/deepLinks.test.ts.

Because of that I dropped the parts of this branch that would have been a second implementation of the same thing:

  • app.json — no longer exists; app.config.ts replaced it, and it already registers both veil and web+stellar schemes.
  • The Linking handler added to app/_layout.tsx+native-intent.ts is expo-router's own hook for this and runs before any navigation state is built, so a second listener in the layout would race it.
  • The root app/send.tsx/send is owned by the (tabs) group since feat(mobile): navigation shell #507, so this would have been a colliding route.
  • parseVeilLinkUri / parseDeepLink from lib/sep7.ts — same reason; there should be one owner for inbound routing.

What was genuinely missing, and is what merged. The SEP-7 payload helpers on either side of routing, which deepLinks.ts doesn't cover:

  • buildSep7PayUri — this one has a real consumer. (tabs)/receive.tsx had its own private copy that only handled destination, with a comment noting it was inlined "because the shared lib lives in the web/SDK workspace". It now uses yours, so requesting a specific amount is a parameter away rather than a rewrite.
  • parseQrValue / looksLikeStellarAddress — the bare-address-or-URI branch a QR scanner needs. There's no mobile scanner component yet, but expo-camera is configured and this is the piece it will want.
  • parseSep7Uri — kept as the parser those two build on.

I also added lib/__tests__/sep7.test.ts (14 cases): parsing each field, case-insensitive scheme, rejecting non-pay operations and non-SEP-7 input, blank params treated as absent, the bare-address QR path, and a build→parse round-trip including a memo containing & and =.

Verified: tsc --noEmit clean, jest 10 suites / 174 tests, expo lint clean, CI green.

Worth flagging for whoever picks up the follow-up: looksLikeStellarAddress is a prefix-and-length check, not a checksum. #508's README notes that full SEP-7 validation — address checksums, amount ranges, hostile callbacks — was deferred, and sdk/src/sep7.ts already implements it for the web wallet. That's still open, and is the more valuable remaining half of this area.

@Miracle656
Miracle656 merged commit d93d4c1 into Miracle656:main Jul 30, 2026
10 of 13 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.

38. SEP-7 pay URI + deep link

3 participants