// agent-402-pqc-client.js // // Drop-in example: agent-side AERE402FacilitatorPQC client. The agent's spending // authority is rooted in a quantum-durable Falcon key (an AereAgentDID); each payment // is authorized by a cheap, short-lived, revocable secp256k1 SESSION key. Rotating or // revoking the Falcon root instantly halts every downstream payment. // // The agent calls a paid endpoint, receives 402 + a PQC quote, signs the DID action // digest with its SESSION key (a single ecrecover on-chain), and retries. The Falcon // root is never touched on the payment hot path — it only ever issues sessions. // // Run: // yarn add ethers // SESSION_PRIVATE_KEY=0x... ENDPOINT=http://localhost:3000/v1/inference node agent-402-pqc-client.js // // Prereq: an AereAgentDID agent (Falcon root) exists, a session is issued for // SESSION_PRIVATE_KEY's address (with scope + spend cap + expiry), and the agent's // AERE402FacilitatorPQC vault is funded. The server issues the 402 quote; the agent only // needs its session key here. const { ethers } = require('ethers'); const SESSION_PK = process.env.SESSION_PRIVATE_KEY; const ENDPOINT = process.env.ENDPOINT ?? 'http://localhost:3000/v1/inference'; if (!SESSION_PK) { console.error('SESSION_PRIVATE_KEY env required'); process.exit(1); } // Domain tags — mirror the contracts exactly. const PAYMENT_DOMAIN = ethers.keccak256(ethers.toUtf8Bytes('AERE402FacilitatorPQC.v1.payment')); const ACTION_DOMAIN = ethers.keccak256(ethers.toUtf8Bytes('AereAgentDID.v1.action')); // keccak256(abi.encode(PAYMENT_DOMAIN, chainId, facilitator, token, payee, resourceId, deadline)) function paymentActionHash(q) { const enc = ethers.AbiCoder.defaultAbiCoder().encode( ['bytes32', 'uint256', 'address', 'address', 'address', 'bytes32', 'uint256'], [PAYMENT_DOMAIN, q.chainId, q.facilitator, q.token, q.payee, q.resourceId, BigInt(q.deadline)], ); return ethers.keccak256(enc); } // keccak256(abi.encode(ACTION_DOMAIN, chainId, did, sessionId, scope, actionHash, amount, actionNonce)) function actionDigest(q, actionHash) { const enc = ethers.AbiCoder.defaultAbiCoder().encode( ['bytes32', 'uint256', 'address', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint64'], [ACTION_DOMAIN, q.chainId, q.did, BigInt(q.sessionId), q.scope, actionHash, BigInt(q.amount), BigInt(q.actionNonce)], ); return ethers.keccak256(enc); } const session = new ethers.SigningKey(SESSION_PK); (async () => { console.log(`session ${ethers.computeAddress(session)} calling ${ENDPOINT}...`); // 1. Unpaid request. const r1 = await fetch(ENDPOINT); if (r1.status !== 402) { console.log(`unexpected status ${r1.status} on first call`); console.log(await r1.text()); return; } const quoteJson = r1.headers.get('x-aere402pqc-quote'); if (!quoteJson) throw new Error('server did not return X-AERE402PQC-Quote'); const quote = JSON.parse(quoteJson); // quote = { chainId, facilitator, did, sessionId, scope, token, payee, amount, // resourceId, deadline, actionNonce } console.log(' received quote:', quote); // 2. Derive the actionHash (binds the payment to THIS facilitator + terms) and the DID // action digest the session key must sign. const actionHash = paymentActionHash(quote); const digest = actionDigest(quote, actionHash); // 3. Sign the raw digest with the SESSION key (canonical low-s; matches on-chain ecrecover). const signature = session.sign(digest).serialized; console.log(` signed: ${signature.slice(0, 18)}...`); // 4. Retry with the signature. The provider POSTs // settle(sessionId, scope, token, payee, amount, resourceId, deadline, signature) // to AERE402FacilitatorPQC, which enforces the Falcon root lifecycle + scope + spend // cap + this session signature + anti-replay in one call, then pays out. const r2 = await fetch(ENDPOINT, { headers: { 'X-AERE402PQC-Sig': signature, 'X-AERE402PQC-Quote': quoteJson, }, }); console.log(` retry status: ${r2.status}`); console.log(` tx hash: ${r2.headers.get('x-aere402pqc-txhash')}`); console.log(' body:', await r2.json()); })();