Skip to main content

Make Payments

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:
  1. Make a request to the protected endpoint
  2. Receive a 402 Payment Required response with payment requirements
  3. Create and sign a payment authorization
  4. 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

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

npx tsx client.ts

Testing Your Client

Make sure you have a server running (see Accept Payments), then run your client:
npx tsx client.ts
The client will:
  1. Make a request to the protected endpoint
  2. Receive the 402 response with payment requirements
  3. Sign a payment authorization using your wallet
  4. Retry the request with the payment header
  5. Receive the premium content

Best Practices

  1. Use Environment Variables: Never hardcode private keys
  2. Handle Errors Gracefully: Always wrap payment logic in try-catch
  3. Check Balances: Ensure wallet has sufficient funds before making payments
  4. 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

Next Steps

Resources