rendergate / demo-client.js
tantk's picture
RenderGate: x402-powered headless rendering API on Stellar
0d53e7e
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);
});