Quickstart: Merchant
Accept your first USDC payment from an AI agent in under 10 minutes.
This tutorial walks you through creating a paywalled API endpoint that accepts USDC payments from AI agents. You'll have a working server accepting payments on Solana devnet by the end.
Prerequisites
- Node.js 22+ (
node --version) - A Solana wallet with a devnet address (Phantom, Solflare, or
solana-keygen) - A PincerPay account with an API key (sign up)
Step 1: Create the project
mkdir my-merchant && cd my-merchant
npm init -y
npm install @pincerpay/merchant express
npm install -D tsx typescript @types/express
Create a .env file:
PINCERPAY_API_KEY=pp_live_your_api_key_here
MERCHANT_ADDRESS=YourSolanaWalletAddress
Step 2: Write the server
Create server.ts:
import express from "express";
import { pincerpay } from "@pincerpay/merchant";
const app = express();
// Add PincerPay middleware — this intercepts requests and returns
// 402 Payment Required for paywalled routes
app.use(
pincerpay({
apiKey: process.env.PINCERPAY_API_KEY!,
merchantAddress: process.env.MERCHANT_ADDRESS!,
routes: {
"GET /api/weather": {
price: "0.001",
chain: "solana-devnet",
description: "Current weather data",
},
},
})
);
// Free endpoint — no paywall
app.get("/api/health", (_req, res) => {
res.json({ status: "ok" });
});
// Paywalled endpoint — middleware handles 402/settlement
app.get("/api/weather", (_req, res) => {
res.json({
temperature: 72,
conditions: "sunny",
location: "San Francisco",
timestamp: new Date().toISOString(),
});
});
app.listen(3001, () => {
console.log("Merchant running at http://localhost:3001");
console.log(" GET /api/health — free");
console.log(" GET /api/weather — 0.001 USDC (Solana Devnet)");
});
Step 3: Run the server
npx tsx --env-file=.env server.ts
Expected output:
Merchant running at http://localhost:3001
GET /api/health — free
GET /api/weather — 0.001 USDC (Solana Devnet)
Step 4: Test with curl
The free endpoint returns data directly:
curl http://localhost:3001/api/health
# {"status":"ok"}
The paywalled endpoint returns 402 Payment Required with payment instructions:
curl -i http://localhost:3001/api/weather
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 2,
"accepts": [{
"scheme": "exact",
"network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
"amount": "1000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "YourSolanaWalletAddress",
"description": "Current weather data"
}]
}
This is the x402 protocol in action. The amount is in USDC base units (6 decimals), so 1000 = 0.001 USDC.
Step 5: Test with the Agent SDK
In a second terminal, create a test agent:
mkdir my-agent && cd my-agent
npm init -y
npm install @pincerpay/agent
npm install -D tsx typescript
Create agent.ts:
import { PincerPayAgent } from "@pincerpay/agent";
async function main() {
const agent = await PincerPayAgent.create({
chains: ["solana-devnet"],
solanaPrivateKey: process.env.AGENT_SOLANA_KEY!,
policies: [
{
maxPerTransaction: "100000", // 0.10 USDC max per payment
maxPerDay: "1000000", // 1.00 USDC max per day
},
],
});
console.log(`Agent address: ${agent.solanaAddress}`);
// agent.fetch() handles the 402 flow automatically
const response = await agent.fetch("http://localhost:3001/api/weather");
const data = await response.json();
console.log("Weather data:", data);
}
main().catch(console.error);
Fund the agent's Solana address with devnet SOL and USDC:
- SOL:
solana airdrop 1 <agent-address> --url devnet - USDC: Use the Circle faucet (select Solana, Devnet, USDC)
Run the agent:
AGENT_SOLANA_KEY=your_base58_private_key npx tsx agent.ts
Expected output:
Agent address: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
Weather data: {
"temperature": 72,
"conditions": "sunny",
"location": "San Francisco",
"timestamp": "2026-03-02T12:00:00.000Z"
}
The agent automatically detected the 402, signed a USDC transfer, submitted it to the facilitator, and retried the request with proof of payment.
Hono Variant
Prefer Hono over Express? The middleware API is nearly identical:
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { pincerpayHono } from "@pincerpay/merchant";
const app = new Hono();
app.use(
"*",
pincerpayHono({
apiKey: process.env.PINCERPAY_API_KEY!,
merchantAddress: process.env.MERCHANT_ADDRESS!,
routes: {
"GET /api/weather": {
price: "0.001",
chain: "solana-devnet",
description: "Current weather data",
},
},
})
);
app.get("/api/weather", (c) => c.json({ temp: 72, condition: "sunny" }));
serve({ fetch: app.fetch, port: 3001 });
Install hono and @hono/node-server instead of express.
Troubleshooting
402 response is empty or missing accepts: Check that your PINCERPAY_API_KEY is valid. An invalid key will cause the middleware to skip paywall logic.
Agent gets "insufficient balance": Fund the agent's Solana address with devnet USDC via the Circle faucet.
Agent gets "policy violation": The payment amount exceeds your maxPerTransaction or maxPerDay policy. Increase the limits or lower the price.
Next Steps
- Interactive Demo to see what agents experience when hitting your paywalled API
- Merchant SDK Reference for the full configuration API
- Next.js Example for App Router integration with Hono
- Express Example for a standalone Express server with multiple price tiers
- Testing Guide for devnet/testnet setup