One of our early publisher partners noticed something while shopping on other crypto-enabled sites:
This is a pattern that has a name in the crypto payment industry: the monitored address (or "payment address") flow. And it turns out, it might solve the iPhone problem more cleanly than anything else we've tried.
The key insight: the shop's browser and the buyer's wallet never need to talk to each other. The buyer opens their wallet independently, sends to the address they see on screen, and the server detects the payment by watching the blockchain. No browser extension. No relay server. No deep links. No app switching.
Every mobile crypto payment solution we've explored so far has the same fundamental constraint: the shop's browser needs to communicate with the buyer's wallet app. That's hard on iPhone because iOS isolates apps from each other.
| Approach | How browser talks to wallet | iPhone friction |
|---|---|---|
| Browser extension (Ch 1–4) | Wallet injects into browser | Impossible — iOS doesn't allow extensions |
| Deep links (Ch 5, Phase 1) | Universal link opens wallet browser | User leaves Safari entirely |
| WalletConnect (Ch 5, Phase 2) | Relay server bridges browser ↔ wallet | Clunky — relay instability, experimental Solana support |
| Monitored address (Ch 7) | They don't talk at all | Zero friction — wallet is independent |
The Solana Pay specification defines exactly this pattern with an elegant correlation mechanism called a reference key.
For each order, the server generates a random keypair. The public key becomes the "reference" — it's included as a non-signing account in the buyer's transfer instruction. The server then calls getSignaturesForAddress(reference) to find the exact transaction that matches this order. No address reuse, no memo parsing, no ambiguity.
// Server generates a payment request
const reference = Keypair.generate().publicKey // unique per order
const paymentUrl = encodeURL({
recipient: merchantWallet,
amount: new BigNumber(12.00),
splToken: USDC_MINT,
reference, // ← this is the magic
label: 'My Shop',
message: 'Premium Content Access',
})
// Server monitors for the payment
const signatures = await connection.getSignaturesForAddress(reference)
// When signatures.length > 0, the payment arrived
| Detection Method | Latency | Cost | Best For |
|---|---|---|---|
RPC polling (getSignaturesForAddress every 2-3s) | 2–5 seconds | Free (RPC credits only) | Demo, low-volume shops |
| Helius enhanced webhooks | <2 seconds | $25/month (developer tier) | Production, real-time UX |
| Custom validator subscription (websocket) | <1 second | RPC provider cost | High-volume, latency-critical |
For a typical small publisher, RPC polling is free and fast enough. A buyer sends USDC from their phone, and within 3–5 seconds the shop page updates to "Payment received!" — comparable to a Venmo notification.
The current x402 signing flow (Chapters 1–4) has the buyer sign a specific transaction — exact amount, exact recipient, exact token. The server verifies that exact transaction. The monitored-address pattern is more permissive: it accepts any incoming transfer that matches the expected amount and reference.
| Concern | Signing Flow (Ch 1–4) | Monitored Address (Ch 7) |
|---|---|---|
| Proves payer authorized the transfer | Yes — cryptographic signature | No — only proves funds arrived |
| Amount verification | Server checks exact amount on-chain | Same — server checks received ≥ expected |
| Front-running risk | None — tx is pre-signed | Possible — mitigated by random reference keys |
| Order correlation | Server knows exactly which tx | Reference key links payment to order |
| Expiration | Tx expires with blockhash (~2 min) | Reference must expire server-side (15 min recommended) |
verify() function carries forward unchanged. Same net-delta accounting, same canonical USDC mint check.A natural follow-up question: "How do micropayments work in this model?"
The monitored-address pattern works beautifully for $1+ payments — the buyer is already thinking "I'm buying something, let me open my wallet and send." But below $1, the manual-send UX breaks down. Asking a human to open a wallet app, paste an address, carefully type "$0.03", and confirm — for three cents — is absurd.
| Amount Range | Best Payment Pattern | Why |
|---|---|---|
| < $0.10 | Embedded wallet + auto-sign (Ch 6 future) | Must be invisible — user shouldn't "feel" the payment |
| $0.10 – $1.00 | Browser signing (Ch 1–4) or embedded wallet | One-click approve is fast enough for small amounts |
| $1.00 – $100 | Monitored address (Ch 7) or browser signing | Manual send is natural at this range — like Venmo |
| > $100 | Browser signing (primary) + monitored address (fallback) | High-value needs cryptographic payer proof |
The x402 framework doesn't force one pattern. It provides adapters for each — and the checkout UI auto-detects the buyer's environment and offers the best available option.
A new adapter — createSolanaMonitorAdapter — implements the same ChainAdapter interface as the signing and WalletConnect adapters. The framework's registry pattern means adding this path doesn't change any existing code:
// Register alongside existing adapters
client.registerChain('solana', solanaAdapter) // browser signing
client.registerChain('solana-wc', walletConnectAdapter) // mobile deep links
client.registerChain('solana-qr', monitorAdapter) // monitored address (new)
// Auto-detect: if browser wallet found → use signing flow
// if mobile + no wallet → offer QR pay
// if desktop + no wallet → show onboarding
findReference(), validateTransfer(), and createQR() built in.This chapter is currently in the research and design phase. The team has analyzed the pattern, identified the security mitigations, and confirmed the infrastructure exists (Solana Pay SDK + Helius webhooks). The next step is building a working demo — a new page in the progressive proof that shows the QR code flow end-to-end.
Estimated effort: 2–3 days for a working demo, 1 additional week for production hardening (expiry, partial payment handling, Helius webhook integration).