// 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, nonceForEntry, incarcaEthers, EIP3009_TYPES } 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; } } /** * Modul cosign (portofel-contract 2-din-2): agentul isi adauga jumatatea de semnatura NUMAI dupa ce recalculeaza singur ce semneaza. * Un serviciu de politica compromis nu poate face agentul sa semneze alta plata decat cea din intrarea lui: se cer platitorul (contractul), * destinatarul si suma din cerinta, nonce-ul = sha256(hash-ul intrarii), un termen marginit, digestul recalculat local, si jumatatea * serviciului recuperata la semnatarul de politica declarat. */ export function completeazaCosemnarea({ payload, req, status, entryHash, agentEvmKey, cosign, now = Math.floor(Date.now() / 1000) }) { const ethers = incarcaEthers(); const a = payload && payload.payload && payload.payload.authorization; if (!a) return { ok: false, error: 'the wallet returned no authorization' }; const eq = (x, y) => String(x).toLowerCase() === String(y).toLowerCase(); if (!eq(a.from, status.address)) return { ok: false, error: 'the authorization is not from the 2-of-2 wallet' }; if (!eq(a.to, req.payTo) || String(a.value) !== String(req.amount)) return { ok: false, error: 'the authorization pays another recipient or amount than the purchase' }; if (!eq(a.nonce, nonceForEntry(entryHash))) return { ok: false, error: "the authorization nonce is not the agent's ledger entry" }; if (!(Number(a.validBefore) > now && Number(a.validBefore) <= now + Number(req.maxTimeoutSeconds) + 60)) return { ok: false, error: 'the authorization validity is not the requirement timeout' }; const domeniu = { name: (req.extra && req.extra.name) || status.tokenName, version: (req.extra && req.extra.version) || status.tokenVersion, chainId: Number(String(req.network).split(':')[1]), verifyingContract: ethers.getAddress(req.asset) }; const digest = ethers.TypedDataEncoder.hash(domeniu, EIP3009_TYPES, { ...a, value: BigInt(a.value), validAfter: BigInt(a.validAfter), validBefore: BigInt(a.validBefore) }); if (!cosign || digest !== cosign.digest) return { ok: false, error: 'the digest the wallet signed is not the one the agent computes' }; let semnatarPolitica = null; try { semnatarPolitica = ethers.recoverAddress(digest, cosign.policySignature); } catch { semnatarPolitica = null; } if (!semnatarPolitica || !eq(semnatarPolitica, status.policySigner)) return { ok: false, error: 'the policy half is not signed by the declared policy signer' }; const agent = typeof agentEvmKey === 'string' ? new ethers.Wallet(agentEvmKey) : agentEvmKey; const signature = ethers.concat([agent.signingKey.sign(digest).serialized, cosign.policySignature]); const complet = { ...payload, payload: { ...payload.payload, signature } }; return { ok: true, header: Buffer.from(JSON.stringify(complet), 'utf8').toString('base64'), paymentPayload: complet }; } /** 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 {string|object} [o.agentEvmKey] cheia secp256k1 a agentului, ceruta numai de un portofel-contract 2-din-2 (modul cosign) * @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 = [], agentEvmKey = null, 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 }; // 2b. portofelul-contract 2-din-2: agentul verifica si isi adauga jumatatea let header = a.header; if (a.cosign) { if (!agentEvmKey) return { paid: false, status: 402, reason: "the wallet is a 2-of-2 contract and needs the agent's signature: agentEvmKey is missing", entry: r.entry }; const c = completeazaCosemnarea({ payload: a.paymentPayload, req, status: w, entryHash: r.entry.hash, agentEvmKey, cosign: a.cosign }); if (!c.ok) return { paid: false, status: 402, reason: `the agent refused to co-sign: ${c.error}`, entry: r.entry }; header = c.header; } // 3. cererea reluata, cu plata const r1 = await fetchImpl(url, { headers: { 'PAYMENT-SIGNATURE': 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)}` }) }; }