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:Copy
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:Copy
# .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:Copy
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:Copy
// 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:Copy
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:Copy
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);
}
};