In Chapter 1, we proved the simplest possible thing: one buyer sends USDC to one seller on Solana. The recipient wallet address was hardcoded — a constant baked into the page. That was the right starting point. It isolated the payment mechanics and confirmed that the x402 protocol works end-to-end: request, sign, broadcast, verify.
But a real-world shop doesn't have one seller. A marketplace has many. A publisher network has many authors. A merch store might split revenue between the brand, the designer, and the fulfillment partner. The question for Chapter 2: can the buyer choose who gets paid — without changing the payment code?
Here is the entirety of what changed between Chapter 1 and Chapter 2. The payment flow — connect wallet, build transaction, sign, broadcast, verify on-chain — is identical. The only difference is where the recipient address comes from.
| Chapter 1 | Chapter 2 |
|---|---|
payTo = "fixed_wallet_address" |
payTo = dropdown.selectedValue |
A dropdown menu lists the available recipients — each one is just a name paired with a Solana wallet address. When the buyer selects a recipient and clicks "Pay," the same payment function fires. It reads the payTo field from the dropdown instead of a constant. That's it.
This is a small code change but a significant conceptual step. It demonstrates that the x402 payment protocol is recipient-agnostic. The protocol doesn't care who receives the funds — it cares that a valid USDC transfer happened on-chain from the buyer to whoever was specified. This has direct implications for real business models:
The recipient list is a simple data structure — an array of objects, each with a name and a walletAddress. In this demo, the list is defined in the page. In a production system, it would come from a database, an API, or a CMS like Titan.
const recipients = [
{ name: "Alice (Author)", walletAddress: "A1ice...xyz" },
{ name: "Bob (Designer)", walletAddress: "B0b...abc" },
{ name: "Carol (Publisher)",walletAddress: "Car0l...def" },
]
// Payment function — unchanged from Chapter 1
async function pay(payTo, amountUSDC) {
const tx = buildTransferTx(buyer, payTo, amountUSDC)
const signed = await wallet.signTransaction(tx)
const sig = await connection.sendRawTransaction(signed.serialize())
return sig
}
The pay() function is byte-for-byte identical to Chapter 1. It accepts a payTo parameter. Chapter 1 always passed the same address. Chapter 2 passes whichever address the buyer selected. The protocol doesn't know or care about the difference.
The x402 protocol treats the recipient wallet address as a parameter, not a configuration. This is by design. The protocol's job is to ensure that a valid USDC transfer happens on-chain — it verifies the amount, the token (USDC), and the network (Solana). Who receives the funds is a business decision, not a protocol decision.
This separation matters because it means x402 can serve fundamentally different business models without protocol changes:
| Business Model | Who Sets the Recipient | Protocol Change Needed |
|---|---|---|
| Single-seller shop | Hardcoded (Chapter 1) | None |
| Marketplace | Product catalog (Chapter 2) | None |
| Publisher network | Article metadata | None |
| Tip jar / donation | Creator profile | None |
| Revenue split | Smart contract | Multi-output (future) |
Choosing a recipient is a user-interface concern. The payment itself is still "buyer clicks pay, money moves." But what if the content is locked — and the buyer has to pay before they can see it? That's a fundamentally different interaction: the server has to enforce access control based on payment status.
In Chapter 3: Minimal Paywall, we add the HTTP 402 response code — the one the protocol is named after — and build the simplest possible pay-to-read flow. The payment mechanics are identical. The new part is the access layer.