Event RSVP (Events Concierge)
A production-ready reference implementation showing how AI agents can autonomously pay for MCP tool calls using smart wallets, Cloudflare Durable Objects, and the x402 payment protocol.
This is the most comprehensive production example in the repository, demonstrating advanced patterns for multi-tenant agent systems with real on-chain payments.
Overview
The Events Concierge enables users to create paid events and allows AI agents to autonomously RSVP by making USDC payments on Base Sepolia. Unlike simpler demos, this implements:
Per-user isolation using Cloudflare Durable Objects
Multi-tenant architecture with separate wallets per host
Production-grade payment verification with ERC-6492 and EIP-1271
Stateful agent connections maintained across requests
Real blockchain settlements on Base Sepolia
What Makes This Different
Real Payments Actual on-chain USDC transactions on Base—no mocks, no simulations
Multi-Tenant Each user gets their own MCP server instance with isolated wallet and data
Stateful Agents Durable Objects maintain connection state, eliminating race conditions
Production Patterns Error handling, retry logic, signature verification, and deployment strategies
Architecture
Core Technologies
1. Cloudflare Durable Objects
Durable Objects provide single-threaded, stateful mini-servers per user:
Host Agent (Per-User Instance)
Guest Agent (Shared Instance)
export class Host extends DurableObject {
private wallet : Wallet ;
private server : McpServer ;
private userScopeId : string ;
constructor ( ctx : DurableObjectState , env : Env ) {
super ( ctx , env );
// Extract user ID from DO name
const headers = new Headers ( request . headers );
this . userScopeId = headers . get ( "x-user-scope-id" );
// Each user gets their own wallet
this . wallet = await createHostWallet ( env , this . userScopeId );
// Each user gets their own MCP server
this . server = new McpServer ({ name: `Events- ${ this . userScopeId } ` })
. withX402 ({ wallet: this . wallet , ... });
}
}
Why Durable Objects?
Need Regular Worker Durable Object MCP connection state Lost between requests Persists in memory WebSocket support Limited Built-in Per-user isolation Shared instance Unique per ID Coordination Race conditions Single-threaded
2. MCP with x402 Payments
MCP (Model Context Protocol) allows agents to call tools. x402 adds payment requirements:
Define Paid Tools
Auto-Pay on 402
// Host defines what tools require payment
this . server . paidTool (
"rsvpToEvent" ,
"RSVP to a paid event" ,
0.05 , // Price in USD
{ eventId: z . string () },
{},
async ({ eventId }, paymentTx : TransactionReceipt ) => {
// Only executed AFTER payment verification!
const event = await eventService . getEvent ( eventId );
await eventService . recordRsvp ({
eventId ,
guestWallet: paymentTx . from ,
txHash: paymentTx . hash ,
amount: "50000" // 0.05 USDC (6 decimals)
});
return {
success: true ,
event ,
transactionHash: paymentTx . hash
};
}
);
3. Crossmint Smart Wallets
Crossmint wallets work before deployment using ERC-6492 signatures:
// Create wallet (not deployed yet!)
const wallet = await crossmintWallets . createWallet ({
chain: "base-sepolia" ,
signer: { type: "api-key" }
});
console . log ( wallet . address ); // 0x123... (counterfactual address)
// Can sign payments before deployment
const signature = await wallet . signTypedData ({
domain: { chainId: 84532 , ... },
types: { Payment: [ ... ] },
message: { amount: "50000" , to: "0xabc..." , ... }
});
// Returns ERC-6492 signature (includes deployment bytecode)
// First payment auto-deploys the wallet
const tx = await facilitator . settle ( signature );
// Wallet is now deployed and owns itself!
Signature Standards:
Stage Standard How It Works Pre-deployed ERC-6492 Signature includes deployment bytecode. Verifier simulates deployment. Deployed EIP-1271 Contract’s isValidSignature() validates signatures.
4. Per-User Data Isolation
KV storage is scoped by user ID:
// User registration creates URL-safe hash
const urlSafeId = await hashUserId ( email ); // "a3f2c1b4..."
// Store user mapping
await env . SECRETS . put ( `users: ${ email } ` , JSON . stringify ({
userId: email ,
walletAddress: wallet . address ,
urlSafeId
}));
await env . SECRETS . put ( `usersByHash: ${ urlSafeId } ` , ... );
// Events scoped by urlSafeId
await env . SECRETS . put (
` ${ urlSafeId } :events: ${ eventId } ` ,
JSON . stringify ( eventData )
);
// Revenue tracking
await env . SECRETS . put (
` ${ urlSafeId } :revenue` ,
totalRevenue . toString ()
);
Routing:
User A → /mcp/users/a3f2c1b4 → Host DO (name: "a3f2c1b4")
├─ wallet: 0xAAA...
├─ events: [event1, event2]
└─ revenue: $12.50
User B → /mcp/users/b8e9d6f1 → Host DO (name: "b8e9d6f1")
├─ wallet: 0xBBB...
├─ events: [event3]
└─ revenue: $3.00
Payment Flow Deep Dive
Guest Calls Paid Tool
await x402Client . callTool ( onPaymentRequired , {
name: "rsvpToEvent" ,
arguments: { eventId: "event-123" }
});
Host Returns 402
{
"statusCode" : 402 ,
"payment" : {
"amount" : "50000" ,
"currency" : "USDC" ,
"to" : "0xHostWallet..." ,
"chainId" : 84532 ,
"facilitator" : "https://x402.org/facilitator"
}
}
Guest Confirms Payment
User sees modal with payment details and clicks “Approve”.
Guest Signs EIP-712
const signature = await wallet . signTypedData ({
domain: { name: "x402 Payment" , version: "1" , chainId: 84532 },
types: {
Payment: [
{ name: "amount" , type: "uint256" },
{ name: "currency" , type: "address" },
{ name: "to" , type: "address" }
]
},
message: {
amount: "50000" ,
currency: "0x036CbD53842c5426634e7929541eC2318f3dCF7e" ,
to: "0xHostWallet..."
}
});
Guest Retries with Signature
await x402Client . callTool ( onPaymentRequired , {
name: "rsvpToEvent" ,
arguments: { eventId: "event-123" }
}, {
headers: { "X-PAYMENT" : signature }
});
Facilitator Verifies + Settles
Verify signature matches message
Check guest has USDC balance
Submit transferFrom transaction
Return TX hash
Host Records RSVP
await eventService . recordRsvp ({
eventId: "event-123" ,
guestWallet: "0xGuestWallet..." ,
txHash: "0xabc...def" ,
amount: "50000"
});
await incrementRevenue ( "50000" );
Guest Receives Confirmation
{
"success" : true ,
"event" : { "id" : "event-123" , "title" : "Web3 Meetup" },
"transactionHash" : "0xabc...def" ,
"message" : "RSVP confirmed! Paid 0.05 USDC."
}
Key Features
// List events (no payment required)
this . server . tool (
"listEvents" ,
"List all available events" ,
{},
{},
async () => {
const events = await eventService . listEvents ();
return { events };
}
);
// RSVP requires $0.05 payment
this . server . paidTool (
"rsvpToEvent" ,
"RSVP to an event" ,
0.05 ,
{ eventId: z . string () },
{},
async ({ eventId }, tx ) => {
// Payment already verified!
await recordRsvp ( eventId , tx );
}
);
// Get event stats for $0.01
this . server . paidTool (
"getEventAnalytics" ,
"Get detailed event analytics" ,
0.01 ,
{ eventId: z . string () },
{},
async ({ eventId }) => {
const rsvps = await getRsvps ( eventId );
return {
totalRsvps: rsvps . length ,
totalRevenue: rsvps . reduce (( sum , r ) => sum + parseFloat ( r . amount ), 0 ),
rsvps
};
}
);
Production Deployment
Create KV Namespace
npx wrangler kv:namespace create "SECRETS"
# Copy the ID to wrangler.toml
Set Secrets
npx wrangler secret put OPENAI_API_KEY
npx wrangler secret put CROSSMINT_API_KEY
Configure Durable Objects
# wrangler.toml
[[ durable_objects . bindings ]]
name = "Host"
class_name = "Host"
script_name = "events-concierge"
[[ durable_objects . bindings ]]
name = "Guest"
class_name = "Guest"
script_name = "events-concierge"
Deploy
npm run deploy
# App live at https://events-concierge.YOUR-SUBDOMAIN.workers.dev
Switch to Mainnet
Update src/constants.ts: export const CHAIN_ID = 8453 ; // Base mainnet
export const NETWORK = "base" ;
export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" ;
Redeploy:
Use Cases
Paid API Access Monetize MCP tools without subscription models. Pay-per-use for data, AI inference, or compute.
Event Ticketing Sell event tickets through AI agents. Automated RSVP management with on-chain receipts.
Premium Features Unlock advanced agent capabilities with micropayments. Free tier + paid upgrades.
Agent Marketplaces Build platforms where agents discover and pay for third-party tools autonomously.
Learn More
Source Code View the complete implementation
Durable Objects Learn about stateful agent architecture
MCP Protocol Explore the Model Context Protocol
x402 Spec Understand HTTP payment protocol