Skip to main content
This example demonstrates how to make x402 payments using a custom SPL token. This is useful when you want to accept payments in your own token rather than USDC. Source: GitHub › scripts/solana-example/token-payment.ts

When to Use Custom Tokens

Use custom tokens when:
  • You’re building a token-gated service
  • You want to accept payments in your project’s native token
  • You’re testing with test tokens on devnet
  • You have a specific tokenomics model
Use USDC when:
  • You need stable value for payments
  • You want maximum compatibility with existing payment systems
  • You’re building general-purpose payment applications

Full Example

import "dotenv/config";
import { logResponse } from "../logger";
import { Keypair, PublicKey } from "@solana/web3.js";
import { createLocalWallet } from "@faremeter/wallet-solana";
import { createPaymentHandler } from "@faremeter/x-solana-settlement";
import { wrap as wrapFetch } from "@faremeter/fetch";
import { lookupKnownSPLToken } from "@faremeter/info/solana";
import fs from "fs";

const { PAYER_KEYPAIR_PATH } = process.env;

if (!PAYER_KEYPAIR_PATH) {
  throw new Error("PAYER_KEYPAIR_PATH must be set in your environment");
}

const network = "devnet";
const splTokenName = "USDC";

const usdcInfo = lookupKnownSPLToken(network, splTokenName);
if (!usdcInfo) {
  throw new Error(`couldn't look up SPLToken ${splTokenName} on ${network}!`);
}

const keypair = Keypair.fromSecretKey(
  Uint8Array.from(JSON.parse(fs.readFileSync(PAYER_KEYPAIR_PATH, "utf-8"))),
);

const mint = new PublicKey(usdcInfo.address);
const wallet = await createLocalWallet(network, keypair);
const fetchWithPayer = wrapFetch(fetch, {
  handlers: [createPaymentHandler(wallet, mint)],
});

const req = await fetchWithPayer("http://127.0.0.1:3000/protected");

await logResponse(req);

Step-by-Step Breakdown

1. Lookup Token Mint

const network = "devnet";
const splTokenName = "USDC";

const usdcInfo = lookupKnownSPLToken(network, splTokenName);
if (!usdcInfo) {
  throw new Error(`couldn't look up SPLToken ${splTokenName} on ${network}!`);
}

const mint = new PublicKey(usdcInfo.address);
This example uses lookupKnownSPLToken to find USDC, but you can use any mint address:
// Use a known token from @faremeter/info
const tokenInfo = lookupKnownSPLToken("devnet", "USDC");
const mint = new PublicKey(tokenInfo.address);

// Or use any custom mint address
const mint = new PublicKey("YourCustomMintAddressHere");

2. Create Wallet

const wallet = await createLocalWallet(network, keypair);
Create a wallet interface that will sign token transfer transactions.

3. Create Payment Handler with Mint

const fetchWithPayer = wrapFetch(fetch, {
  handlers: [createPaymentHandler(wallet, mint)],
});
Key difference: createPaymentHandler is called with a mint parameter. This tells the handler to use that specific SPL token for payments.

4. Make Payment

const req = await fetchWithPayer("http://127.0.0.1:3000/protected");
The payment handler will:
  1. Create an SPL token transfer instruction
  2. Transfer tokens from your associated token account
  3. Submit the transaction
  4. Retry the original request with payment proof

Using Custom Tokens

Option 1: Create Your Own Token

See Creating a Test Token for instructions on creating a custom SPL token on devnet. Once created, use its mint address:
// Use your custom token
const mint = new PublicKey("YourCustomTokenMintAddress");
const wallet = await createLocalWallet("devnet", keypair);
const fetchWithPayer = wrapFetch(fetch, {
  handlers: [createPaymentHandler(wallet, mint)],
});

Option 2: Use Existing Token

You can use any SPL token mint address:
// Devnet USDC
const mint = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");

// Mainnet USDC
const mint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");

// Any other SPL token
const mint = new PublicKey("YourTokenMintAddress");

Token Payment vs SOL Payment

import { createPaymentHandler } from "@faremeter/x-solana-settlement";

const mint = new PublicKey(tokenInfo.address);
const wallet = await createLocalWallet("devnet", keypair);

// Mint specified = SPL token transfer
const fetchWithPayer = wrapFetch(fetch, {
  handlers: [createPaymentHandler(wallet, mint)],
});
Key Differences:
  • Token: Requires associated token accounts, specific token mint
  • SOL: No token accounts needed, simpler but price volatile

Prerequisites

Before making token payments:
  1. Token must exist: The mint must be created on the network
  2. Token account exists: Your wallet needs an associated token account for the mint
  3. Sufficient balance: Your token account must have enough tokens for the payment
The payment handler will automatically:
  • Create associated token accounts if needed (if you provide a connection)
  • Check balances
  • Handle account creation automatically

Environment Variables

  • PAYER_KEYPAIR_PATH: Path to your Solana keypair JSON file