ما هو 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
ملاحظة: المُيسِّر اختياري تمامًا ويمكنك أيضًا تطبيق منطق التحقق الخاص بك ببضعة أسطر من الكود. يُجرّد المُيسِّر تفاصيل تكامل البلوكشين من الخادم والعميل مما يُسهّل التطبيق.
أجزاء المواصفات التي يجب معرفتها: هيكل 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 لبروتوكول x402 | GitHub |
| ACK | في طلب سحب (PR) | بروتوكول دفع للوكلاء مع دعم x402 | GitHub |
| Crossmint | قيد التطوير | المدفوعات والمحافظ؛ التمويل الوكيل؛ غير مخصص لـ x402 | crossmint.com |
| A2A x402 (Google) | قيد التطوير | مدفوعات بين الوكلاء باستخدام ذكاء اصطناعي Google | GitHub |
| Nexus (Thirdweb) | قيد التطوير | غلاف x402 حول مفاتيح API | Nexus |
| 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 fileconst 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 interfaceconst wallet = {network,publicKey: keypair.publicKey,updateTransaction: async (tx: VersionedTransaction) => {tx.sign([keypair]);return tx;}};// Setup payment handlerconst handler = createPaymentHandler(wallet, usdcMint, connection);const fetchWithPayer = wrap(fetch, { handlers: [handler] });// Call the API - payment happens automaticallyconst 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 confirmationapp.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 paymentapp.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
يوجد طلب سحب (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-examplesnpm install# Terminal 1: Start servernpm run usdc:server# Terminal 2: Run client (requires devnet USDC)npm run usdc:client
نظرة عامة على التدفق
- يطلب العميل
/premium. - يردّ الخادم بـ 402 مع شروط الدفع: المستلم والمبلغ.
- ينشئ العميل معاملة مع تعليمة تحويل إلى المستلم.
- يُعيد العميل محاولة
/premiumمع حمولة المعاملة. - يتحقق الخادم من المعاملة ويرسلها إلى الشبكة.
- بمجرد التأكيد، يردّ الخادم بـ 200.
البديل الخاص بـ سولانا: على سولانا، يمكنك تنفيذ نوع مختلف حيث يقوم العميل بإرسال المعاملة مباشرةً إلى الشبكة مع تعليمة مذكرة (بدلاً من إرسالها إلى الخادم)، ثم يرسل توقيع المعاملة فقط إلى الخادم للتحقق منه. يحل هذا مشكلة انقطاع الاتصال—إذا انقطع اتصال العميل بعد الدفع ولكن قبل aستلام المحتوى، يمكنه إعادة المحاولة بنفس التوقيع بما أن الدفع مؤكد بالفعل على السلسلة. غير أن هذا النهج يحيد عن تدفق معيار x402.org القياسي (الذي يتوقع من الخادم بث المعاملة)، لذا نستخدم النهج القياسي في هذا المثال.
ملاحظة: لم يخضع كود هذا المثال لأي تدقيق وليس جاهزاً للإنتاج، وهو لأغراض توضيحية فحسب. يُظهر أنه يمكنك تنفيذ x402 بدون تبعيات وبدون استخدام ميسّر. استخدام ميسّر أمر مفيد لأنه يخفي التعقيد ويمكنه تولي رسوم المعاملات، لكنه قد يكون أيضاً nقطة فشل واحدة، كما في حالة نفاد أموال محفظة الميسّر مثلاً. يقوم خادم المثال بإرسال المعاملات الموقعة من العميل. قد تحتاج إلى validator هذه المعاملات.
الخادم الأدنى (Express)
// x402-compliant server with USDC (SPL Token) paymentsimport 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 addressconst 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 priceconst PRICE_USDC = 100; // 0.0001 USDCconst app = express();app.use(express.json());// x402 endpoint - Quote or verify paymentapp.get("/premium", async (req, res) => {const xPaymentHeader = req.header("X-Payment");// If client provided X-Payment header, verify and submit transactionif (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 transactionconst 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 instructionconst instructions = tx.instructions;let validTransfer = false;let transferAmount = 0;for (const ix of instructions) {// Check if this is a Token Program instructionif (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 submittingconsole.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 signaturesconsole.log("Submitting transaction to network...");const signature = await connection.sendRawTransaction(txBuffer, {skipPreflight: false,preflightCommitment: "confirmed"});console.log(`Transaction submitted: ${signature}`);// Wait for confirmationconst 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 detailsconst 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 metadataconst postTokenBalances = confirmedTx.meta?.postTokenBalances ?? [];const preTokenBalances = confirmedTx.meta?.preTokenBalances ?? [];// Find the recipient's token account in the balance changeslet 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 accountconst 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 contentreturn 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 detailsconsole.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 serverconst 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 accountconsole.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 USDCconst 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 notconsole.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 neededif (!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, // payerrecipientTokenAccount, // associated token account addressrecipientWallet, // ownermint // mint);tx.add(createAccountIx);console.log(" + Added create token account instruction");}// Add transfer instructionconst transferIx = createTransferInstruction(payerTokenAccount.address, // sourcerecipientTokenAccount, // destinationpayer.publicKey, // owneramount // 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 transactionconst 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 proofconst 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 successfulif (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?