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).
21 lines
1.8 KiB
JavaScript
21 lines
1.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// inregistreaza-rpc.mjs: scoate de pe lant, O DATA, raspunsurile RPC de care are nevoie verifica-plati.mjs pentru un dosar-dovada
|
|
// (eth_chainId, chitanta fiecarei plati, Transfer-urile din portofel), ca proba verificatorului sa ruleze apoi fara retea pe date REALE.
|
|
// node inregistreaza-rpc.mjs <dovada.json> --rpc <url> --out <rpc-inregistrat.json>
|
|
import fs from 'node:fs';
|
|
import { incarcaEthers } from './wallet.mjs';
|
|
|
|
const a = process.argv.slice(2); const get = (f) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : undefined; };
|
|
const f = a[0]; const rpc = get('--rpc'); const out = get('--out');
|
|
if (!f || !rpc || !out) { console.error('usage: node inregistreaza-rpc.mjs <evidence.json> --rpc <url> --out <file>'); process.exit(2); }
|
|
const ethers = incarcaEthers();
|
|
const d = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
const apel = async (method, params) => { const r = await fetch(rpc, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(j.error.message); return j.result; };
|
|
const chainId = await apel('eth_chainId', []);
|
|
const receipts = {};
|
|
for (const p of d.payments) receipts[p.transaction.toLowerCase()] = await apel('eth_getTransactionReceipt', [p.transaction]);
|
|
const logs = await apel('eth_getLogs', [{ address: d.token, fromBlock: '0x' + Number(d.fromBlock).toString(16), toBlock: 'latest',
|
|
topics: [ethers.id('Transfer(address,address,uint256)'), '0x' + '0'.repeat(24) + d.wallet.toLowerCase().slice(2)] }]);
|
|
fs.writeFileSync(out, JSON.stringify({ v: 1, recordedAt: new Date().toISOString(), rpc, chainId, receipts, transfersOut: logs }, null, 1) + '\n');
|
|
console.log(`recorded chain ${Number(chainId)}: ${Object.keys(receipts).length} receipts, ${logs.length} transfers out of the wallet -> ${out}`);
|