5️⃣Guide to test x402 server
Prerequesites:
Installed Typescript Installed ts-node Installed dotenv Installed node-fetch Installed tweetnacl Installed bs58 env file with public and private key for Solana wallet. and API URL - htttp://api.hashhunter.app/api/process Store the private key in json format.
SVM setup:
// --------------------------------------------------------
// LOAD ENVIRONMENT
// --------------------------------------------------------
import dotenv from "dotenv";
dotenv.config();
import fetch from "node-fetch";
import nacl from "tweetnacl";
import bs58 from "bs58";
import {
Connection,
VersionedTransaction,
Transaction as LegacyTx,
MessageV0,
Keypair,
PublicKey
} from "@solana/web3.js";
// --------------------------------------------------------
// ENV VARIABLES (YOUR EXACT ENV)
// --------------------------------------------------------
const API_URL = process.env.API_URL;
const PRIVATE_KEY: number[] = JSON.parse(process.env.SOL_PRIVATE_KEY!);
const PUBLIC_KEY_BASE58 = process.env.SOL_PUBLIC_KEY!;
// ✔ Public Solana RPC placeholder (unless overridden)
const RPC_URL =
process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";
// Validate env
if (!API_URL) throw new Error("Missing API_URL");
if (!PRIVATE_KEY) throw new Error("Missing SOL_PRIVATE_KEY");
if (!PUBLIC_KEY_BASE58) throw new Error("Missing SOL_PUBLIC_KEY");
// Construct signing keypair
const keypair = Keypair.fromSecretKey(new Uint8Array(PRIVATE_KEY));
const walletPublicKey = new PublicKey(PUBLIC_KEY_BASE58);
console.log("🔑 Using Wallet (ENV):", walletPublicKey.toBase58());
// --------------------------------------------------------
// HELPERS
// --------------------------------------------------------
function base64ToUint8Array(b64: string) {
return new Uint8Array(Buffer.from(b64, "base64"));
}
async function post(url: string, body: any, headers: any = {}) {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify(body),
});
return await res.json();
}
// --------------------------------------------------------
// BASE58 ADDRESS FIXER
// --------------------------------------------------------
const BASE58_REGEX = /\b[1-9A-HJ-NP-Za-km-z]{30,60}\b/g;
// Fix token mint case issues
function normalizeSolanaAddresses(nlp: string): string {
const matches = nlp.match(BASE58_REGEX);
if (!matches) return nlp;
let fixed = nlp;
for (const token of matches) {
try {
const decoded = bs58.decode(token);
// Valid Solana address = 32 bytes
if (decoded.length === 32) {
const re = new RegExp(token, "gi");
fixed = fixed.replace(re, token);
}
} catch {}
}
return fixed;
}
// --------------------------------------------------------
// STEP 1 — NLP → operationId
// --------------------------------------------------------
async function initOperation(nlp: string) {
const cleanedNlp = normalizeSolanaAddresses(nlp);
const payload = {
messages: [{ role: "user", content: cleanedNlp }],
wallet: walletPublicKey.toBase58(), // ← EXACT ENV PUBLIC KEY
chain: "solana",
};
const initRes = await post(API_URL!, payload);
console.log("STEP 1 INIT:", initRes);
if (initRes.operationId) return initRes.operationId;
if (initRes.operations?.[0]?.id) return initRes.operations[0].id;
throw new Error(`API returned no operationId: ${JSON.stringify(initRes)}`);
}
// --------------------------------------------------------
// STEP 2 — Handle X402 challenge
// --------------------------------------------------------
async function handleX402(operationId: string) {
const challengeRes = await post(API_URL!, { operationId });
console.log("STEP 2 CHALLENGE:", challengeRes);
if (challengeRes.error !== "Token payment required") {
return challengeRes;
}
const cfg = challengeRes.accepts[0];
const challenge = {
scheme: cfg.scheme,
network: cfg.network,
resource: cfg.resource,
payTo: cfg.payTo,
asset: cfg.asset,
amount: "0",
};
const challengeBytes = Buffer.from(JSON.stringify(challenge));
const signature = nacl.sign.detached(challengeBytes, keypair.secretKey);
const paymentProof = Buffer.from(signature).toString("base64");
console.log("X-PAYMENT:", paymentProof);
const txData = await post(
API_URL!,
{ operationId },
{ "X-PAYMENT": paymentProof }
);
console.log("STEP 3 TX DATA:", txData);
return txData;
}
// --------------------------------------------------------
// STEP 3 — Deserialize, sign, broadcast
// --------------------------------------------------------
async function processTx(base64Tx: string) {
const txBytes = base64ToUint8Array(base64Tx);
let solTx: VersionedTransaction | LegacyTx;
try {
solTx = VersionedTransaction.deserialize(txBytes);
} catch {
solTx = LegacyTx.from(txBytes);
}
const connection = new Connection(RPC_URL, "confirmed");
const { blockhash } = await connection.getLatestBlockhash("confirmed");
// Versioned
if (solTx instanceof VersionedTransaction) {
const old = solTx.message;
solTx = new VersionedTransaction(
new MessageV0({
header: old.header,
staticAccountKeys: old.staticAccountKeys,
recentBlockhash: blockhash,
compiledInstructions: old.compiledInstructions,
addressTableLookups: old.addressTableLookups,
})
);
solTx.sign([keypair]);
}
// Legacy
else {
solTx.recentBlockhash = blockhash;
solTx.sign(keypair);
}
const sig = await connection.sendRawTransaction(solTx.serialize(), {
skipPreflight: false,
preflightCommitment: "confirmed",
});
console.log("🚀 TX SENT:", sig);
await connection.confirmTransaction(sig, "confirmed");
console.log("✅ TX CONFIRMED:", sig);
return sig;
}
// --------------------------------------------------------
// MAIN PIPELINE
// --------------------------------------------------------
async function main() {
const nlp =
`buy sh3nQze56RGQFBxr3FZidbbVNVh2oUctgt2sGP4pump with 0.1 sol`;
const operationId = await initOperation(nlp);
const txData = await handleX402(operationId);
if (!txData.transactions?.[0]?.data) {
throw new Error("No transaction returned by API");
}
await processTx(txData.transactions[0].data);
}
main().catch((err) => {
console.error("❌ ERROR:");
console.error(err);
});
In order to bypass the default protocol paywall you will need to sign a transaction that costs 0USDC to push your transaction through the PayAI facilitator.
Last updated