You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
1. Create an API route and add Thirdweb x402 facilitator
Open or create an API folder
Create a route.ts file
Copy code from Thirdweb --> x402 Payment
Change the network name and asset to USDC on Celo
or
Paste the following prompt:
Create a Next.js API route handler (app/api/route.ts) that implements x402 payment processing using Thirdweb. The route should:
Import the following from thirdweb:
createThirdwebClient from "thirdweb"
facilitator and settlePayment from "thirdweb/x402"
celo from "thirdweb/chains"
Create a Thirdweb client using THIRDWEB_SECRET_KEY from environment variables
Create a facilitator instance using:
The client created above
THIRDWEB_SERVER_WALLET_ADDRESS from environment variables
Export an async GET function that:
Extracts the x-payment header from the request
Calls settlePayment with:
resourceUrl: "http://localhost:3000/api"
method: "GET"
paymentData: the value from the x-payment header
payTo: THIRDWEB_SERVER_WALLET_ADDRESS from environment variables
network: celo
price: object with amount "10000" and asset object containing address "0xcebA9300f2b948710d2653dD7B07f33A8B32118C" and decimals 6
facilitator: the facilitator instance created above
If the result status is 200, return a JSON response with { data: "you unlocked the secret content" }
Otherwise, return the result.responseBody with the result status and response headers
Use TypeScript and Next.js App Router conventions. Use non-null assertions (!) for environment variables.
Create a Next.js client component (app/page.tsx) for x402 payments with Thirdweb:
Imports: React hooks, Button from "@/components/ui/button", ConnectButton/useActiveWallet from "thirdweb/react", Wallet type, wrapFetchWithPayment from "thirdweb/x402", and client from "@/lib/thirdweb-client"
Async function accessSecretContent: Takes a Wallet, uses wrapFetchWithPayment(fetch, client, wallet, { maxValue: BigInt(10000) }) to call "http://localhost:3000/api", handles 402 and other errors, returns JSON data
Home component:
State: mounted, loading, error, success
useEffect sets mounted to true; return null if not mounted
handlePay calls accessSecretContent, updates state, handles errors
Emoji: "🔓" if success, "🔒" otherwise
UI: Centered layout (min-h-screen flex items-center justify-center)
Large emoji (text-8xl) at top
If no wallet: show "Connect your wallet" + ConnectButton
If wallet: show Button (disabled when loading/unlocked) with text "Processing..." / "Unlocked!" / "Pay to access secret content"
Show error/success messages below button
Use TypeScript, Tailwind CSS, and "use client" directive.
**RESULT: **
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { ConnectButton, useActiveWallet } from "thirdweb/react";
import { Wallet } from "thirdweb/wallets";
import { wrapFetchWithPayment } from "thirdweb/x402";
import { client } from "@/lib/thirdweb-client";
async function accessSecretContent(wallet: Wallet) {
try {
const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet, {
maxValue: BigInt(10000),
});
console.log("Making payment request...");
const response = await fetchWithPay("http://localhost:3000/api");
console.log("Response status:", response.status);
// wrapFetchWithPayment should handle 402 automatically and retry after payment
// If we get here, the payment should have been processed
if (response.status === 402) {
// This shouldn't happen if wrapFetchWithPayment is working correctly
// but if it does, it means payment wasn't completed
const errorData = await response.json().catch(() => ({}));
throw new Error(`Payment required but not completed. ${JSON.stringify(errorData)}`);
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`HTTP error! status: ${response.status}, ${JSON.stringify(errorData)}`);
}
const data = await response.json();
console.log("Success:", data);
return data;
} catch (error) {
console.error("Error accessing secret content:", error);
throw error;
}
}
export default function Home() {
const wallet = useActiveWallet();
const [mounted, setMounted] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null; // Prevent hydration mismatch
}
const handlePay = async () => {
if (!wallet) return;
setLoading(true);
setError(null);
setSuccess(null);
try {
const data = await accessSecretContent(wallet);
setSuccess(data.data || "Payment successful!");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to process payment");
} finally {
setLoading(false);
}
};
const isUnlocked = success !== null;
const emoji = isUnlocked ? "🔓" : "🔒";
return (
<div className="min-h-screen flex items-center justify-center p-8">
<div className="flex flex-col items-center space-y-6">
{/* Emoji */}
<div className="text-8xl animate-in fade-in duration-300">
{emoji}
</div>
{/* Content based on wallet connection */}
{!wallet ? (
<div className="flex flex-col items-center space-y-4">
<p className="text-lg text-muted-foreground">Connect your wallet to continue</p>
<ConnectButton client={client} />
</div>
) : (
<>
{/* Button */}
<Button
onClick={handlePay}
disabled={loading || isUnlocked}
size="lg"
className="min-w-[200px]"
>
{loading ? "Processing..." : isUnlocked ? "Unlocked!" : "Pay to access secret content"}
</Button>
{/* Status messages */}
{error && (
<div className="text-red-500 text-sm text-center max-w-md">
Error: {error}
</div>
)}
{success && (
<div className="text-green-500 text-sm text-center max-w-md">
{success}
</div>
)}
</>
)}
</div>
</div>
);
}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Building an API Payment Gate with x402
What You'll Build
Pre-requisites
NOTE: If using Celo mainnet, you'll need to be on a starter plan, not the free tier
Setup
1. Create a new next.js app
Option 1: Use Celo Composer to get a full mono repo
NOTE: This is a useful starting point if you plan to write smart contracts
Option 2: Create a regular next.js app
(Optional) Use Shadcn for nicer UI
2. Connect Thirdweb account to app
Building the app
1. Create an API route and add Thirdweb x402 facilitator
or
**RESULT: **
2. Update your frontend to handle the payment request
or
**RESULT: **
All reactions