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.
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.
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.
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.
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.
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.
| Approach | Ship Time | Dependencies | Wallet 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 |
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).
Our competitive intelligence agent monitored the mobile crypto payment landscape during this chapter's development. Key findings:
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.
| Layer | Chapter 4 | Chapter 5 |
|---|---|---|
| Payment logic | Multi-chain via registry | Unchanged |
| Wallet connection | Browser extension only | Extension + deep link + WalletConnect + embedded |
| Platform support | Desktop browsers | Desktop + iOS + Android |
| Environment detection | None | Auto-detect and adapt UI |
| State management | In-memory only | sessionStorage persistence across redirects |
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.