rendergate / save-demo.js
tantk's picture
fix: SSRF IPv6 bypass, SIGINT handler, README accuracy fixes
a4ad7d1
import "dotenv/config";
import { writeFileSync } from "fs";
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 SERVER_URL = process.env.SERVER_URL || "https://tantk-rendergate.hf.space";
const NETWORK = "stellar:testnet";
const signer = createEd25519Signer(process.env.STELLAR_PRIVATE_KEY, NETWORK);
const client = new x402Client().register(
"stellar:*",
new ExactStellarScheme(signer, { url: "https://soroban-testnet.stellar.org" }),
);
const httpClient = new x402HTTPClient(client);
async function paidRender(targetUrl) {
const endpoint = `${SERVER_URL}/render?url=${encodeURIComponent(targetUrl)}`;
const r1 = await fetch(endpoint);
if (r1.status !== 402) return await r1.json();
const pr = httpClient.getPaymentRequiredResponse((n) => r1.headers.get(n));
let pp = await client.createPaymentPayload(pr);
const np = getNetworkPassphrase(NETWORK);
const tx = new Transaction(pp.payload.transaction, np);
const sd = tx.toEnvelope().v1()?.tx()?.ext()?.sorobanData();
if (sd) {
pp = {
...pp,
payload: {
...pp.payload,
transaction: TransactionBuilder.cloneFrom(tx, {
fee: "1", sorobanData: sd, networkPassphrase: np,
}).build().toXDR(),
},
};
}
const headers = httpClient.encodePaymentSignatureHeader(pp);
const r2 = await fetch(endpoint, { headers });
return await r2.json();
}
function formatOutput(data, targetUrl) {
let out = `=== RenderGate: ${targetUrl} ===\n`;
out += `Payment: ${data.payment?.price} USDC on ${data.payment?.network}\n`;
out += `Render time: ${data.renderTimeMs}ms\n\n`;
out += `Title: ${data.title}\n`;
if (data.description) out += `Description: ${data.description}\n`;
out += `\n`;
if (data.headings?.length > 0) {
out += `Headings (${data.headings.length}):\n`;
data.headings.slice(0, 15).forEach((h) => {
out += ` ${h.level}: ${h.text}\n`;
});
out += `\n`;
}
if (data.links?.length > 0) {
out += `Links (${data.links.length}):\n`;
data.links.slice(0, 15).forEach((l) => {
out += ` ${l.text.substring(0, 50)}${l.href}\n`;
});
out += `\n`;
}
if (data.refund) {
out += `*** AUTO-REFUND ***\n`;
out += `Reason: ${data.refund.reason}\n`;
out += `Refund amount: ${data.refund.amount}\n`;
out += `Refund transaction: ${data.refund.transaction}\n`;
out += `Verify: https://stellar.expert/explorer/testnet/tx/${data.refund.transaction}\n\n`;
}
out += `Content (${data.content?.length} chars):\n`;
out += data.content || "(empty)";
return out;
}
const sites = [
{ name: "1-twitter", url: "https://x.com/stellarorg" },
{ name: "2-linkedin", url: "https://www.linkedin.com/company/stellar-development-foundation" },
{ name: "3-soroswap", url: "https://soroswap.finance" },
{ name: "4-producthunt", url: "https://www.producthunt.com" },
];
for (const site of sites) {
console.log(`Rendering ${site.name}...`);
try {
const data = await paidRender(site.url);
const output = formatOutput(data, site.url);
const path = `demo-output/${site.name}-with.txt`;
writeFileSync(path, output);
console.log(` Saved: ${path} (${data.content?.length} chars, refund: ${!!data.refund})`);
} catch (err) {
console.error(` Failed: ${err.message}`);
}
}
console.log("Done!");