#!/usr/bin/env node // Proba cap la cap pe testnetul PUBLIC 28001 (2026-09-29, punctele 23, 25): un agent AI cumpara o resursa x402 de pe un server de // resurse local, platind prin portofelul lui (wallet.mjs) si prin facilitatorul x402 de pe testnet, cu decontare EIP-3009 pe lant. // 1. un portofel NOU (cheie generata aici, tinuta numai in memorie) primeste 0,05 tUSD de la cheia de dezvoltator a testnetului // (singura tranzactie trimisa de proba; decontarile le plateste facilitatorul); // 2. politica agentului: 30000 (0,03 tUSD) pe ora, un singur destinatar, portofelul numit; // 3. trei cumparaturi de 10000 platite si servite; a patra refuzata de politica, inainte de portofel; aceeasi plata trimisa din nou: // 402 (nonce folosit pe lant); // 4. pe lant: soldurile, si authorizationState(portofel, sha256(hash-ul intrarii)) pentru fiecare plata; // 5. dosarul-dovada scris (registrul, politica, platile) si verificat cu verifica-plati.mjs: VALID, inclusiv "orice Transfer din // portofel e o plata din registru"; o copie cu o suma schimbata in registru: INVALID. // Refuza orice alt lant decat 28001 (citeste eth_chainId). Cheia de dezvoltator se citeste din fisierul dat in AERE_TESTNET_KEY_FILE // (un rand d= sau PRIVATE_KEY=0x) si nu se tipareste. // AERE_TESTNET_KEY_FILE= node proba-x402-testnet.mjs [--rpc URL] [--facilitator URL] import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { definePolicy } from '../agent-policy.mjs'; import { newAgentIdentity, openLedger } from '../agent-ledger.mjs'; import { createWallet, assetId, nonceForEntry, incarcaEthers } from './wallet.mjs'; import { payWithAgent } from './client.mjs'; import { createResourceServer } from './resource-server.mjs'; import { verificaPlati } from './verifica-plati.mjs'; const AICI = path.dirname(fileURLToPath(import.meta.url)); const a = process.argv.slice(2); const get = (f, d) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : d; }; const RPC = get('--rpc', 'https://testnet-rpc.aere.network'); const FAC = get('--facilitator', 'https://testnet-rpc.aere.network/x402'); const NET = 'eip155:28001'; const TOKEN = { address: '0x8215bA247a3574af8EBC36606eB437811E318FBd', name: 'AereTestUSD', version: '2' }; const ethers = incarcaEthers(); let ok = 0, rau = 0; const cer = (c, ce) => { console.log((c ? ' OK ' : ' RAU ') + ce); c ? ok++ : rau++; return c; }; const taie = (s) => String(s || '').replace(/(0x)?[0-9a-fA-F]{60,}/g, '').slice(0, 160); const kf = process.env.AERE_TESTNET_KEY_FILE; if (!kf || !fs.existsSync(kf)) { console.log('NEMASURAT: AERE_TESTNET_KEY_FILE nu numeste un fisier cu cheia de dezvoltator a testnetului'); process.exit(2); } const rand = fs.readFileSync(kf, 'utf8').split(/\r?\n/).map((l) => l.trim()).find((l) => /^(d|PRIVATE_KEY)=/.test(l)) || ''; const hex = rand.replace(/^(d|PRIVATE_KEY)=/, '').replace(/^0x/, '').trim(); if (!/^[0-9a-fA-F]{1,64}$/.test(hex)) { console.log('NEMASURAT: fisierul cheii nu are un rand d= sau PRIVATE_KEY=0x'); process.exit(2); } const provider = new ethers.JsonRpcProvider(RPC, undefined, { staticNetwork: false }); const lant = Number((await provider.getNetwork()).chainId); if (lant !== 28001) { console.log(`REFUZ: RPC-ul serveste lantul ${lant}; proba ruleaza numai pe testnetul 28001`); process.exit(2); } const dev = new ethers.Wallet('0x' + hex.padStart(64, '0'), provider); const tUSD = new ethers.Contract(TOKEN.address, ['function transfer(address,uint256) returns (bool)', 'function balanceOf(address) view returns (uint256)', 'function authorizationState(address,bytes32) view returns (bool)'], dev); try { // 1. portofelul nou, finantat const cheie = ethers.Wallet.createRandom(); const payee = ethers.Wallet.createRandom().address; const tx0 = await tUSD.transfer(cheie.address, 50000n); const rc0 = await tx0.wait(1, 120000); cer(rc0 && rc0.status === 1, `1. portofelul nou ${cheie.address} primeste 0,05 tUSD (bloc ${rc0 && rc0.blockNumber})`); // 2. agentul si politica lui const agent = newAgentIdentity(); const { policy, policyHash } = definePolicy({ agentId: agent.agentId, spend: { amount: '30000', windowSeconds: 3600, asset: assetId(NET, TOKEN.address) }, recipients: [payee], wallet: cheie.address }); const wallet = createWallet({ policy, policyHash, evmPrivateKey: cheie.privateKey, network: NET, token: TOKEN }); const L = openLedger({ identity: agent, policy, policyHash }); const cerinta = { scheme: 'exact', network: NET, amount: '10000', asset: TOKEN.address, payTo: payee, maxTimeoutSeconds: 120, extra: { name: TOKEN.name, version: TOKEN.version } }; const rs = createResourceServer({ requirement: cerinta, facilitator: FAC, content: 'the paid content' }); const url = await rs.listen(); // 3. trei cumparaturi platite, a patra refuzata const plati = []; for (let i = 0; i < 3; i++) plati.push(await payWithAgent({ url, ledger: L, wallet })); cer(plati.every((p) => p.paid && p.status === 200 && p.body === 'the paid content' && /^0x[0-9a-f]{64}$/.test(p.settlement.transaction || '')), `3. trei cumparaturi de 0,01 tUSD platite prin facilitatorul testnetului si servite (${plati.map((p) => p.status + (p.reason ? ' ' + taie(p.reason) : '')).join('; ')})`); const p4 = await payWithAgent({ url, ledger: L, wallet }); cer(!p4.paid && /policy refused.*over the limit/.test(p4.reason || '') && wallet.status().signed === 3, `3. CONTROL: a patra depaseste 0,03 pe ora: refuzata de politica, portofelul a semnat tot 3 (${taie(p4.reason)})`); // reluarea: acelasi PaymentPayload pe care serverul l-a primit la prima plata const prima = rs.lastPayloads ? rs.lastPayloads[0] : null; if (prima) { const r = await fetch(url, { headers: { 'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(prima)).toString('base64') } }); cer(r.status === 402 && /nonce_already_used|already/.test(await r.text()), '3. CONTROL: prima plata trimisa din nou -> 402 (autorizarea e deja folosita pe lant)'); } else cer(false, '3. serverul de resurse nu a pastrat plata primita (lastPayloads)'); rs.server.close(); // 4. pe lant await new Promise((r) => setTimeout(r, 2000)); const soldPayee = await tUSD.balanceOf(payee), soldPortofel = await tUSD.balanceOf(cheie.address); cer(soldPayee === 30000n && soldPortofel === 20000n, `4. pe lant: vanzatorul are 30000, portofelul 20000 (${soldPayee}, ${soldPortofel})`); const folosite = await Promise.all(plati.map((p) => tUSD.authorizationState(cheie.address, nonceForEntry(p.entry.hash)))); cer(folosite.every(Boolean), '4. pe lant: autorizarea cu nonce-ul sha256(hash-ul intrarii) e folosita, pentru fiecare plata'); // 5. dosarul-dovada, verificat din afara const dovada = { v: 1, kind: 'aere-agent-x402-payments', network: NET, token: TOKEN.address, wallet: cheie.address, fromBlock: rc0.blockNumber, facilitator: FAC, policy, policyHash, ledger: L.export(), payments: plati.map((p) => ({ entrySeq: p.entry.seq, entryHash: p.entry.hash, transaction: p.settlement.transaction })), createdAt: new Date().toISOString() }; const dir = path.join(AICI, 'dovezi-28001'); fs.mkdirSync(dir, { recursive: true }); const f = path.join(dir, `plati-agent-${dovada.createdAt.slice(0, 19).replace(/[:T]/g, '-')}.json`); fs.writeFileSync(f, JSON.stringify(dovada, null, 1) + '\n'); const v = await verificaPlati(dovada, { rpc: RPC, allTransfers: true }); cer(v.verdict === 'VALID', `5. verifica-plati pe dosarul-dovada: ${v.verdict} (${v.checks.length} verificari, printre ele: orice Transfer din portofel e o plata din registru)`); const stricat = JSON.parse(JSON.stringify(dovada)); stricat.ledger.entries[plati[1].entry.seq].body.action.amount = '1'; const vs = await verificaPlati(stricat, { rpc: RPC }); cer(vs.verdict === 'INVALID', `5. CONTROL: o suma schimbata in registrul din dosar -> ${vs.verdict}`); const fara = JSON.parse(JSON.stringify(dovada)); fara.payments = fara.payments.slice(1); const vf = await verificaPlati(fara, { rpc: RPC, allTransfers: true }); cer(vf.verdict === 'INVALID', `5. CONTROL: o plata de pe lant scoasa din dosar -> ${vf.verdict} (un Transfer din portofel fara intrare)`); console.log(` dosarul-dovada: ${path.relative(process.cwd(), f)}`); } catch (e) { cer(false, `proba s-a oprit: ${taie(e.shortMessage || e.message)}`); } console.log(`\nagent-x402-testnet: ${ok}/${ok + rau} ${rau ? 'CU ESECURI' : 'cum trebuia'}`); process.exitCode = rau ? 1 : 0;