x402
x402 Payment Protocol Integration for Scaffold-ETH 2
Prerequisites
Check if ./packages/nextjs/scaffold.config.ts exists directly in the current working directory (do not search subdirectories). If it doesn't exist, this is not a Scaffold-ETH 2 project. Follow the instructions at https://docs.scaffoldeth.io/SKILL.md to scaffold it first. If it exists, continue directly with building.
Overview
x402 is an open payment protocol by Coinbase that uses HTTP status code 402 ("Payment Required") to enable instant stablecoin micropayments over HTTP. When a client requests a protected resource without payment, the server responds with 402 + payment instructions. The client signs a payment, retries the request, and gets access.
This skill covers integrating x402 into SE-2 using Next.js middleware. For the full protocol spec and advanced usage, refer to the x402 docs or the GitHub repo. This skill focuses on SE-2 integration specifics and gotchas.
Dependencies
NextJS package
Add to packages/nextjs/package.json:
{
"dependencies": {
"@x402/core": "^2.2.0",
"@x402/evm": "^2.2.0",
"@x402/next": "^2.2.0",
"@x402/paywall": "^2.2.0"
}
}
Hardhat package (for CLI payment script)
If the user wants a CLI script to test API routes programmatically, add to packages/hardhat/package.json:
{
"scripts": {
"send402request": "hardhat run scripts/send402request.ts"
},
"dependencies": {
"@x402/core": "^2.2.0",
"@x402/evm": "^2.2.0",
"@x402/fetch": "^2.2.0"
}
}
Add to root package.json: "send402request": "yarn workspace @se-2/hardhat send402request"
Environment variables
Create packages/nextjs/.env.development (or .env.local):
# Facilitator service URL — verifies and settles payments
# Default testnet facilitator (free, no signup needed):
NEXT_PUBLIC_FACILITATOR_URL=https://x402.org/facilitator
# Address that receives payments (set to your deployer or any wallet)
RESOURCE_WALLET_ADDRESS=0xYourAddressHere
# CAIP-2 network identifier (eip155:84532 = Base Sepolia, eip155:8453 = Base Mainnet)
NETWORK=eip155:84532
scaffold.config.ts
x402 payments happen onchain, so targetNetworks must include a supported chain. For development, use baseSepolia:
targetNetworks: [chains.baseSepolia],
Do not use hardhat (localhost) as the target network for x402 — the facilitator needs a real chain to verify/settle payments.
x402 Protocol Flow
Client GET /api/protected
→ Server: no X-PAYMENT header → responds 402 + PAYMENT-REQUIRED header
Client: signs EIP-712 payment authorization (USDC approve)
Client GET /api/protected + X-PAYMENT header
→ Server middleware: sends payment to facilitator for verification
→ Facilitator: verifies signature, checks balance
→ Server: serves content
→ Server middleware: sends settlement to facilitator
→ Facilitator: executes the USDC transfer onchain
Key insight: The user never sends a transaction themselves. They sign an EIP-712 message authorizing a USDC transfer. The facilitator executes it after the server confirms content was delivered.
Middleware Configuration
The core of x402 integration is middleware.ts in the Next.js app root. The v2 API uses paymentProxy from @x402/next with explicit server and paywall setup.
// packages/nextjs/middleware.ts
import { paymentProxy } from "@x402/next";
import { HTTPFacilitatorClient, x402ResourceServer } from "@x402/core/server";
import { registerExactEvmScheme } from "@x402/evm/exact/server";
import { createPaywall } from "@x402/paywall";
import { evmPaywall } from "@x402/paywall/evm";
const facilitatorUrl = process.env.NEXT_PUBLIC_FACILITATOR_URL!;
const payTo = process.env.RESOURCE_WALLET_ADDRESS as `0x${string}`;
const network = process.env.NETWORK as `${string}:${string}`;
// Create facilitator client and resource server
const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl });
const server = new x402ResourceServer(facilitatorClient);
registerExactEvmScheme(server);
// Create paywall UI (shown to browsers visiting protected pages)
const paywall = createPaywall()
.withNetwork(evmPaywall)
.withConfig({
appName: "My dApp",
appLogo: "/logo.png",
testnet: true, // set false for mainnet
})
.build();
export const middleware = paymentProxy(
{
"/api/payment/:path*": {
accepts: [
{
scheme: "exact",
price: "$0.01",
network,
payTo,
},
],
description: "Access to premium API data",
mimeType: "application/json",
},
"/payment/:path*": {
accepts: [
{
scheme: "exact",
price: "$0.01",
network,
payTo,
},
],
description: "Access to premium content",
mimeType: "text/html",
},
},
server,
undefined, // optional request context
paywall,
);
// IMPORTANT: matcher must cover all protected routes
export const config = {
matcher: ["/api/payment/:path*", "/payment/:path*"],
};
CAIP-2 Network Identifiers
The v2 API uses CAIP-2 network identifiers — format: eip155:{chainId} for EVM chains.
| CAIP-2 ID | Chain | Notes |
|---|---|---|
eip155:84532 |
Base Sepolia | Default for development — Circle faucet for test USDC |
eip155:8453 |
Base | Recommended for production — lowest fees |
Legacy network names (base-sepolia, base, etc.) may still work for backwards compatibility, but prefer CAIP-2 format. For the full list of supported networks, check the x402 docs.
Gotchas & Common Pitfalls
Facilitator is required. x402 doesn't do peer-to-peer payments. The facilitator service verifies signatures and executes settlements. For testnet, https://x402.org/facilitator works without signup. For production, you may need to run your own — check x402 docs.
Register the EVM scheme. The server needs registerExactEvmScheme(server) in middleware.ts. Without this, payment payloads won't be understood.
Payments are in USDC by default. The $0.01 price syntax means USDC.
Don't use hardhat localhost as the network. The facilitator can't verify or settle payments on a local chain. Always use a testnet (eip155:84532) even during development.
The matcher in middleware.ts must cover protected routes. If you add a new protected route in the routes config but forget to add it to matcher, the middleware won't run on that route.
CLI Payment Script
For testing API routes programmatically (without a browser), create a script using @x402/fetch:
// packages/hardhat/scripts/send402request.ts
import { privateKeyToAccount } from "viem/accounts";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
async function main() {
const privateKey = process.env.DEPLOYER_PRIVATE_KEY as `0x${string}`;
if (!privateKey) { console.log("No deployer key. Run `yarn generate` first."); return; }
const signer = privateKeyToAccount(privateKey);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const response = await fetchWithPayment("http://localhost:3000/api/payment/builder", { method: "GET" });
console.log("Response:", await response.json());
}
main().catch(console.error);
Note: Register the EVM scheme on the client side too —
registerExactEvmScheme(client, { signer })from@x402/evm/exact/client.
How to Test
- Set
targetNetworks: [chains.baseSepolia]inscaffold.config.ts - Configure
.env.developmentwith facilitator URL, pay-to address, andNETWORK=eip155:84532 yarn start— visithttp://localhost:3000- Navigate to a protected page — you should see the x402 paywall
- To test API routes:
curl http://localhost:3000/api/payment/buildershould return 402 withPAYMENT-REQUIREDheader - To test paid access:
yarn send402request(needs funded wallet on Base Sepolia — get test USDC from Circle faucet)
Production
- Switch
NETWORKtoeip155:8453(Base mainnet) - Update
scaffold.config.tsto target the mainnet chain - Set
RESOURCE_WALLET_ADDRESSto your production payment receiver - Set
testnet: falsein paywall config
More from scaffold-eth/scaffold-eth-2
solidity-security
Master smart contract security best practices to prevent common vulnerabilities and implement secure Solidity patterns. Use when writing smart contracts, auditing existing contracts, or implementing security measures for blockchain applications.
10defi-protocol-templates
Implement DeFi protocols with production-ready templates for staking, AMMs, governance, and lending systems. Use when building decentralized finance applications or smart contract protocols.
10ponder
Integrate Ponder into a Scaffold-ETH 2 project for blockchain event indexing. Use when the user wants to: index contract events, add a blockchain backend, set up GraphQL for onchain data, use Ponder with SE-2, or build an indexer for their dApp.
7erc-20
Add an ERC-20 token contract to a Scaffold-ETH 2 project. Use when the user wants to: create a fungible token, deploy an ERC-20, add token minting, build a token transfer UI, or work with ERC-20 tokens in SE-2.
6erc-721
Add an ERC-721 NFT contract to a Scaffold-ETH 2 project. Use when the user wants to: create an NFT collection, deploy an ERC-721, add NFT minting, build an NFT gallery or transfer UI, or work with non-fungible tokens in SE-2.
5siwe
Add Sign-In with Ethereum (SIWE) authentication to a Scaffold-ETH 2 project. Use when the user wants to: add wallet-based login, implement SIWE, authenticate users with their Ethereum wallet, add session management with wallet signing, build sign-in with Ethereum, or add Web3 authentication.
5