Spaces:
Running
Running
| import "dotenv/config"; | |
| import { Transaction, TransactionBuilder } from "@stellar/stellar-sdk"; | |
| import { x402Client, x402HTTPClient } from "@x402/fetch"; | |
| import { createEd25519Signer, getNetworkPassphrase } from "@x402/stellar"; | |
| import { ExactStellarScheme } from "@x402/stellar/exact/client"; | |
| const STELLAR_PRIVATE_KEY = process.env.STELLAR_PRIVATE_KEY; | |
| const SERVER_URL = process.env.SERVER_URL || "http://localhost:3001"; | |
| const NETWORK = "stellar:testnet"; | |
| const STELLAR_RPC_URL = "https://soroban-testnet.stellar.org"; | |
| if (!STELLAR_PRIVATE_KEY) { | |
| console.error("ERROR: STELLAR_PRIVATE_KEY not set in .env"); | |
| process.exit(1); | |
| } | |
| const targetUrl = process.argv[2] || "https://x.com/stellarorg"; | |
| async function main() { | |
| const renderEndpoint = `${SERVER_URL}/render?url=${encodeURIComponent(targetUrl)}`; | |
| // Setup x402 client | |
| const signer = createEd25519Signer(STELLAR_PRIVATE_KEY, NETWORK); | |
| const rpcConfig = { url: STELLAR_RPC_URL }; | |
| const client = new x402Client().register( | |
| "stellar:*", | |
| new ExactStellarScheme(signer, rpcConfig), | |
| ); | |
| const httpClient = new x402HTTPClient(client); | |
| console.log(`Target URL: ${targetUrl}`); | |
| console.log(`Render endpoint: ${renderEndpoint}`); | |
| console.log(`Paying from: ${signer.address}`); | |
| // Step 1: Request without payment — expect 402 | |
| console.log("\n--- Step 1: Request without payment ---"); | |
| const firstTry = await fetch(renderEndpoint); | |
| console.log(`Response: ${firstTry.status} ${firstTry.statusText}`); | |
| if (firstTry.status !== 402) { | |
| console.log("No payment required! Response:", await firstTry.text()); | |
| return; | |
| } | |
| // Step 2: Extract payment requirements from 402 response | |
| console.log("\n--- Step 2: Create payment ---"); | |
| const paymentRequired = httpClient.getPaymentRequiredResponse((name) => | |
| firstTry.headers.get(name), | |
| ); | |
| console.log("Payment required:", JSON.stringify(paymentRequired, null, 2)); | |
| // Step 3: Create and configure payment payload | |
| let paymentPayload = await client.createPaymentPayload(paymentRequired); | |
| const networkPassphrase = getNetworkPassphrase(NETWORK); | |
| const tx = new Transaction( | |
| paymentPayload.payload.transaction, | |
| networkPassphrase, | |
| ); | |
| const sorobanData = tx.toEnvelope().v1()?.tx()?.ext()?.sorobanData(); | |
| if (sorobanData) { | |
| paymentPayload = { | |
| ...paymentPayload, | |
| payload: { | |
| ...paymentPayload.payload, | |
| transaction: TransactionBuilder.cloneFrom(tx, { | |
| fee: "1", | |
| sorobanData, | |
| networkPassphrase, | |
| }) | |
| .build() | |
| .toXDR(), | |
| }, | |
| }; | |
| } | |
| // Step 4: Send paid request | |
| console.log("\n--- Step 3: Send paid request ---"); | |
| const paymentHeaders = | |
| httpClient.encodePaymentSignatureHeader(paymentPayload); | |
| const start = Date.now(); | |
| const paidResponse = await fetch(renderEndpoint, { | |
| method: "GET", | |
| headers: paymentHeaders, | |
| }); | |
| const elapsed = Date.now() - start; | |
| // Step 5: Show results | |
| console.log(`\n--- Result (${elapsed}ms) ---`); | |
| console.log(`Status: ${paidResponse.status}`); | |
| const paymentResponse = httpClient.getPaymentSettleResponse((name) => | |
| paidResponse.headers.get(name), | |
| ); | |
| if (paymentResponse) { | |
| console.log("Settlement:", JSON.stringify(paymentResponse, null, 2)); | |
| } | |
| const data = await paidResponse.json(); | |
| console.log(`\nTitle: ${data.title}`); | |
| console.log(`Render time: ${data.renderTimeMs}ms`); | |
| console.log(`Content length: ${data.content?.length} chars`); | |
| console.log(`\nContent preview:\n${data.content?.substring(0, 600)}`); | |
| } | |
| main().catch((err) => { | |
| console.error("Client error:", err); | |
| process.exit(1); | |
| }); | |