Documentation Index
Fetch the complete documentation index at: https://docs.skale.space/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows how to make x402 payments from the buyer’s perspective. Use the SDK to handle payment flows, interact with paywalled resources, and process 402 Payment Required responses.
Prerequisites
- Node.js and npm installed
- A SKALE Chain endpoint
- Understanding of x402 protocol
- Familiarity with TypeScript
- A wallet with funds (USDC or supported token)
Overview
When accessing a paywalled resource, the flow is:
- Make a request to the protected endpoint
- Receive a
402 Payment Required response with payment requirements
- Create and sign a payment authorization
- Retry the request with the payment signature header
Environment Setup
Create a .env file with the following variables:
# Your wallet private key (never commit this!)
PRIVATE_KEY=0xYourPrivateKey
# USDC token contract on SKALE
PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD
PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)"
Implementation
Coinbase SDK
Faremeter SDK
Step 1: Install Dependencies
npm install @x402/core @x402/evm viem dotenv
Step 2: Define Your Chain
Create a chain configuration for SKALE:import { defineChain } from "viem";
export const skaleChain = defineChain({
id: 324705682,
name: "SKALE Base Sepolia",
nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" },
rpcUrls: {
default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] },
},
});
Step 3: Create the Payment Client
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import "dotenv/config";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
// Create the x402 client with EVM scheme
const evmScheme = new ExactEvmScheme(account);
const coreClient = new x402Client().register("eip155:*", evmScheme);
const httpClient = new x402HTTPClient(coreClient);
Step 4: Access Paywalled Resources
async function accessResource(url: string): Promise<unknown> {
// Make initial request
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
// If no payment required, return data
if (response.status !== 402) {
return response.json();
}
// Get payment requirements from response
const responseBody = await response.json();
const paymentRequired = httpClient.getPaymentRequiredResponse(
(name: string) => response.headers.get(name),
responseBody
);
// Create payment payload (signs the authorization)
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
// Get payment headers to send with retry request
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
// Retry request with payment
const paidResponse = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
...paymentHeaders,
},
});
if (!paidResponse.ok) {
throw new Error(`Payment failed: ${paidResponse.status}`);
}
return paidResponse.json();
}
Complete Working Example
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient, http, formatEther } from "viem";
import { defineChain } from "viem";
import "dotenv/config";
export const skaleChain = defineChain({
id: 324705682,
name: "SKALE Base Sepolia",
nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" },
rpcUrls: {
default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] },
},
});
async function main() {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("PRIVATE_KEY environment variable is required");
}
// Setup wallet
const account = privateKeyToAccount(privateKey as `0x${string}`);
console.log(`Wallet address: ${account.address}`);
// Setup x402 client
const evmScheme = new ExactEvmScheme(account);
const coreClient = new x402Client().register("eip155:*", evmScheme);
const httpClient = new x402HTTPClient(coreClient);
// Access paywalled resource
const url = "http://localhost:3000/premium/data";
try {
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (response.status === 402) {
console.log("Payment required, processing...");
const responseBody = await response.json();
const paymentRequired = httpClient.getPaymentRequiredResponse(
(name: string) => response.headers.get(name),
responseBody
);
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
const paidResponse = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
...paymentHeaders,
},
});
const data = await paidResponse.json();
console.log("Premium data:", data);
} else {
const data = await response.json();
console.log("Data:", data);
}
} catch (error) {
console.error("Error:", error);
}
}
main();
Run the Client
Step 1: Install Dependencies
npm install @faremeter/fetch @faremeter/wallet-evm @faremeter/payment-evm viem dotenv
Step 2: Define Your Chain
Create a chain configuration for SKALE:import { defineChain } from "viem";
export const skaleChain = defineChain({
id: 324705682,
name: "SKALE Base Sepolia",
nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" },
rpcUrls: {
default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] },
},
});
Step 3: Create the Payment-Enabled Fetch
The Faremeter SDK wraps the native fetch function to automatically handle 402 responses:import { createLocalWallet } from "@faremeter/wallet-evm";
import { createPaymentHandler } from "@faremeter/payment-evm/exact";
import { wrap as wrapFetch } from "@faremeter/fetch";
import "dotenv/config";
const wallet = await createLocalWallet(skaleChain, process.env.PRIVATE_KEY!);
const fetchWithPayment = wrapFetch(fetch, {
handlers: [
createPaymentHandler(wallet, {
asset: {
address: process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`,
contractName: process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)",
forwarderName: process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)",
forwarderVersion: "1",
},
}),
],
});
Step 4: Access Paywalled Resources
// The wrapped fetch automatically handles 402 responses
const response = await fetchWithPayment("http://localhost:3000/premium/data", {
method: "GET",
headers: { "Content-Type": "application/json" },
});
const data = await response.json();
console.log("Premium data:", data);
Complete Working Example
import { createLocalWallet } from "@faremeter/wallet-evm";
import { createPaymentHandler } from "@faremeter/payment-evm/exact";
import { wrap as wrapFetch } from "@faremeter/fetch";
import { createPublicClient, http, formatEther, defineChain } from "viem";
import "dotenv/config";
export const skaleChain = defineChain({
id: 324705682,
name: "SKALE Base Sepolia",
nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" },
rpcUrls: {
default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] },
},
});
async function main() {
const privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
throw new Error("PRIVATE_KEY environment variable is required");
}
// Setup wallet
const wallet = await createLocalWallet(skaleChain, privateKey);
console.log(`Wallet address: ${wallet.account.address}`);
// Setup payment-enabled fetch
const assetAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`;
const assetName = process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)";
const fetchWithPayment = wrapFetch(fetch, {
handlers: [
createPaymentHandler(wallet, {
asset: {
address: assetAddress,
contractName: assetName,
forwarderName: assetName,
forwarderVersion: "1",
},
}),
],
});
// Access paywalled resource - payment handled automatically!
const url = "http://localhost:3000/premium/data";
try {
console.log(`Accessing ${url}...`);
const response = await fetchWithPayment(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log("Premium data:", data);
} catch (error) {
console.error("Error:", error);
}
}
main();
Run the Client
POST Requests with Body
The wrapped fetch works the same way for POST requests:const response = await fetchWithPayment("http://localhost:3000/premium/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "analyze this data" }),
});
const result = await response.json();
console.log("Analysis result:", result);
Testing Your Client
Make sure you have a server running (see Accept Payments), then run your client:
The client will:
- Make a request to the protected endpoint
- Receive the 402 response with payment requirements
- Sign a payment authorization using your wallet
- Retry the request with the payment header
- Receive the premium content
Best Practices
- Use Environment Variables: Never hardcode private keys
- Handle Errors Gracefully: Always wrap payment logic in try-catch
- Check Balances: Ensure wallet has sufficient funds before making payments
- Log Transactions: Keep track of payments for debugging and accounting
Security Considerations
- Never expose private keys in client-side code
- Use secure key management (environment variables, secrets manager)
- Validate payment amounts before approving
- Consider implementing spending limits
Resources