Jak zacząć korzystać z x402 na Solanie

Czym jest x402?

x402 to otwarty protokół do płatności natywnych dla internetu. Kod błędu 402 oznacza „Wymagana płatność“ i od dawna istniał w specyfikacji HTTP, jednak dopiero teraz jego praktyczne zastosowanie stało się możliwe dzięki rozwojowi sieci blockchain. Protokół 402 odnosi się do implementacji wzorca HTTP 402 Payment Required: serwer wymaga płatności przed zwróceniem chronionej odpowiedzi. Na Solanie jest to zazwyczaj realizowane poprzez poproszenie klienta o wykonanie małego przelewu, po czym serwer weryfikuje go onchain i udostępnia treść.

W tej chwili nie jest jeszcze jasne, który z pakietów SDK dla protokołu 402 okaże się najpopularniejszy. Dlatego w tym przewodniku pokażemy, jak wdrożyć x402 przy użyciu minimalnego serwera i klienta, oraz wymienimy wszystkie dostępne pakiety SDK dla protokołu 402 wraz z opisem ich aktualnego wsparcia dla Solany.

Jak to działa?

Istnieje kilka sposobów implementacji x402 — od bardzo uproszczonych po w pełni zarządzane.

Idea protokołu: Użyj zwykłego HTTP. Klient odpytuje Twój URL → odpowiadasz kodem 402 Payment Required z obiektem JSON zawierającym wymagania płatności → klient dokonuje płatności i ponawia żądanie z nagłówkiem X-PAYMENT → weryfikujesz/rozliczasz → odpowiadasz 200 OK. Bez kont, bez OAuth.

Diagram przepływu x402Diagram przepływu x402

Uwaga: Fasilitator jest całkowicie opcjonalny — możesz też zaimplementować własną logikę walidacji za pomocą kilku linii kodu. Fasilitator ukrywa szczegóły integracji z blockchainem po stronie serwera i klienta, co ułatwia implementację.

Elementy specyfikacji, które warto znać: Struktura PaymentRequirements, nagłówek X-PAYMENT zakodowany w base64, opcjonalny nagłówek X-PAYMENT-RESPONSE przy sukcesie oraz opcjonalne API fasilitatora dla /verify, /settle, /supported. Aktualnym konkretnym schematem jest exact (zapłać określoną kwotę). Inne, takie jak upto, są w fazie propozycji.

Wsparcie dla Solany: Sam protokół jest niezależny od sieci blockchain; na Solanie obsługuje wszystkie tokeny SPL. Wsparcie dla Solany jest dostępne lub w trakcie opracowania dla większości pakietów SDK protokołu 402.

Poniżej znajduje się lista dostępnych pakietów SDK dla protokołu 402 wraz z ich aktualnym wsparciem dla Solany.

Przypadki użycia

x402 umożliwia szeroki zakres scenariuszy mikropłatności i płatności za użycie, które wcześniej nie były ekonomicznie wykonalne bez blockchainu. Wyobraź sobie Netflix płacący za każde obejrzenie lub Spotify płacący za każdą piosenkę zamiast pobierać opłaty za subskrypcje. Oto kilka możliwych pomysłów. Jednak cały wachlarz możliwości jest otwarty na wyobraźnię:

AI i handel agentowy:

  • Dostęp do API agentów AI: Płatność za każde wywołanie modelu LLM, generowanie obrazów lub wywołanie API modelu AI (zob. przykład ACK)
  • Monetyzacja serwerów MCP: Pobieranie opłat za narzędzia Model Context Protocol, źródła danych i wyspecjalizowane możliwości agentów (zob. MCPay.tech)
  • Płatności agent-do-agenta: Umożliwienie autonomicznym agentom dokonywania transakcji między sobą za usługi i dane (zob. przykład a2a-x402)
  • Dane treningowe premium dla AI: Sprzedaż dostępu do wyselekcjonowanych zbiorów danych w modelu płatności za zapytanie

Treści i media:

  • Artykuły z paywallem: Pobieranie mikropłatności za artykuł zamiast pełnych subskrypcji
  • Streaming wideo/audio: Płatność za obejrzenie lub za minutę treści
  • Obrazy w wysokiej rozdzielczości: Odblokowanie pobrań w pełnej rozdzielczości po dokonaniu płatności (zob. przykład ACK) lub przykład x402 coinbase
  • Dostęp do newsletterów premium: Monetyzacja poszczególnych wydań newslettera

Usługi deweloperskie:

  • Mierzenie zużycia API: Płatność za wywołanie RPC, zapytanie do bazy danych lub jednostkę obliczeniową (zob. przykład Corbits)
  • Funkcje bezserwerowe: Pobieranie opłat za poszczególne wywołania funkcji

Dane i analityka:

  • Dane rynkowe w czasie rzeczywistym: Cenniki per-kwotowanie lub per-tick
  • Pulpity analityczne: Odblokowanie określonych raportów lub eksportów danych
  • Dane z czujników IoT: Mikropłatności za odczyty czujników z sieci DePIN

Gry i dobra wirtualne:

  • Dostęp do serwerów gier: Płatność za sesję lub za godzinę
  • Pobieranie modów/zasobów: Monetyzacja treści tworzonych przez użytkowników
  • Opłaty za udział w turniejach: Automatyczna dystrybucja puli nagród

Różne:

  • Filtrowanie e-maili/DM: Wymaganie płatności w celu dotarcia do Twojej skrzynki odbiorczej (zapobieganie spamowi)
  • Zasoby obliczeniowe: Płatność za godzinę CPU, minutę GPU lub GB pamięci masowej
  • Dostęp do VPN/Proxy: Cennik za GB przepustowości
  • Jednorazowe pobieranie plików: Sprzedaż plików cyfrowych bez kosztów subskrypcji (zob. przykład ACK)

Kluczową zaletą x402 na Solanie są niskie koszty transakcji (ułamki centa), co sprawia, że prawdziwe mikropłatności stają się opłacalne, oraz natychmiastowe rozliczenia umożliwiające kontrolę dostępu w czasie rzeczywistym.

Pakiety SDK i ich wsparcie dla Solany

To jest rozwijająca się lista, która będzie aktualizowana w miarę wydawania nowych pakietów SDK lub dodawania wsparcia dla Solany.

SDK / ProjektWsparcie dla SolanyUwagiDokumentacja / URL
CorbitsTakWygodny pakiet SDK dla protokołu 402 na SolanieDokumentacja
MCPay.techTakPłatności za serwery MCP w mikropłatnościachStrona
PayAI FacilitatorTakFasilitator x402 ze wsparciem dla Solanypayai.network
CoinbaseTak / Python w trakcie realizacjiReferencyjna implementacja protokołu x402 firmy CoinbaseGitHub
ACKW PRProtokół płatności agentowych ze wsparciem x402GitHub
CrossmintW trakcie opracowaniaPłatności, portfele; finanse agentowe; nie dedykowane dla x402crossmint.com
A2A x402 (Google)W trakcie opracowaniaPłatności agent-do-agenta z wykorzystaniem Google AIGitHub
Nexus (Thirdweb)W trakcie opracowaniaOpakowanie x402 wokół kluczy APINexus
x402scanN/D (Eksplorator)Eksplorator ekosystemu x402 (nie jest pakietem SDK)x402scan.com
Przykład natywnyTakMinimalny przykład bez zależnościPrzykłady

Corbits

Pakiet SDK stawiający Solanę na pierwszym miejscu, umożliwiający szybkie wdrażanie przepływów x402 na Solanie. Zobacz dokumentację: https://corbits.dev/

Przykład umożliwiający płatność za żądania RPC na Solanie.

npm install @faremeter/payment-solana @faremeter/fetch @faremeter/info
@solana/web3.js

Utwórz plik payer-wallet.json i zasil go pewną ilością USDC oraz mainnet SOL.

import {
Keypair,
PublicKey,
VersionedTransaction,
Connection
} from "@solana/web3.js";
import { createPaymentHandler } from "@faremeter/payment-solana/exact";
import { wrap } from "@faremeter/fetch";
import { lookupKnownSPLToken } from "@faremeter/info/solana";
import * as fs from "fs";
// Load keypair from file
const keypairData = JSON.parse(fs.readFileSync("./payer-wallet.json", "utf-8"));
const keypair = Keypair.fromSecretKey(Uint8Array.from(keypairData));
const network = "mainnet-beta";
const connection = new Connection("https://api.mainnet.solana.com");
const usdcInfo = lookupKnownSPLToken(network, "USDC");
const usdcMint = new PublicKey(usdcInfo.address);
// Create wallet interface
const wallet = {
network,
publicKey: keypair.publicKey,
updateTransaction: async (tx: VersionedTransaction) => {
tx.sign([keypair]);
return tx;
}
};
// Setup payment handler
const handler = createPaymentHandler(wallet, usdcMint, connection);
const fetchWithPayer = wrap(fetch, { handlers: [handler] });
// Call the API - payment happens automatically
const response = await fetchWithPayer("https://helius.api.corbits.dev", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "getBlockHeight"
})
});
const data = await response.json();
console.log(data);
npx tsx e2e.ts

To wywołanie opłaci żądanie RPC i zwróci wysokość bloku, korzystając z protokołu corbits 402.

Coinbase

Referencyjna implementacja Coinbase protokołu x402 dostarcza biblioteki TypeScript oraz przykłady zarówno dla przepływów klienta, jak i serwera. Repozytorium zawiera testy end-to-end obejmujące 6 różnych scenariuszy SVM (Solana Virtual Machine). Implementacja obejmuje weryfikację płatności, generowanie potwierdzeń oraz obsługę błędów.

Kluczowe funkcje:

  • Implementacje klienta i serwera w TypeScript
  • Narzędzia do weryfikacji płatności
  • Wsparcie dla wielu schematów płatności (dokładna kwota, kwota do wysokości)
  • Zestaw testów z przykładami transakcji na Solanie
  • Rozdzielenie logiki protokołu od logiki biznesowej

Łatwy w użyciu przykład z minimalnym serwerem i klientem znajdziesz tutaj.

const app = express();
const PORT = 3000;
// Apply x402 payment middleware
// This automatically handles:
// - 402 responses with payment requirements
// - Payment verification (pre-flight checks)
// - Transaction submission via facilitator
// - Settlement confirmation
app.use(
paymentMiddleware(RECIPIENT, {
// Protected endpoint: requires $0.001 USDC payment
"GET /premium": {
price: "$0.0001", // Price in USD (converted to USDC)
network: "solana-devnet" // Solana devnet
},
// Another endpoint with different price
"GET /expensive": {
price: "$0.001",
network: "solana-devnet"
}
})
);
// Protected endpoints - only accessible after payment
app.get("/premium", (req, res) => {
res.json({
message: "🎉 Premium content accessed!",
data: {
secret: "This is premium content",
timestamp: new Date().toISOString()
}
});
});

Wsparcie dla Pythona jest w trakcie opracowania — działający przykład end-to-end jest dostępny tutaj.

ACK

Agent Commerce Kit (ACK) obsługuje protokół x402, ale dodaje krytyczne warstwy dla gospodarki agentowej: weryfikowalną tożsamość agenta (ACK-ID) przy użyciu W3C DID/VC oraz kryptograficznie bezpieczne potwierdzenia (ACK-Pay) w postaci Weryfikowalnych Poświadczeń. Umożliwia to agentom udowodnienie własności, autonomiczne uwierzytelnianie oraz generowanie gotowych do celów compliance dowodów płatności, rozwiązując kryzys tożsamości i bariery transakcyjne uniemożliwiające agentom AI uczestnictwo w handlu.

Diagram przepływu ACKDiagram przepływu ACK

Istnieje PR z przykładem e2e, który nie został jeszcze scalony, ale działa. Dostępny jest również Live Example pokazujący, jak zabezpieczyć obrazy paywallem, stworzyć szafę grającą oraz API animujące obrazy. Kod źródłowy przykładów wraz z botem na Twitterze używającym API do animowania obrazów na osi czasu można znaleźć tutaj.

MCPay.tech

Mikropłatności za żądanie dla serwerów MCP (Model Context Protocol) przy użyciu przepływów zbliżonych do x402. Umożliwia deweloperom monetyzację narzędzi i zasobów MCP poprzez wymaganie małych płatności za każde wywołanie API lub narzędzia, ułatwiając pobieranie opłat za dostęp agentów AI do premium źródeł danych, wyspecjalizowanych narzędzi lub zasobów obliczeniowych. Strona: https://mcpay.tech/

PayAI Facilitator

Fasilitator x402 stawiający Solanę na pierwszym miejscu, z działającym echo merchantem do testowania i zwrotu płatności. PayAI przejmuje obecnie wszystkie opłaty transakcyjne. Strona: https://payai.network/

A2A x402 (Google)

Inicjatywa płatności agent-do-agenta badająca ustandaryzowane przepływy z wymaganą płatnością. Wsparcie dla Solany jest obecnie w trakcie realizacji, a działający przykład czatu można znaleźć tutaj

Crossmint

Crossmint to kompleksowa platforma dla firm i agentów umożliwiająca integrację infrastruktury krypto — w tym portfeli, onrampów, orkiestracji stablecoinów i wiele więcej. Wsparcie dla x402 na Solanie jest obecnie w trakcie realizacji i ma zostać ukończone do 30.10.2025. Strona: https://www.crossmint.com/

x402scan

Eksplorator ekosystemu x402 dostarczający kompleksowe statystyki, listingi projektów i analitykę dla implementacji x402. Śledź wolumeny transakcji, odkrywaj aktywnych merchantów i monitoruj wzrost liczby endpointów z wymaganą płatnością w różnych sieciach. Strona: https://x402scan.com/

Nexus (Thirdweb)

Thirdweb Nexus tworzy opakowanie x402 wokół kluczy API (obecnie w trakcie opracowania). Strona: https://nexus.thirdweb.com/

Przykład natywny

Natywny przykład bez zależności z minimalnym serwerem i klientem.

Możesz sklonować repozytorium i uruchomić przykład:

git clone https://github.com/Woody4618/x402-solana-examples
npm install
# Terminal 1: Start server
npm run usdc:server
# Terminal 2: Run client (requires devnet USDC)
npm run usdc:client

Przegląd przepływu

  1. Klient wysyła żądanie do /premium.
  2. Serwer odpowiada kodem 402 z warunkami płatności: adresem odbiorcy i kwotą.
  3. Klient tworzy transakcję z instrukcją przelewu do odbiorcy.
  4. Klient ponawia żądanie do /premium z ładunkiem transakcji.
  5. Serwer weryfikuje transakcję i przesyła ją do sieci.
  6. Po potwierdzeniu serwer odpowiada kodem 200.

Alternatywa specyficzna dla Solany: Na Solanie można zaimplementować wariant, w którym klient przesyła transakcję bezpośrednio do sieci z instrukcją memo (zamiast wysyłać ją do serwera), a następnie wysyła jedynie sygnaturę transakcji do serwera w celu weryfikacji. Rozwiązuje to problem utraty połączenia — jeśli klient rozłączy się po dokonaniu płatności, ale przed otrzymaniem treści, może ponowić próbę z tą samą sygnaturą, ponieważ płatność jest już potwierdzona onchain. Jednak to podejście odbiega od standardowego przepływu x402.org (który zakłada, że serwer rozgłasza transakcję), dlatego w tym przykładzie stosujemy podejście standardowe.

Uwaga: Kod tego przykładu nie został poddany audytowi, nie jest gotowy do użytku produkcyjnego i służy wyłącznie celom demonstracyjnym. Pokazuje, że można zaimplementować x402 bez zależności i bez użycia facilitatora. Korzystanie z facilitatora jest wygodne, ponieważ ukrywa złożoność i może przejąć opłaty transakcyjne, jednak może też stanowić pojedy punkt awarii — na przykład gdy portfel facilitatora wyczerpie środki. Przykładowy serwer przesyła transakcje podpisane przez klienta. Być może będziesz musiał je zwalidować.

Minimalny serwer (Express)

// x402-compliant server with USDC (SPL Token) payments
import express from "express";
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID, getAssociatedTokenAddress } from "@solana/spl-token";
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
// Devnet USDC mint address
const USDC_MINT = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
// Your recipient wallet address (same as SOL example)
const RECIPIENT_WALLET = new PublicKey(
"seFkxFkXEY9JGEpCyPfCWTuPZG9WK6ucf95zvKCfsRX"
);
// Derive the recipient's USDC token account (Associated Token Account)
const RECIPIENT_TOKEN_ACCOUNT = await getAssociatedTokenAddress(
USDC_MINT,
RECIPIENT_WALLET
);
// Picking a small USDC price
const PRICE_USDC = 100; // 0.0001 USDC
const app = express();
app.use(express.json());
// x402 endpoint - Quote or verify payment
app.get("/premium", async (req, res) => {
const xPaymentHeader = req.header("X-Payment");
// If client provided X-Payment header, verify and submit transaction
if (xPaymentHeader) {
try {
// Decode base64 and parse JSON (x402 standard)
const paymentData = JSON.parse(
Buffer.from(xPaymentHeader, "base64").toString("utf-8")
) as {
x402Version: number;
scheme: string;
network: string;
payload: {
serializedTransaction: string;
};
};
console.log("Received USDC payment proof from client");
console.log(` Network: ${paymentData.network}`);
// Deserialize the transaction
const txBuffer = Buffer.from(
paymentData.payload.serializedTransaction,
"base64"
);
const tx = Transaction.from(txBuffer);
console.log("Verifying SPL Token transfer instructions...");
// Step 1: Introspect and decode SPL Token transfer instruction
const instructions = tx.instructions;
let validTransfer = false;
let transferAmount = 0;
for (const ix of instructions) {
// Check if this is a Token Program instruction
if (ix.programId.equals(TOKEN_PROGRAM_ID)) {
// SPL Token Transfer instruction layout:
// [0] = instruction type (3 for Transfer)
// [1-8] = amount (u64, little-endian)
if (ix.data.length >= 9 && ix.data[0] === 3) {
// Read the amount (u64 in little-endian, starts at byte 1)
transferAmount = Number(ix.data.readBigUInt64LE(1));
// Verify accounts: [source, destination, owner]
if (ix.keys.length >= 2) {
const destAccount = ix.keys[1].pubkey;
if (
destAccount.equals(RECIPIENT_TOKEN_ACCOUNT) &&
transferAmount >= PRICE_USDC
) {
validTransfer = true;
console.log(
` ✓ Valid USDC transfer: ${transferAmount / 1000000} USDC`
);
console.log(` To: ${RECIPIENT_TOKEN_ACCOUNT.toBase58()}`);
break;
}
}
}
}
}
if (!validTransfer) {
return res.status(402).json({
error:
"Transaction does not contain valid USDC transfer to recipient with correct amount",
details:
transferAmount > 0
? `Found transfer of ${transferAmount}, expected ${PRICE_USDC}`
: "No valid token transfer instruction found"
});
}
// Step 2: Simulate the transaction BEFORE submitting
console.log("Simulating transaction...");
try {
const simulation = await connection.simulateTransaction(tx);
if (simulation.value.err) {
console.error("Simulation failed:", simulation.value.err);
return res.status(402).json({
error: "Transaction simulation failed",
details: simulation.value.err,
logs: simulation.value.logs
});
}
console.log(" ✓ Simulation successful");
} catch (simError) {
console.error("Simulation error:", simError);
return res.status(402).json({
error: "Failed to simulate transaction",
details:
simError instanceof Error ? simError.message : "Unknown error"
});
}
// Step 3: Submit the transaction (only if verified and simulated successfully)
// Note: Solana blockchain automatically rejects duplicate transaction signatures
console.log("Submitting transaction to network...");
const signature = await connection.sendRawTransaction(txBuffer, {
skipPreflight: false,
preflightCommitment: "confirmed"
});
console.log(`Transaction submitted: ${signature}`);
// Wait for confirmation
const confirmation = await connection.confirmTransaction(
signature,
"confirmed"
);
if (confirmation.value.err) {
return res.status(402).json({
error: "Transaction failed onchain",
details: confirmation.value.err
});
}
// Fetch the transaction to verify payment details
const confirmedTx = await connection.getTransaction(signature, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0
});
if (!confirmedTx) {
return res.status(402).json({
error: "Could not fetch confirmed transaction"
});
}
// Verify token balance changes from transaction metadata
const postTokenBalances = confirmedTx.meta?.postTokenBalances ?? [];
const preTokenBalances = confirmedTx.meta?.preTokenBalances ?? [];
// Find the recipient's token account in the balance changes
let amountReceived = 0;
for (let i = 0; i < postTokenBalances.length; i++) {
const postBal = postTokenBalances[i];
const preBal = preTokenBalances.find(
(pre) => pre.accountIndex === postBal.accountIndex
);
// Check if this is the recipient's account
const accountKey =
confirmedTx.transaction.message.staticAccountKeys[
postBal.accountIndex
];
if (accountKey && accountKey.equals(RECIPIENT_TOKEN_ACCOUNT)) {
const postAmount = postBal.uiTokenAmount.amount;
const preAmount = preBal?.uiTokenAmount.amount ?? "0";
amountReceived = Number(postAmount) - Number(preAmount);
break;
}
}
if (amountReceived < PRICE_USDC) {
return res.status(402).json({
error: `Insufficient payment: received ${amountReceived}, expected ${PRICE_USDC}`
});
}
console.log(
`Payment verified: ${amountReceived / 1000000} USDC received`
);
console.log(
`View transaction: https://explorer.solana.com/tx/${signature}?cluster=devnet`
);
// Payment verified! Return premium content
return res.json({
data: "Premium content - USDC payment verified!",
paymentDetails: {
signature,
amount: amountReceived,
amountUSDC: amountReceived / 1000000,
recipient: RECIPIENT_TOKEN_ACCOUNT.toBase58(),
explorerUrl: `https://explorer.solana.com/tx/${signature}?cluster=devnet`
}
});
} catch (e) {
console.error("Payment verification error:", e);
return res.status(402).json({
error: "Payment verification failed",
details: e instanceof Error ? e.message : "Unknown error"
});
}
}
// No payment provided - return 402 with payment details
console.log("New USDC payment quote requested");
return res.status(402).json({
payment: {
recipientWallet: RECIPIENT_WALLET.toBase58(),
tokenAccount: RECIPIENT_TOKEN_ACCOUNT.toBase58(),
mint: USDC_MINT.toBase58(),
amount: PRICE_USDC,
amountUSDC: PRICE_USDC / 1000000,
cluster: "devnet",
message: "Send USDC to the token account"
}
});
});
app.listen(3001, () => console.log("x402 USDC server listening on :3001"));

Minimalny klient (Node)

import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import {
createTransferInstruction,
getOrCreateAssociatedTokenAccount,
createAssociatedTokenAccountInstruction,
getAccount
} from "@solana/spl-token";
import fetch from "node-fetch";
import { readFileSync } from "fs";
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const keypairData = JSON.parse(
readFileSync("./pay-in-usdc/client.json", "utf-8")
);
const payer = Keypair.fromSecretKey(Uint8Array.from(keypairData));
async function run() {
// 1) Request payment quote from server
const quote = await fetch("http://localhost:3001/premium");
const q = (await quote.json()) as {
payment: {
tokenAccount: string;
mint: string;
amount: number;
amountUSDC: number;
cluster: string;
};
};
if (quote.status !== 402) throw new Error("Expected 402 quote");
const recipientTokenAccount = new PublicKey(q.payment.tokenAccount);
const mint = new PublicKey(q.payment.mint);
const amount = q.payment.amount;
console.log("USDC Payment required:");
console.log(` Recipient Token Account: ${q.payment.tokenAccount}`);
console.log(` Mint (USDC): ${q.payment.mint}`);
console.log(
` Amount: ${q.payment.amountUSDC} USDC (${amount} smallest units)`
);
// 2) Get or create the payer's associated token account
console.log("\nChecking/creating associated token account...");
const payerTokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
payer,
mint,
payer.publicKey
);
console.log(` Payer Token Account: ${payerTokenAccount.address.toBase58()}`);
// Check if payer has enough USDC
const balance = await connection.getTokenAccountBalance(
payerTokenAccount.address
);
console.log(` Current Balance: ${balance.value.uiAmountString} USDC`);
if (Number(balance.value.amount) < amount) {
throw new Error(
`Insufficient USDC balance. Have: ${balance.value.uiAmountString}, Need: ${q.payment.amountUSDC}`
);
}
// 3) Check if recipient token account exists, create if not
console.log("\nChecking recipient token account...");
let recipientAccountExists = false;
try {
await getAccount(connection, recipientTokenAccount);
recipientAccountExists = true;
console.log(" ✓ Recipient token account exists");
} catch (error) {
console.log(" ⚠ Recipient token account doesn't exist, will create it");
}
// 4) Create USDC transfer transaction (but DON'T submit it)
const { blockhash } = await connection.getLatestBlockhash();
const tx = new Transaction({
feePayer: payer.publicKey,
blockhash,
lastValidBlockHeight: (await connection.getLatestBlockhash())
.lastValidBlockHeight
});
// Add create account instruction if needed
if (!recipientAccountExists) {
// We need to know the recipient wallet address to create the ATA
// The server should provide this, so let's get it from the wallet address
// Usually the server will already have the token account, but to be sure for the examples
// lets create one.
const recipientWallet = new PublicKey(
"seFkxFkXEY9JGEpCyPfCWTuPZG9WK6ucf95zvKCfsRX"
);
const createAccountIx = createAssociatedTokenAccountInstruction(
payer.publicKey, // payer
recipientTokenAccount, // associated token account address
recipientWallet, // owner
mint // mint
);
tx.add(createAccountIx);
console.log(" + Added create token account instruction");
}
// Add transfer instruction
const transferIx = createTransferInstruction(
payerTokenAccount.address, // source
recipientTokenAccount, // destination
payer.publicKey, // owner
amount // amount in smallest units
);
tx.add(transferIx);
// Sign the transaction (but don't send it, the server will do that)
tx.sign(payer);
// Serialize the signed transaction
const serializedTx = tx.serialize().toString("base64");
console.log("\nTransaction created and signed (not submitted yet)");
console.log(` Instructions: ${tx.instructions.length}`);
// 4) Send X-Payment header with serialized transaction (x402 standard)
const paymentProof = {
x402Version: 1,
scheme: "exact",
network:
q.payment.cluster === "devnet" ? "solana-devnet" : "solana-mainnet",
payload: {
serializedTransaction: serializedTx
}
};
// Base64 encode the payment proof
const xPaymentHeader = Buffer.from(JSON.stringify(paymentProof)).toString(
"base64"
);
console.log(
"\nSending payment proof to server (server will submit transaction)..."
);
const paid = await fetch("http://localhost:3001/premium", {
headers: {
"X-Payment": xPaymentHeader
}
});
const result = (await paid.json()) as {
data?: string;
error?: string;
paymentDetails?: {
signature: string;
amount: number;
amountUSDC: number;
recipient: string;
explorerUrl: string;
};
};
console.log("\nServer response:");
console.log(result);
// Display explorer link if payment was successful
if (result.paymentDetails?.explorerUrl) {
console.log("\n🔗 View transaction on Solana Explorer:");
console.log(result.paymentDetails.explorerUrl);
}
}
run().catch(console.error);

Ulepszenia

  • Rozważ zwracanie JWT po dokonaniu płatności, aby klienci mogli krótkotrwale ponownie wykorzystywać dostęp. ACK znacznie to ułatwia.
  • Upewnij się, że Twoje klucze nie wyciekają i przechowuj je w zmiennych środowiskowych.

Is this page helpful?