كيفية البدء مع x402 على سولانا

ما هو x402؟

x402 هو بروتوكول مفتوح للمدفوعات الأصيلة على الإنترنت. رمز الخطأ 402 يعني "الدفع مطلوب" وكان موجودًا منذ زمن طويل في مواصفات HTTP، لكنه أصبح قابلاً للاستخدام الفعلي الآن فقط بفضل ظهور شبكات البلوكشين. يشير بروتوكول 402 حاليًا إلى تطبيق نمط HTTP 402 Payment Required: يطلب الخادم دفعةً قبل إرجاع استجابة محمية. على سولانا، يُنفَّذ هذا عادةً بمطالبة العميل بإرسال تحويل صغير، ثم يتحقق الخادم من ذلك على السلسلة ويقدم المحتوى.

في الوقت الحالي، لم يتضح بعد أي من حزم SDK الخاصة بـ 402 ستكون الأكثر شيوعًا. لذا في هذا الدليل سنوضح كيفية تطبيق x402 باستخدام خادم وعميل بسيطَين، وسنُدرج جميع حزم SDK المتاحة لـ 402 مع توضيح مستوى دعم سولانا الحالي لكل منها.

كيف يعمل؟

ثمة عدة طرق لتطبيق x402، تتراوح بين البسيط للغاية والمُدار بالكامل.

فكرة البروتوكول: استخدام HTTP العادي. يصل العميل إلى رابطك ← تردّ بـ 402 Payment Required مع كائن JSON لمتطلبات الدفع ← يدفع العميل ويُعيد المحاولة مع رأس X-PAYMENT ← تتحقق/تُسوّي ← تردّ بـ 200 OK. لا حسابات، لا OAuth.

مخطط تدفق x402مخطط تدفق x402

ملاحظة: المُيسِّر اختياري تمامًا ويمكنك أيضًا تطبيق منطق التحقق الخاص بك ببضعة أسطر من الكود. يُجرّد المُيسِّر تفاصيل تكامل البلوكشين من الخادم والعميل مما يُسهّل التطبيق.

أجزاء المواصفات التي يجب معرفتها: هيكل PaymentRequirements، ورأس X-PAYMENT المُرمَّز بـ base64، وX-PAYMENT-RESPONSE الاختياري عند النجاح، وواجهة برمجة المُيسِّر الاختيارية لـ /verify و/settle و/supported. المخطط الملموس الحالي هو exact (دفع مبلغ محدد). أخرى مثل upto مقترحة.

دعم سولانا: البروتوكول نفسه مستقل عن السلسلة؛ على سولانا يدعم جميع رموز SPL. دعم سولانا متاح أو قيد التطوير لمعظم حزم SDK الخاصة بـ 402.

في ما يلي قائمة بـ حزم SDK المتاحة لـ 402 مع مستوى دعمها الحالي لسولانا.

حالات الاستخدام

يُتيح x402 مجموعة واسعة من سيناريوهات المدفوعات الصغيرة والدفع مقابل الاستخدام التي لم تكن مجدية اقتصاديًا قبل البلوكشين. تخيّل نتفليكس تدفع مقابل كل مشاهدة أو سبوتيفاي تدفع مقابل كل أغنية بدلاً من الاشتراكات. فيما يلي بعض الأفكار الممكنة، لكن الطيف الكامل من الإمكانيات مفتوح للخيال:

الذكاء الاصطناعي وتجارة الوكلاء:

  • وصول وكيل الذكاء الاصطناعي إلى API: الدفع لكل استدلال LLM أو توليد صور أو استدعاء لـ API نموذج ذكاء اصطناعي (انظر مثال ACK)
  • تحقيق الدخل من خادم MCP: فرض رسوم على أدوات بروتوكول سياق النموذج ومصادر البيانات وقدرات الوكيل المتخصصة (انظر MCPay.tech)
  • المدفوعات بين الوكلاء: تمكين الوكلاء المستقلين من التعامل مع بعضهم البعض مقابل الخدمات والبيانات (انظر مثال a2a-x402)
  • بيانات تدريب الذكاء الاصطناعي المميزة: بيع الوصول إلى مجموعات البيانات المنتقاة على أساس كل استعلام

المحتوى والوسائط:

  • المقالات المدفوعة: فرض مبالغ صغيرة لكل مقالة بدلاً من الاشتراكات الكاملة
  • بث الفيديو/الصوت: الدفع لكل مشاهدة أو لكل دقيقة من المحتوى
  • الصور عالية الدقة: فتح التنزيلات بالدقة الكاملة بعد الدفع (انظر مثال ACK) أو مثال x402 coinbase
  • الوصول إلى النشرات الإخبارية المميزة: تحقيق الدخل من إصدارات النشرات الإخبارية الفردية

خدمات المطورين:

  • قياس استخدام API: الدفع لكل استدعاء RPC أو استعلام قاعدة بيانات أو وحدة حوسبة (انظر مثال Corbits)
  • الدوال بدون خادم: فرض رسوم على تنفيذ الدوال الفردية

البيانات والتحليلات:

  • بيانات السوق في الوقت الفعلي: تغذية أسعار لكل اقتباس أو لكل نقطة بيانات
  • لوحات التحليلات: فتح تقارير أو صادرات بيانات محددة
  • بيانات مستشعرات إنترنت الأشياء: مدفوعات صغيرة لقراءات المستشعرات من شبكات DePIN

الألعاب والسلع الافتراضية:

  • الوصول إلى خادم الألعاب: الدفع لكل جلسة أو لكل ساعة
  • تحميلات الإضافات/الأصول: تحقيق الدخل من المحتوى الذي أنشأه المستخدمون
  • رسوم الدخول في البطولات: توزيع آلي لمجموعة الجوائز

متنوع:

  • تصفية البريد الإلكتروني/الرسائل المباشرة: اشتراط الدفع للوصول إلى صندوق الوارد الخاص بك (منع البريد العشوائي)
  • موارد الحوسبة: الدفع لكل ساعة CPU أو دقيقة GPU أو جيجابايت تخزين
  • الوصول إلى VPN/البروكسي: تسعير النطاق الترددي لكل جيجابايت
  • تنزيلات الملفات لمرة واحدة: بيع الملفات الرقمية دون تكاليف الاشتراك (انظر مثال ACK)

الميزة الرئيسية لـ x402 على سولانا هي انخفاض تكاليف المعاملات (أجزاء من السنت) مما يجعل المدفوعات الصغيرة الحقيقية قابلة للتطبيق، إضافةً إلى التسوية الفورية التي تُتيح التحكم في الوصول في الوقت الفعلي.

حزم SDK ودعمها لسولانا

هذه قائمة متطورة وسيتم تحديثها مع إصدار المزيد من حزم SDK أو إضافة دعم سولانا.

SDK / المشروعدعم سولاناملاحظاتالتوثيق / الرابط
Corbitsنعمحزمة SDK ملائمة لـ 402 على سولاناالتوثيق
MCPay.techنعمالدفع لخوادم MCP بمدفوعات صغيرةالموقع
PayAI Facilitatorنعممُيسِّر x402 مع دعم سولاناpayai.network
Coinbaseنعم / Python قيد التطويرالتطبيق المرجعي لـ Coinbase لبروتوكول x402GitHub
ACKفي طلب سحب (PR)بروتوكول دفع للوكلاء مع دعم x402GitHub
Crossmintقيد التطويرالمدفوعات والمحافظ؛ التمويل الوكيل؛ غير مخصص لـ x402crossmint.com
A2A x402 (Google)قيد التطويرمدفوعات بين الوكلاء باستخدام ذكاء اصطناعي GoogleGitHub
Nexus (Thirdweb)قيد التطويرغلاف x402 حول مفاتيح APINexus
x402scanغير متاح (مستكشف)مستكشف نظام x402 البيئي (ليس حزمة SDK)x402scan.com
مثال أصيلنعممثال بسيط بدون تبعياتالأمثلة

Corbits

حزمة SDK مُصمَّمة أولاً لسولانا لتطبيق تدفقات x402 بسرعة على سولانا. راجع التوثيق: https://corbits.dev/

مثال يتيح لك الدفع مقابل طلبات RPC على سولانا.

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

أنشئ ملف payer-wallet.json وزوّده ببعض USDC وبعض 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

سيدفع هذا مقابل طلب RPC ويعيد ارتفاع الكتلة مستخدمًا بروتوكول corbits 402.

Coinbase

التطبيق المرجعي لـ Coinbase لبروتوكول x402 يوفر مكتبات TypeScript وأمثلة لتدفقات العميل والخادم على حدٍّ سواء. يتضمن المستودع اختبارات شاملة من البداية إلى النهاية تغطي 6 سيناريوهات مختلفة لـ SVM (الآلة الافتراضية لسولانا). يشمل التطبيق التحقق من الدفع وتوليد الإيصالات ومعالجة الأخطاء.

الميزات الرئيسية:

  • تطبيقات عميل وخادم بـ TypeScript
  • أدوات التحقق من الدفع
  • دعم مخططات دفع متعددة (المبلغ المحدد، والمبلغ الأقصى)
  • مجموعة اختبارات مع أمثلة على معاملات سولانا
  • الفصل بين منطق البروتوكول ومنطق الأعمال

يمكنك العثور على مثال سهل الاستخدام مع خادم وعميل بسيطَين هنا.

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()
}
});
});

دعم Python قيد التطوير مع توفر مثال عملي من البداية إلى النهاية هنا.

ACK

يدعم Agent Commerce Kit (ACK) بروتوكول x402 لكنه يضيف طبقات حيوية لاقتصاد الوكلاء: هوية وكيل قابلة للتحقق (ACK-ID) باستخدام W3C DIDs/VCs وإيصالات مؤمَّنة تشفيريًا (ACK-Pay) كبيانات اعتماد قابلة للتحقق. يُتيح هذا للوكلاء إثبات الملكية والمصادقة باستقلالية وإنشاء إثباتات دفع جاهزة للامتثال، مما يعالج أزمة الهوية وعقبات المعاملات التي تحول دون مشاركة وكلاء الذكاء الاصطناعي في التجارة.

مخطط تدفق ACKمخطط تدفق ACK

يوجد طلب سحب (PR) مع مثال شامل من البداية إلى النهاية لم يُدمج بعد لكنه يعمل. يوجد أيضًا مثال مباشر يوضح كيفية حجب الصور بالدفع وصندوق موسيقى وAPI قادر على تحريك الصور. يمكن العثور على الكود المصدري للأمثلة إلى جانب بوت تويتر يستخدم الـ API لتحريك الصور في التسلسل الزمني هنا.

MCPay.tech

مدفوعات صغيرة بالدفع لكل طلب لخوادم MCP (بروتوكول سياق النموذج) باستخدام تدفقات مشابهة لـ x402. يُمكّن المطورين من تحقيق الدخل من أدوات MCP والموارد بالمطالبة بمدفوعات صغيرة لكل استدعاء API أو استدعاء أداة، مما يُسهّل فرض رسوم على وصول وكيل الذكاء الاصطناعي إلى مصادر البيانات المميزة والأدوات المتخصصة أو الموارد الحاسوبية. الموقع: https://mcpay.tech/

PayAI Facilitator

مُيسِّر x402 مُصمَّم أولاً لسولانا مع تاجر صدى مباشر لاختبار المدفوعات واستردادها. يتولى PayAI في الوقت الحالي تغطية جميع رسوم المعاملات. الموقع: https://payai.network/

A2A x402 (Google)

مبادرة 402 بين الوكلاء تستكشف تدفقات دفع مطلوبة موحّدة. دعم سولانا قيد التطوير حاليًا ويمكن العثور على مثال دردشة يعمل هنا

Crossmint

Crossmint هي منصة شاملة للشركات والوكلاء لدمج مسارات التشفير — بما في ذلك المحافظ وبوابات الدخول وتنسيق العملات المستقرة والمزيد. دعم x402 على سولانا قيد التطوير حاليًا ومن المقرر اكتماله بحلول 30.10.2025. الموقع: https://www.crossmint.com/

x402scan

مستكشف لنظام x402 البيئي يوفر إحصائيات شاملة وقوائم مشاريع وتحليلات لتطبيقات x402. تتبع أحجام المعاملات واكتشف التجار النشطين وراقب نمو نقاط النهاية التي تتطلب الدفع عبر شبكات مختلفة. الموقع: https://x402scan.com/

Nexus (Thirdweb)

يطور Thirdweb Nexus غلاف x402 حول مفاتيح API (قيد التطوير حاليًا). الموقع: https://nexus.thirdweb.com/

المثال الأصيل

مثال أصيل بدون تبعيات مع خادم وعميل بسيطَين.

يمكنك استنساخ المستودع وتشغيل المثال:

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

نظرة عامة على التدفق

  1. يطلب العميل /premium.
  2. يردّ الخادم بـ 402 مع شروط الدفع: المستلم والمبلغ.
  3. ينشئ العميل معاملة مع تعليمة تحويل إلى المستلم.
  4. يُعيد العميل محاولة /premium مع حمولة المعاملة.
  5. يتحقق الخادم من المعاملة ويرسلها إلى الشبكة.
  6. بمجرد التأكيد، يردّ الخادم بـ 200.

البديل الخاص بـ سولانا: على سولانا، يمكنك تنفيذ نوع مختلف حيث يقوم العميل بإرسال المعاملة مباشرةً إلى الشبكة مع تعليمة مذكرة (بدلاً من إرسالها إلى الخادم)، ثم يرسل توقيع المعاملة فقط إلى الخادم للتحقق منه. يحل هذا مشكلة انقطاع الاتصال—إذا انقطع اتصال العميل بعد الدفع ولكن قبل aستلام المحتوى، يمكنه إعادة المحاولة بنفس التوقيع بما أن الدفع مؤكد بالفعل على السلسلة. غير أن هذا النهج يحيد عن تدفق معيار x402.org القياسي (الذي يتوقع من الخادم بث المعاملة)، لذا نستخدم النهج القياسي في هذا المثال.

ملاحظة: لم يخضع كود هذا المثال لأي تدقيق وليس جاهزاً للإنتاج، وهو لأغراض توضيحية فحسب. يُظهر أنه يمكنك تنفيذ x402 بدون تبعيات وبدون استخدام ميسّر. استخدام ميسّر أمر مفيد لأنه يخفي التعقيد ويمكنه تولي رسوم المعاملات، لكنه قد يكون أيضاً nقطة فشل واحدة، كما في حالة نفاد أموال محفظة الميسّر مثلاً. يقوم خادم المثال بإرسال المعاملات الموقعة من العميل. قد تحتاج إلى validator هذه المعاملات.

الخادم الأدنى (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"));

العميل الأدنى (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);

التحسينات

  • فكّر في إعادة JWT بعد الدفع حتى يتمكن العملاء من إعادة استخدام الوصول لفترة وجيزة. يجعل ACK ذلك أمراً سهلاً للغاية.
  • تأكد من عدم تسرب مفاتيحك وضعها في متغيرات البيئة.

Is this page helpful?