This guide shows you how to manually integrate X402 payments into your frontend application without using middleware. This approach gives you complete control over the payment flow and is ideal for custom implementations.Documentation Index
Fetch the complete documentation index at: https://docs.mrdn.finance/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
Before starting, ensure you have:- A React/Next.js application
- Wallet connection functionality (MetaMask, WalletConnect, etc.)
- Access to a Meridian facilitator service
- Required dependencies installed
Installation
Install the necessary packages:npm install viem axios x402/client x402/types
# or
yarn add viem axios x402/client x402/types
# or
pnpm add viem axios x402/client x402/types
Environment Setup
Configure your environment variables:# .env.local
NEXT_PUBLIC_MERIDIAN_PK=pk_...
NEXT_PUBLIC_FACILITATOR_URL=https://api.mrdn.finance/v1
Core Implementation
1. Wallet Connection Setup
First, set up wallet connection functionality:import { createWalletClient, custom, WalletClient } from "viem";
import { baseSepolia } from "viem/chains";
// Extend Window interface for wallet access
interface ExtendedWindow extends Window {
ethereum?: {
request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
on: (event: string, callback: (...args: unknown[]) => void) => void;
removeListener: (
event: string,
callback: (...args: unknown[]) => void
) => void;
};
}
const getExtendedWindow = (): ExtendedWindow | null => {
if (typeof window !== "undefined") {
return window as ExtendedWindow;
}
return null;
};
const connectWallet = async () => {
const extendedWindow = getExtendedWindow();
if (extendedWindow?.ethereum) {
try {
// Request account access
const accounts = (await extendedWindow.ethereum.request({
method: "eth_requestAccounts",
})) as string[];
if (accounts.length > 0) {
const account = accounts[0] as `0x${string}`;
// Create wallet client with browser wallet
const walletClient = createWalletClient({
account,
transport: custom(extendedWindow.ethereum),
chain: baseSepolia,
});
return { account, walletClient };
}
} catch (error) {
console.error("Error connecting to wallet:", error);
throw error;
}
} else {
throw new Error("Please install MetaMask or another wallet extension");
}
};
2. Authentication Headers
Create functions to generate authentication headers:// Function to create Bearer Auth header with API key
const createAuthHeader = () => {
const apiKey = process.env.NEXT_PUBLIC_MERIDIAN_PK;
if (!apiKey) {
console.warn("API key not configured. Requests may fail authentication.");
return {};
}
return {
Authorization: `Bearer ${apiKey}`,
};
};
// Function to create headers for forwarding to x402 middleware
const createForwardingHeaders = () => {
const apiKey = process.env.NEXT_PUBLIC_MERIDIAN_PK;
if (!apiKey) {
console.warn("API key not configured for forwarding");
return {};
}
return {
Authorization: `Bearer ${apiKey}`,
};
};
// Function to create Meridian-specific forwarding headers
const createMeridianForwardingHeaders = () => {
const meridianPk = process.env.NEXT_PUBLIC_MERIDIAN_PK;
if (!meridianPk) {
console.warn("Meridian public key not configured, using default API key");
return createForwardingHeaders();
}
return {
Authorization: `Bearer ${meridianPk}`,
};
};
3. Making API Requests
Implement the core API request logic with payment handling:import axios from "axios";
import { createPaymentHeader, selectPaymentRequirements } from "x402/client";
import { ChainIdToNetwork, PaymentRequirementsSchema } from "x402/types";
interface PaymentRequirements {
amount: string;
recipient: string;
network: string;
scheme: string;
maxAmountRequired: string;
resource: string;
mimeType: string;
payTo: string;
maxTimeoutSeconds: number;
asset: string;
}
const requestSample = async (client: WalletClient, account: string) => {
try {
// Make a regular API request to the facilitator with auth headers
const response = await axios.get("http://localhost:4021/v1/samples", {
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_MERIDIAN_PK}`,
},
});
console.log("Sample response:", response.data);
// Check if payment is required
if (response.status === 402) {
// Handle payment manually
console.log("Payment required:", response.data.accepts[0]);
await handlePayment(
response.data.accepts[0],
response.data.x402Version,
response.data.accepts,
client,
account
);
}
return response.data;
} catch (error) {
console.error("Error requesting sample:", error);
if (axios.isAxiosError(error) && error.response?.status === 402) {
// Handle payment requirement
console.log("Payment required:", error.response.data.accepts[0]);
await handlePayment(
error.response.data.accepts[0],
error.response.data.x402Version,
error.response.data.accepts,
client,
account
);
} else {
throw error;
}
}
};
4. Payment Handling
Implement the payment processing logic:const handlePayment = async (
paymentDetails: PaymentRequirements,
x402Version: number,
accepts: PaymentRequirements[],
client: WalletClient,
account: string
) => {
try {
const parsed = accepts.map((x) => PaymentRequirementsSchema.parse(x));
// Get chain ID from the client's chain property
const chainId = client.chain?.id;
const selectedPaymentRequirements = selectPaymentRequirements(
parsed,
chainId ? ChainIdToNetwork[chainId] : undefined,
"exact"
);
console.log("selectedPaymentRequirements", selectedPaymentRequirements);
const paymentHeader = await createPaymentHeader(
client as unknown as Parameters<typeof createPaymentHeader>[0],
x402Version,
selectedPaymentRequirements
);
console.log("Payment header:", paymentHeader);
// Send the payment header to our facilitator
const facilitatorResponse = await axios.get(
"http://localhost:4021/v1/settle",
{
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_MERIDIAN_PK}`,
"X-PAYMENT": paymentHeader,
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE",
},
}
);
console.log("Facilitator response:", facilitatorResponse.data);
// Request the sample again with the payment header
const response = await axios.get("http://localhost:4021/v1/samples", {
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_MERIDIAN_PK}`,
"X-PAYMENT": paymentHeader,
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE",
},
});
console.log("Sample response:", response.data);
return response.data;
} catch (error: unknown) {
console.error("Payment error:", error);
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
throw new Error("Payment failed: " + errorMessage);
}
};