x402 Progressive Demo · Chapter 5 of 7

Mobile Crypto for iPhone

The Safari hurdle — accepting crypto on iOS without forcing users into a different app.
Published 2026-04-16 · Northwoods Software · x402 Payment Protocol

The Problem

A user opens a crypto-enabled shop on their iPhone. They tap "Pay with Crypto," and the page tells them: no wallet detected.

This isn't a bug. It's a platform constraint. Chapters 1 through 4 relied on browser wallet extensions — software like Phantom or MetaMask that injects into the browser and lets websites request transaction signatures. On desktop Chrome or Firefox, this works seamlessly. On iOS Safari, it is impossible. Apple does not allow browser extensions to inject JavaScript into Safari pages.

The user's iPhone has Phantom installed. They have USDC in their wallet. They want to buy something. But Safari and Phantom cannot talk to each other. This chapter is about solving that gap — three different ways, each with different trade-offs.

Key insight: This chapter exists because a real buyer on a real phone hit a real wall. Every chapter in this progression started the same way — someone tried to do something natural and the technology wasn't ready for it yet.

The Three Phases

Rather than picking one solution and betting on it, the team designed a three-phase approach. Each phase works independently. Each adds capability for a broader set of users. The framework auto-detects the buyer's environment and offers the best available option.

Phase 1: Wallet Deep Links (Shipped)

The simplest solution with zero external dependencies. When the checkout page detects iOS Safari (no injected wallet), it shows a "Open in Phantom" button instead of the standard "Pay" button. This button is a universal link — a specially formatted URL that iOS routes to the Phantom app.

The link encodes the shop's URL. When Phantom opens, it loads the shop inside its built-in browser — which does have the Phantom wallet extension injected. The buyer is now inside Phantom's browser, looking at the same shop, with full wallet access. They complete the purchase normally.

iOS Safari Phantom App ────────── ─────────── │ │ 1. │ "No wallet detected" │ │ Shows: "Open in Phantom" │ │ │ 2. │ User taps button │ │ (universal link fires) │ │──────────────────────────▶│ │ │ 3. │ Phantom opens │ loads shop URL │ in its browser │ │ 4. │ Wallet IS available │ buyer pays normally │ │

Trade-off: The buyer leaves Safari. They're now inside Phantom's app. This works, but it's not the seamless in-Safari experience we ultimately want. For buyers who already have Phantom installed, it's a minor context switch. For buyers who don't have Phantom, this phase doesn't help at all.

Phase 2: WalletConnect v2 via Reown AppKit (In Progress)

WalletConnect is a relay protocol that bridges a website and a mobile wallet app without requiring them to share a browser context. The website generates a pairing request (displayed as a QR code on desktop, or a deep link on mobile). The wallet app scans or opens the link, establishing an encrypted relay channel. The website can then request transaction signatures through this channel.

With WalletConnect, the buyer stays in Safari the entire time. They tap "Connect Wallet," their wallet app briefly opens to approve the connection, and then they return to Safari. When they tap "Pay," the wallet app opens again for signature approval, and the buyer returns to Safari to see the confirmation.

Trade-off: More complex setup. Requires a WalletConnect project ID (free tier available). Relay server adds a dependency and occasional instability. Solana support via WalletConnect is still maturing — EVM chains are better supported. But the UX is significantly better: the buyer stays in their browser.

Phase 3: Embedded Wallets via Privy/Turnkey (Planned)

The ultimate solution for mainstream adoption: the buyer doesn't need a wallet app at all. An embedded wallet is created for them — backed by their Apple ID or email — and secured by the device's biometric hardware (Face ID, Touch ID). The buyer taps "Pay," Face ID confirms, and the transaction signs. No app installs. No seed phrases. No extensions.

Trade-off: The buyer is using a custodial or semi-custodial wallet. Crypto purists will object. But for the 95% of potential buyers who will never install Phantom, this is the only viable path. The framework will offer all three phases simultaneously — the buyer who has Phantom gets the full self-custody experience, and the buyer who doesn't gets Face ID checkout.

Options Comparison

ApproachShip TimeDependenciesWallet Required?Buyer Stays in Safari?
Deep Links (Phase 1) 1-2 days None Yes — Phantom or Solflare No — opens wallet app browser
WalletConnect (Phase 2) 1-2 weeks Reown AppKit, WC project ID Yes — any WC-compatible wallet Yes — relay bridges the gap
Embedded Wallet (Phase 3) 2-3 weeks Privy or Turnkey SDK No — wallet created on the fly Yes — fully in-browser
Each phase widens the funnel. Phase 1 captures buyers who already have a wallet app. Phase 2 captures buyers with any WalletConnect-compatible wallet. Phase 3 captures everyone else — including people who have never touched crypto before. The phases are additive, not replacements.

How the Framework Handles Detection

The x402 framework (built in Chapter 4) already has a registry of chain adapters. Mobile support extends this with an environment detection step that runs before the payment UI renders:

// Environment detection (runs on page load)
const env = detectEnvironment()
// env.browser      → 'safari' | 'chrome' | 'firefox' | ...
// env.platform     → 'ios' | 'android' | 'desktop'
// env.walletFound  → true | false
// env.deepLinkable → ['phantom', 'solflare']
// env.wcSupported  → true | false

// The checkout UI adapts automatically:
if (env.walletFound)        → standard pay button (Ch 1-4 flow)
if (env.deepLinkable.length) → "Open in Phantom" button (Phase 1)
if (env.wcSupported)        → WalletConnect button (Phase 2)
else                        → embedded wallet flow (Phase 3)

The buyer never sees this logic. They see one button that does the right thing for their environment. A desktop Chrome user with MetaMask installed sees "Pay $12.00." An iPhone Safari user with Phantom installed sees "Pay with Phantom." An iPhone user with no wallet sees "Pay with Face ID" (once Phase 3 ships).

Market Intelligence

Our competitive intelligence agent monitored the mobile crypto payment landscape during this chapter's development. Key findings:

State preservation across redirects. Any solution that sends the buyer to another app (deep links, WalletConnect approval) risks losing the checkout state. The framework preserves cart data, selected chain, and payment intent in sessionStorage before any redirect, and restores it when the buyer returns. This was a hard-won lesson — the first deep-link implementation lost the cart on every round-trip.

What Changed from Chapter 4

LayerChapter 4Chapter 5
Payment logicMulti-chain via registryUnchanged
Wallet connectionBrowser extension onlyExtension + deep link + WalletConnect + embedded
Platform supportDesktop browsersDesktop + iOS + Android
Environment detectionNoneAuto-detect and adapt UI
State managementIn-memory onlysessionStorage persistence across redirects

What's Next

The framework now handles multiple chains and multiple platforms. But how do we know it works reliably? How do we catch regressions before they reach buyers? And how do we validate that the payment flow works end-to-end without spending real money?

In Chapter 6: Testing, we build the test infrastructure — from unit tests that mock blockchain responses to end-to-end tests that run against Solana devnet. Testing a payment system is fundamentally harder than testing a CRUD app, and the approach we took reflects that.