aere-quantum/agents/x402/client.mjs
Aere Network 2293c1da86 agents/x402: an agent wallet that pays over x402 only what the agent's ledger records and its policy allows, run on the public testnet
The agent does not hold the payment key: the wallet holds it for the owner and signs an EIP-3009 authorization only for a payment the
agent wrote into its signed ledger, verified without trusting the agent under the policy the owner pinned and against the ledger heads
the wallet itself saw (a branch is refused with a proof of equivocation, a backdated entry is refused), naming exactly this purchase,
written now, and within the limit judged also against what the wallet itself has signed. The authorization nonce is sha256(entry hash),
so the on-chain payment names the ledger entry. The policy gains an optional `wallet` field. Also: an x402 v2 client, a minimal resource
server, a local facilitator for tests, and verifica-plati.mjs, which proves from outside that a wallet's on-chain payments were allowed
by the agent's policy (with --all-transfers, that no payment left the wallet without a ledger entry).

Tests: wallet 25/25 and payment verifier 10/10 without a network (the verifier on chain responses recorded on testnet 28001), negative
control 21/21; policy 27/27, agents control 26/26. On the public testnet 28001 through its x402 facilitator: 9/9, with the evidence in
agents/x402/dovezi-28001/. Needs ethers (npm install in agents/x402).
2026-09-30 00:01:34 +03:00

49 lines
3.7 KiB
JavaScript

// Clientul x402 al unui agent AI (2026-09-29, punctele 23, 25): cumpara o resursa HTTP care cere plata (402), scriind INTAI plata in
// registrul agentului, apoi cerand portofelului (wallet.mjs) semnatura, apoi reluand cererea cu antetul PAYMENT-SIGNATURE. Numele
// antetelor si forma lor sunt ale specificatiei x402 v2 (transportul HTTP): PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE, toate
// JSON in base64. Daca politica refuza plata, clientul se opreste inainte de portofel (si portofelul ar refuza oricum).
import { x402Action } from './wallet.mjs';
export const b64json = (o) => Buffer.from(JSON.stringify(o), 'utf8').toString('base64');
export function dinB64json(s) { try { return JSON.parse(Buffer.from(String(s), 'base64').toString('utf8')); } catch { return null; } }
/** portofelul prin HTTP (serviciul wallet.mjs serve), cu aceeasi forma ca obiectul din createWallet */
export function walletOverHttp(baseUrl, fetchImpl = fetch) {
const b = String(baseUrl).replace(/\/+$/, '');
return {
async status() { const r = await fetchImpl(b + '/status'); return r.json(); },
async authorize(body) { const r = await fetchImpl(b + '/authorize', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }); return r.json(); },
};
}
/**
* @param {object} o
* @param {string} o.url resursa
* @param {object} o.ledger registrul agentului (openLedger / resumeLedger)
* @param {object} o.wallet portofelul (createWallet sau walletOverHttp)
* @param {Array} [o.approvals] aprobari umane pentru aceasta plata, cand politica le cere
* @param {Function} [o.fetchImpl]
* @returns {Promise<{paid:boolean, status:number, reason?:string, body?:string, settlement?:object, entry?:object, receipt?:object, decision?:object}>}
*/
export async function payWithAgent({ url, ledger, wallet, approvals = [], fetchImpl = fetch }) {
const r0 = await fetchImpl(url);
if (r0.status !== 402) return { paid: false, status: r0.status, body: await r0.text(), reason: r0.ok ? 'no payment was required' : `the resource answered ${r0.status}` };
const pr = dinB64json(r0.headers.get('payment-required')) || (await r0.json().catch(() => null));
if (!pr || pr.x402Version !== 2 || !Array.isArray(pr.accepts)) return { paid: false, status: 402, reason: 'the 402 response carries no x402 v2 PaymentRequired' };
const w = await wallet.status();
const req = pr.accepts.find((a) => a.scheme === 'exact' && a.network === w.network && String(a.asset).toLowerCase() === String(w.asset).toLowerCase());
if (!req) return { paid: false, status: 402, reason: `no accepted payment is in this wallet's asset on ${w.network}` };
// 1. plata, in registru, inainte de orice semnatura
const r = ledger.record(x402Action(w.address, pr.resource || null, req), { approvals });
if (!r.allowed) return { paid: false, status: 402, reason: `the agent's policy refused the payment: ${r.reason}`, entry: r.entry };
// 2. semnatura portofelului, care judeca din nou tot registrul
const a = await wallet.authorize({ requirements: req, resource: pr.resource || null, ledger: ledger.export() });
if (!a.ok) return { paid: false, status: 402, reason: `the wallet refused: ${a.error}`, entry: r.entry, equivocation: a.equivocation };
// 3. cererea reluata, cu plata
const r1 = await fetchImpl(url, { headers: { 'PAYMENT-SIGNATURE': a.header } });
const settlement = dinB64json(r1.headers.get('payment-response'));
const body = await r1.text();
return { paid: r1.ok && !!(settlement && settlement.success), status: r1.status, body, settlement, entry: r.entry, receipt: a.receipt, decision: a.decision,
...(r1.ok ? {} : { reason: `the resource answered ${r1.status} after payment: ${body.slice(0, 160)}` }) };
}