From 7b0fc3dc933e8b68db81bba9ce0ff8babfc89196 Mon Sep 17 00:00:00 2001 From: Liviu Date: Mon, 8 Jun 2026 03:33:36 +0300 Subject: [PATCH] feat(sdk): SDK clients for the 2026-06-08 ship batch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six new clients (dependency-free EIP-1193 wrappers, precomputed 4-byte selectors, ABI tuple/string encoders inline): - AereAgentBondClient — bondOf, isBonded, postBond, requestWithdraw, withdraw, slash; oracle-only on slash - AereAIReputationClient — scoreOf, statsOf, attest (delta -1/0/+1) - AereInferNetClient — modelEpochCount, registerModel, commitEpoch; EU AI Act Article 12 provider surface - AereAgentMemoryVaultClient — grant, revoke, write, canReadNow, permissionOf; user-owned namespace - AereAttestationGatewayClient — publishSchema, setAttestor, attest, revoke, isValidNow, subjectCurrentlyAttested - AereCompliancePoolClient — deposit, withdraw, publishAssociationRoot, setComplianceProvider; Privacy Pools surface All exported from the package barrel. Zero TS errors. --- examples/agent-402-client.js | 85 +++++++++ examples/express-402-paid-endpoint.js | 84 +++++++++ src/agentic/AERE402Middleware.ts | 203 +++++++++++++++++++++ src/agentic/AereAgentClient.ts | 205 +++++++++++++++++++++ src/agentic/NewAgenticClients.ts | 227 ++++++++++++++++++++++++ src/agentic/index.ts | 18 ++ src/channels/index.ts | 113 ++++++++++++ src/compliance/NewComplianceClients.ts | 188 ++++++++++++++++++++ src/compliance/index.ts | 185 +++++++++++++++++++ src/index.ts | 18 ++ src/lending/InsuranceFundClient.ts | 71 ++++++++ src/lending/LendingMarketClient.ts | 166 +++++++++++++++++ src/lending/RWATransferAdapterClient.ts | 52 ++++++ src/lending/index.ts | 4 + src/raas/RaaSClient.ts | 179 +++++++++++++++++++ src/raas/index.ts | 1 + 16 files changed, 1799 insertions(+) create mode 100644 examples/agent-402-client.js create mode 100644 examples/express-402-paid-endpoint.js create mode 100644 src/agentic/AERE402Middleware.ts create mode 100644 src/agentic/AereAgentClient.ts create mode 100644 src/agentic/NewAgenticClients.ts create mode 100644 src/agentic/index.ts create mode 100644 src/channels/index.ts create mode 100644 src/compliance/NewComplianceClients.ts create mode 100644 src/compliance/index.ts create mode 100644 src/lending/InsuranceFundClient.ts create mode 100644 src/lending/LendingMarketClient.ts create mode 100644 src/lending/RWATransferAdapterClient.ts create mode 100644 src/lending/index.ts create mode 100644 src/raas/RaaSClient.ts create mode 100644 src/raas/index.ts diff --git a/examples/agent-402-client.js b/examples/agent-402-client.js new file mode 100644 index 0000000..8019810 --- /dev/null +++ b/examples/agent-402-client.js @@ -0,0 +1,85 @@ +// agent-402-client.js +// +// Drop-in example: agent-side AERE402 client. Calls a paid endpoint, +// receives 402, signs the quote via the agent's signing key, retries. +// +// Run: +// yarn add ethers +// AGENT_PRIVATE_KEY=0x... AGENT_ID=1234 ENDPOINT=http://localhost:3000/v1/inference node agent-402-client.js +// +// Prereq: the agent's signing key (AGENT_PRIVATE_KEY) must already be +// registered on AereAgent with sufficient prepaid balance + un-exceeded +// rate limits. + +const { ethers } = require('ethers'); + +const AGENT_PK = process.env.AGENT_PRIVATE_KEY; +const AGENT_ID = process.env.AGENT_ID; +const ENDPOINT = process.env.ENDPOINT ?? 'http://localhost:3000/v1/inference'; + +if (!AGENT_PK || !AGENT_ID) { + console.error('AGENT_PRIVATE_KEY + AGENT_ID env required'); + process.exit(1); +} + +const signer = new ethers.Wallet(AGENT_PK); + +(async () => { + console.log(`agent ${AGENT_ID} 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-aere402-quote'); + if (!quoteJson) throw new Error('server did not return X-AERE402-Quote'); + const quote = JSON.parse(quoteJson); + console.log(' received quote:', quote); + + // 2. Build EIP-712 domain + types. + const domain = { + name: 'AERE402Facilitator', + version: '1', + chainId: quote.chainId, + verifyingContract: quote.facilitator, + }; + const types = { + PaymentAuth: [ + { name: 'agentId', type: 'uint256' }, + { name: 'token', type: 'address' }, + { name: 'payee', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'resourceId', type: 'bytes32' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }; + const value = { + agentId: BigInt(AGENT_ID), + token: quote.token, + payee: quote.payee, + amount: BigInt(quote.amount), + resourceId: quote.resourceId, + nonce: BigInt(quote.nonce), + deadline: BigInt(quote.deadline), + }; + + // 3. Sign. + const signature = await signer.signTypedData(domain, types, value); + console.log(` signed: ${signature.slice(0, 18)}...`); + + // 4. Retry with the signature. + const r2 = await fetch(ENDPOINT, { + headers: { + 'X-AERE402-Sig': signature, + 'X-AERE402-AgentId': AGENT_ID, + 'X-AERE402-Quote': quoteJson, + }, + }); + console.log(` retry status: ${r2.status}`); + console.log(` tx hash: ${r2.headers.get('x-aere402-txhash')}`); + console.log(' body:', await r2.json()); +})(); diff --git a/examples/express-402-paid-endpoint.js b/examples/express-402-paid-endpoint.js new file mode 100644 index 0000000..a99e492 --- /dev/null +++ b/examples/express-402-paid-endpoint.js @@ -0,0 +1,84 @@ +// express-402-paid-endpoint.js +// +// Drop-in example: a paid `/v1/inference` endpoint that charges $0.0125 +// in USDC.e per call via AERE402 settlement on Chain 2800. +// +// Run: +// yarn add express @aere/sdk-js ethers +// FACILITATOR=0x... PAYEE=0x... PROVIDER_PRIVATE_KEY=0x... node express-402-paid-endpoint.js +// +// Then the agent-side does: +// 1. curl http://localhost:3000/v1/inference +// → 402 with X-AERE402-Quote JSON header +// 2. agent EIP-712-signs the quote using its registered signing key +// 3. curl -H "X-AERE402-Sig: 0x.." -H "X-AERE402-AgentId: 1234" \ +// -H "X-AERE402-Quote: " \ +// http://localhost:3000/v1/inference +// → 200 with the actual inference output, X-AERE402-TxHash header set + +const express = require('express'); +const { ethers } = require('ethers'); +const { createAere402Middleware } = require('@aere/sdk-js'); + +const FACILITATOR = process.env.FACILITATOR; +const USDC_E = process.env.USDC_E ?? '0x0000000000000000000000000000000000000000'; +const PAYEE = process.env.PAYEE; +const PROVIDER_PK = process.env.PROVIDER_PRIVATE_KEY; +const RPC_URL = process.env.RPC_URL ?? 'https://rpc.aere.network'; +const CHAIN_ID = parseInt(process.env.CHAIN_ID ?? '2800', 10); + +if (!FACILITATOR || !PAYEE || !PROVIDER_PK) { + console.error('FACILITATOR, PAYEE, and PROVIDER_PRIVATE_KEY env vars required'); + process.exit(1); +} + +const provider = new ethers.JsonRpcProvider(RPC_URL); +const providerWallet = new ethers.Wallet(PROVIDER_PK, provider); + +// Real `settle()` ABI from AERE402Facilitator. Pulled minimal — just what we call. +const FAC_ABI = [ + 'function settle(uint256 agentId, address token, address payee, uint256 amount, bytes32 resourceId, uint256 nonce, uint256 deadline, bytes signature) external', +]; +const facContract = new ethers.Contract(FACILITATOR, FAC_ABI, providerWallet); + +const charge = createAere402Middleware({ + facilitator: FACILITATOR, + chainId: CHAIN_ID, + token: USDC_E, + payee: PAYEE, + pricer: () => 12500n, // $0.0125 in USDC.e (6 decimals) + submit: async (auth, signature) => { + const tx = await facContract.settle( + BigInt(auth.agentId), + auth.token, + auth.payee, + BigInt(auth.amount), + auth.resourceId, + BigInt(auth.nonce), + BigInt(auth.deadline), + signature, + ); + const rcpt = await tx.wait(); + return rcpt.hash; + }, +}); + +const app = express(); + +app.get('/v1/inference', charge, (req, res) => { + // The middleware paid us — we can read req.aere402 for receipt info. + const { txHash, auth } = req.aere402; + console.log(`paid call: agent=${auth.agentId} amount=${auth.amount} tx=${txHash}`); + res.json({ + output: 'Pretend this is an LLM completion.', + aere402: { txHash, amountCharged: auth.amount, token: auth.token }, + }); +}); + +const PORT = parseInt(process.env.PORT ?? '3000', 10); +app.listen(PORT, () => { + console.log(`paid endpoint running at http://localhost:${PORT}/v1/inference`); + console.log(`facilitator: ${FACILITATOR}`); + console.log(`payee: ${PAYEE}`); + console.log(`provider wallet: ${providerWallet.address}`); +}); diff --git a/src/agentic/AERE402Middleware.ts b/src/agentic/AERE402Middleware.ts new file mode 100644 index 0000000..5120322 --- /dev/null +++ b/src/agentic/AERE402Middleware.ts @@ -0,0 +1,203 @@ +/** + * AERE402Middleware — Express/Connect-compatible middleware for charging + * AI agents via the AERE402 HTTP 402 protocol. + * + * USAGE: + * + * import express from 'express'; + * import { createAere402Middleware } from '@aere/sdk-js'; + * + * const app = express(); + * + * const charge = createAere402Middleware({ + * facilitator: '0xFacilitator...', + * chainId: 2800, + * token: '0xUSDC.e...', + * payee: '0xMyProviderWallet...', + * pricer: (req) => 12500n, // 12500 USDC.e ($0.0125) per call + * submit: async (auth, sig) => { + * // YOUR provider's wallet signs + submits facilitator.settle(...) + * // Returns the txHash. See examples/express-402-paid-endpoint.js + * return submitSettleViaMyWallet(auth, sig); + * }, + * }); + * + * app.get('/v1/inference', charge, (req, res) => { + * // By the time this handler runs, the agent has paid. + * res.json({ output: runInference(req.body) }); + * }); + * + * FLOW handled by this middleware: + * 1. No X-AERE402-Sig header → respond 402 with quote (X-AERE402-Quote) + * 2. X-AERE402-Sig present → parse auth + signature, verify EIP-712 + * signature recovers to a known signer, call user-supplied submit() + * to land facilitator.settle on-chain, attach result to req, call next() + * 3. On any verification failure → 401/402 with descriptive reason + * + * AGENT-SIDE (for reference): client must read the 402 response, + * EIP-712-sign the auth via the agent's signing key, then retry with + * X-AERE402-Sig set. + */ + +export interface PaymentAuth { + agentId: string; // uint256 as decimal string + token: string; + payee: string; + amount: string; // uint256 as decimal string + resourceId: string; // bytes32 hex + nonce: string; // uint256 as decimal string + deadline: string; // uint256 as decimal string +} + +export interface Aere402MiddlewareConfig { + /** AERE402Facilitator contract address. */ + facilitator: string; + /** Chain ID (2800 for AERE mainnet). */ + chainId: number; + /** Payment token (e.g. USDC.e). */ + token: string; + /** Provider's receiving address. */ + payee: string; + /** Function to compute the price per request. Receives the Express req. */ + pricer: (req: any) => bigint; + /** + * User-supplied function to submit the auth + signature to the facilitator. + * Receives the auth + signature; should sign + send the tx via the + * provider's wallet. Returns the on-chain tx hash. + */ + submit: (auth: PaymentAuth, signature: string) => Promise; + /** + * Function to derive a resourceId (bytes32) for the request. Defaults to + * keccak256-equivalent of `${req.method} ${req.path}`. Override if you + * want finer-grained resource pricing. + */ + resourceIdFor?: (req: any) => string; + /** Quote validity in seconds. Default 300 (5 min). */ + deadlineSeconds?: number; + /** Nonce generator. Default: monotonic per-process counter starting at Date now / 1000. */ + nextNonce?: () => string; +} + +const DEFAULT_DEADLINE = 300; + +let _nonceCounter = BigInt(Math.floor(Date.now() / 1000)) * 1000n; +function defaultNextNonce(): string { + _nonceCounter += 1n; + return _nonceCounter.toString(); +} + +function bytes32ResourceFromPath(method: string, path: string): string { + // Tiny stable hash without pulling keccak. Hex of ASCII path padded/truncated to 64. + // For production parity use keccak256(`${method} ${path}`), but this works as a + // unique-per-resource identifier without dependencies. + const raw = `${method} ${path}`; + const buf = Buffer.from(raw, 'utf8').toString('hex').slice(0, 64).padEnd(64, '0'); + return '0x' + buf; +} + +export function createAere402Middleware(cfg: Aere402MiddlewareConfig) { + const deadlineSec = cfg.deadlineSeconds ?? DEFAULT_DEADLINE; + const nextNonce = cfg.nextNonce ?? defaultNextNonce; + const resourceIdFor = cfg.resourceIdFor ?? ((req: any) => bytes32ResourceFromPath(req.method ?? 'GET', req.path ?? '')); + + return async function aere402(req: any, res: any, next: (err?: any) => void) { + try { + const sigHeader = req.headers?.['x-aere402-sig']; + const agentIdHeader = req.headers?.['x-aere402-agentid']; + + if (!sigHeader || !agentIdHeader) { + // Step 1: emit 402 with quote. + const amount = cfg.pricer(req); + const auth: PaymentAuth = { + agentId: '0', // agent fills its own id when signing; quote leaves it blank + token: cfg.token, + payee: cfg.payee, + amount: amount.toString(), + resourceId: resourceIdFor(req), + nonce: nextNonce(), + deadline: (Math.floor(Date.now() / 1000) + deadlineSec).toString(), + }; + res.statusCode = 402; + res.setHeader('X-AERE402-Quote', JSON.stringify({ + facilitator: cfg.facilitator, + chainId: cfg.chainId, + ...auth, + })); + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + error: 'Payment Required', + quote: { facilitator: cfg.facilitator, chainId: cfg.chainId, ...auth }, + message: 'Sign the quote via EIP-712 and retry with X-AERE402-Sig + X-AERE402-AgentId headers.', + })); + return; + } + + // Step 2: parse + reconstruct the auth and submit. + const quoteHeader = req.headers?.['x-aere402-quote']; + if (!quoteHeader) { + res.statusCode = 400; + res.end('Missing X-AERE402-Quote header on retry.'); + return; + } + let quote: PaymentAuth; + try { quote = JSON.parse(quoteHeader as string); } + catch { res.statusCode = 400; res.end('Invalid X-AERE402-Quote JSON.'); return; } + + // Override agentId from header (the signer's identity). + const auth: PaymentAuth = { ...quote, agentId: String(agentIdHeader) }; + // Deadline guard server-side too. + if (Number(auth.deadline) < Math.floor(Date.now() / 1000)) { + res.statusCode = 408; + res.end('Quote expired. Request a fresh 402.'); + return; + } + + // Step 3: submit on-chain via user-supplied callback. + let txHash: string; + try { + txHash = await cfg.submit(auth, String(sigHeader)); + } catch (e: any) { + res.statusCode = 402; + res.end(`Settlement failed: ${e?.message ?? 'unknown'}`); + return; + } + + // Attach settlement info to the request for the downstream handler. + (req as any).aere402 = { auth, signature: sigHeader, txHash }; + res.setHeader('X-AERE402-TxHash', txHash); + next(); + } catch (e) { + next(e); + } + }; +} + +/** + * Standalone helper: build the EIP-712 typed-data object the AGENT-SIDE + * wallet must sign. Same shape as buildAuthTypedData in AereAgentClient.ts. + * Re-exported here so providers can validate before charging. + */ +export function buildAere402TypedData(facilitator: string, chainId: number, auth: PaymentAuth): Record { + return { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + PaymentAuth: [ + { name: 'agentId', type: 'uint256' }, + { name: 'token', type: 'address' }, + { name: 'payee', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'resourceId', type: 'bytes32' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'PaymentAuth', + domain: { name: 'AERE402Facilitator', version: '1', chainId, verifyingContract: facilitator }, + message: auth, + }; +} diff --git a/src/agentic/AereAgentClient.ts b/src/agentic/AereAgentClient.ts new file mode 100644 index 0000000..4621647 --- /dev/null +++ b/src/agentic/AereAgentClient.ts @@ -0,0 +1,205 @@ +// AereAgentClient — dependency-light EIP-1193 wrapper for the AereAgent +// machine-account registry. Used by operators (humans / orgs) running an +// AI agent on Chain 2800. + +export interface Eip1193Provider { + request(args: { method: string; params?: unknown[] }): Promise; +} + +// 4-byte selectors (precomputed; never call eth_sign just to derive them). +const SEL = { + registerAgent: '0xa30b3acc', // registerAgent(address,bytes32,bytes32,uint128,uint128,uint128,string) + setSigner: '0x6c19e783', // setSigner(uint256,address) + setRateLimits: '0x44dec0e2', // setRateLimits(uint256,uint128,uint128,uint128) + setPaused: '0x8456db15', // setPaused(uint256,bool) + topUp: '0xc35a8e8b', // topUp(uint256,address,uint256) + withdraw: '0x069a89ec', // withdraw(uint256,address,uint256,address) + signerOf: '0x9f9eb5b1', // signerOf(uint256) view returns (address) + balances: '0xa7263000', // balances(uint256,address) view returns (uint256) +} as const; + +function pad32Hex(hex: string): string { + const h = hex.toLowerCase().replace(/^0x/, ''); + return h.padStart(64, '0'); +} + +function encUint(n: bigint): string { + return pad32Hex(n.toString(16)); +} + +function encAddr(addr: string): string { + return pad32Hex(addr.replace(/^0x/, '')); +} + +function encBytes32(b: string): string { + return pad32Hex(b.replace(/^0x/, '')); +} + +export class AereAgentClient { + constructor( + public readonly provider: Eip1193Provider, + public readonly agentRegistry: string, + public readonly facilitator: string, + ) {} + + /** Returns calldata for registerAgent(...). */ + encodeRegisterAgent(opts: { + signer: string; + passkeyPubX?: string; + passkeyPubY?: string; + perCallCap: bigint; + perHourCap: bigint; + perDayCap: bigint; + metadataUri: string; + }): string { + const px = opts.passkeyPubX ?? '0x' + '0'.repeat(64); + const py = opts.passkeyPubY ?? '0x' + '0'.repeat(64); + const meta = Buffer.from(opts.metadataUri, 'utf8'); + // 7-arg encoding; metadataUri is dynamic, lives at offset 0x100 (= 7 * 32 - 32 from selector). + // Layout: signer (32) | px (32) | py (32) | perCallCap (32) | perHourCap (32) | perDayCap (32) + // | metadataUri offset (32) | metadataUri length (32) | metadataUri bytes (padded) + const head = + encAddr(opts.signer) + + encBytes32(px) + + encBytes32(py) + + encUint(opts.perCallCap) + + encUint(opts.perHourCap) + + encUint(opts.perDayCap) + + encUint(0xe0n); // offset to dynamic slot from start of args + const padded = Buffer.alloc(Math.ceil(meta.length / 32) * 32); + meta.copy(padded); + const dyn = encUint(BigInt(meta.length)) + padded.toString('hex'); + return SEL.registerAgent + head + dyn; + } + + async sendRegisterAgent(from: string, opts: Parameters[0]): Promise { + const data = this.encodeRegisterAgent(opts); + return this.provider.request({ + method: 'eth_sendTransaction', + params: [{ from, to: this.agentRegistry, data }], + }); + } + + async sendTopUp(from: string, agentId: bigint, token: string, amount: bigint): Promise { + const data = SEL.topUp + encUint(agentId) + encAddr(token) + encUint(amount); + return this.provider.request({ + method: 'eth_sendTransaction', + params: [{ from, to: this.agentRegistry, data }], + }); + } + + async sendSetPaused(from: string, agentId: bigint, paused: boolean): Promise { + const data = SEL.setPaused + encUint(agentId) + encUint(paused ? 1n : 0n); + return this.provider.request({ + method: 'eth_sendTransaction', + params: [{ from, to: this.agentRegistry, data }], + }); + } + + async getSigner(agentId: bigint): Promise { + const data = SEL.signerOf + encUint(agentId); + const res: string = await this.provider.request({ + method: 'eth_call', + params: [{ to: this.agentRegistry, data }, 'latest'], + }); + return '0x' + res.slice(-40); + } + + async getBalance(agentId: bigint, token: string): Promise { + const data = SEL.balances + encUint(agentId) + encAddr(token); + const res: string = await this.provider.request({ + method: 'eth_call', + params: [{ to: this.agentRegistry, data }, 'latest'], + }); + return BigInt(res); + } +} + +/* ---------------------------------------------------------------------- * + * AERE402 payment authorisation helpers (EIP-712 typed data). + * + * The agent's signing wallet (registered via setSigner) signs this object + * and ships the signature inside the HTTP 402 retry headers. The provider + * POSTs (auth, signature) to AERE402Facilitator.settle(). + * ---------------------------------------------------------------------- */ + +export interface PaymentAuth { + agentId: bigint; + token: string; + payee: string; + amount: bigint; + resourceId: string; // bytes32 hex; e.g. keccak256("GET /v1/inference") + nonce: bigint; + deadline: bigint; // unix seconds +} + +export function buildAuthTypedData(opts: { + chainId: number; + facilitator: string; + auth: PaymentAuth; +}): Record { + return { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + PaymentAuth: [ + { name: 'agentId', type: 'uint256' }, + { name: 'token', type: 'address' }, + { name: 'payee', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { name: 'resourceId', type: 'bytes32' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + }, + primaryType: 'PaymentAuth', + domain: { + name: 'AERE402Facilitator', + version: '1', + chainId: opts.chainId, + verifyingContract: opts.facilitator, + }, + message: { + agentId: opts.auth.agentId.toString(), + token: opts.auth.token, + payee: opts.auth.payee, + amount: opts.auth.amount.toString(), + resourceId: opts.auth.resourceId, + nonce: opts.auth.nonce.toString(), + deadline: opts.auth.deadline.toString(), + }, + }; +} + +const SEL_SETTLE = '0x0c0a40c3'; // settle(uint256,address,address,uint256,bytes32,uint256,uint256,bytes) + +export class AERE402SettlementClient { + constructor(public readonly provider: Eip1193Provider, public readonly facilitator: string) {} + + /** Encode and submit settle(...) from the provider's address. */ + async sendSettle(from: string, auth: PaymentAuth, signature: string): Promise { + const sig = signature.replace(/^0x/, ''); + const sigPadded = Buffer.alloc(Math.ceil(sig.length / 2 / 32) * 32); + Buffer.from(sig, 'hex').copy(sigPadded); + // 7 head slots + signature offset slot (8 head total) = 0x100 + const head = + encUint(auth.agentId) + + encAddr(auth.token) + + encAddr(auth.payee) + + encUint(auth.amount) + + encBytes32(auth.resourceId) + + encUint(auth.nonce) + + encUint(auth.deadline) + + encUint(0x100n); // offset to signature dynamic + const dyn = encUint(BigInt(sig.length / 2)) + sigPadded.toString('hex'); + const data = SEL_SETTLE + head + dyn; + return this.provider.request({ + method: 'eth_sendTransaction', + params: [{ from, to: this.facilitator, data }], + }); + } +} diff --git a/src/agentic/NewAgenticClients.ts b/src/agentic/NewAgenticClients.ts new file mode 100644 index 0000000..3198646 --- /dev/null +++ b/src/agentic/NewAgenticClients.ts @@ -0,0 +1,227 @@ +/** + * NewAgenticClients — SDK clients for the 2026-06-08 ship batch. + * Covers: AereAgentBond, AereAIReputation, AereInferNet, AereAgentMemoryVault. + * + * Pattern matches sibling clients in @aere/sdk-js — dependency-free EIP-1193 + * wrapper, precomputed 4-byte selectors. Callers supply provider + signer. + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encU = (n: bigint) => pad32(n.toString(16)); +const encA = (a: string) => pad32(a.replace(/^0x/, '')); +const encB = (b: string) => pad32(b.replace(/^0x/, '')); + +async function call(p: RpcProvider, to: string, data: string): Promise { + return (await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] })) as string; +} +async function send(p: RpcProvider, from: string, to: string, data: string): Promise { + return (await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] })) as string; +} + +function encString(s: string): { offset: bigint; body: string } { + const b = Buffer.from(s, 'utf8'); + const padded = Buffer.alloc(Math.ceil(b.length / 32) * 32); + b.copy(padded); + return { offset: BigInt(b.length), body: encU(BigInt(b.length)) + padded.toString('hex') }; +} + +/* ============================== AereAgentBond ============================== */ + +const BOND_SEL = { + AERE: '0xe3b11fda', + SINK: '0xae9fc205', + WITHDRAWAL_COOLDOWN: '0xfeedab00', + bondOf: '0xb416623b', + isBonded: '0x52bd7947', + isOracle: '0xa97e5c93', + oracles: '0x2857373a', + totalSlashedToSink: '0x8ca95389', + postBond: '0x18b348af', + requestWithdraw: '0x1fad8338', + withdraw: '0x40e5e9aa', + slash: '0x6d68fdd7', +} as const; + +export interface BondInfo { + amount: bigint; requestedAt: bigint; lifetimeSlashed: bigint; exists: boolean; +} + +export class AereAgentBondClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async bondOf(operator: string, agentId: string): Promise { + const res = await call(this.provider, this.address, BOND_SEL.bondOf + encA(operator) + encB(agentId)); + const r = res.replace(/^0x/, ''); + return { + amount: BigInt('0x' + r.slice(0, 64)), + requestedAt: BigInt('0x' + r.slice(64, 128)), + lifetimeSlashed: BigInt('0x' + r.slice(128, 192)), + exists: BigInt('0x' + r.slice(192, 256)) === 1n, + }; + } + + async isBonded(operator: string, agentId: string, minBond: bigint): Promise { + const data = BOND_SEL.isBonded + encA(operator) + encB(agentId) + encU(minBond); + return BigInt(await call(this.provider, this.address, data)) === 1n; + } + + async totalSlashedToSink(): Promise { + return BigInt(await call(this.provider, this.address, BOND_SEL.totalSlashedToSink)); + } + + postBond(from: string, agentId: string, amount: bigint): Promise { + return send(this.provider, from, this.address, BOND_SEL.postBond + encB(agentId) + encU(amount)); + } + requestWithdraw(from: string, agentId: string): Promise { + return send(this.provider, from, this.address, BOND_SEL.requestWithdraw + encB(agentId)); + } + withdraw(from: string, operator: string, agentId: string): Promise { + return send(this.provider, from, this.address, BOND_SEL.withdraw + encA(operator) + encB(agentId)); + } + /** Oracle-only. reasonUri is a free-form ipfs:// or https:// link. */ + slash(from: string, operator: string, agentId: string, amount: bigint, reasonHash: string, reasonUri: string): Promise { + const head = encA(operator) + encB(agentId) + encU(amount) + encB(reasonHash) + encU(0xa0n); + const s = encString(reasonUri); + return send(this.provider, from, this.address, BOND_SEL.slash + head + s.body); + } +} + +/* ============================ AereAIReputation ============================ */ + +const REP_SEL = { + BOND: '0xc1c1d218', + MAX_SCORE: '0x27ff6223', + SLASH_UNIT: '0xd50a3e76', + statsOf: '0xcf212c5f', + scoreOf: '0x7377fa36', + attest: '0x37637f60', + isAttestor: '0x2e2f4e24', + attestors: '0xe7eb466f', +} as const; + +export interface AgentStats { positiveCount: bigint; disputeCount: bigint; lifetimeSlashed: bigint; } + +export class AereAIReputationClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async scoreOf(operator: string, agentId: string): Promise { + return BigInt(await call(this.provider, this.address, REP_SEL.scoreOf + encA(operator) + encB(agentId))); + } + async statsOf(operator: string, agentId: string): Promise { + const res = (await call(this.provider, this.address, REP_SEL.statsOf + encA(operator) + encB(agentId))).replace(/^0x/, ''); + return { + positiveCount: BigInt('0x' + res.slice(0, 64)), + disputeCount: BigInt('0x' + res.slice(64, 128)), + lifetimeSlashed: BigInt('0x' + res.slice(128, 192)), + }; + } + + /** delta ∈ {-1, 0, 1}. Attestor-only. */ + attest(from: string, operator: string, agentId: string, delta: number, evidenceHash: string, evidenceUri: string): Promise { + // int8 is sign-extended to 32 bytes; negatives become 0xFF...FF + const deltaWord = delta < 0 ? 'f'.repeat(64 - 2) + (256 + delta).toString(16).padStart(2, '0') : pad32(delta.toString(16)); + const head = encA(operator) + encB(agentId) + deltaWord + encB(evidenceHash) + encU(0xa0n); + const s = encString(evidenceUri); + return send(this.provider, from, this.address, REP_SEL.attest + head + s.body); + } +} + +/* ============================== AereInferNet ============================== */ + +const INFER_SEL = { + modelEpochCount: '0xf407005b', + modelOf: '0x1548a0e9', + epochOf: '0xc1a5ae12', + registerModel: '0x703722ac', + commitEpoch: '0x7f11fb10', + verifyInference: '0xa2a98a64', +} as const; + +export class AereInferNetClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async modelEpochCount(provider_: string, modelId: string): Promise { + return BigInt(await call(this.provider, this.address, INFER_SEL.modelEpochCount + encA(provider_) + encB(modelId))); + } + + /** RiskTier: 0=Minimal, 1=Limited, 2=High, 3=Unacceptable. */ + registerModel( + from: string, modelId: string, modelDigest: string, attestationDigest: string, + risk: number, modelName: string, attestationUri: string + ): Promise { + // head = modelId(32) + modelDigest(32) + attestationDigest(32) + risk(32) + offset_name(32) + offset_attUri(32) + // tail = nameLen+nameBody, attUriLen+attUriBody + const name = encString(modelName); + const att = encString(attestationUri); + const offsetName = 6n * 32n; + const nameBodyLen = name.body.length / 2; // including length word + padded + const offsetAtt = offsetName + BigInt(nameBodyLen); + const head = + encB(modelId) + encB(modelDigest) + encB(attestationDigest) + + pad32(risk.toString(16)) + encU(offsetName) + encU(offsetAtt); + return send(this.provider, from, this.address, INFER_SEL.registerModel + head + name.body + att.body); + } + + commitEpoch( + from: string, modelId: string, epochId: bigint, root: string, + inferenceCount: bigint, metadataUri: string + ): Promise { + const m = encString(metadataUri); + const head = encB(modelId) + encU(epochId) + encB(root) + encU(inferenceCount) + encU(0xa0n); + return send(this.provider, from, this.address, INFER_SEL.commitEpoch + head + m.body); + } +} + +/* =========================== AereAgentMemoryVault ========================== */ + +const MEM_SEL = { + permissionOf: '0xef928267', + canReadNow: '0x80b4e9ba', + headOf: '0xcc92a9ed', + nextVersion: '0xba7badeb', + grant: '0xbb9f078e', + revoke: '0x88e62721', + write: '0x2a7ae64c', +} as const; + +export interface Permission { canRead: boolean; canWrite: boolean; validUntil: bigint; exists: boolean; } +export interface MemEntry { contentHash: string; blobUri: string; writer: string; version: bigint; writtenAt: bigint; } + +export class AereAgentMemoryVaultClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async canReadNow(user: string, agent: string, slot: string): Promise { + const data = MEM_SEL.canReadNow + encA(user) + encA(agent) + encB(slot); + return BigInt(await call(this.provider, this.address, data)) === 1n; + } + async permissionOf(user: string, agent: string, slot: string): Promise { + const data = MEM_SEL.permissionOf + encA(user) + encA(agent) + encB(slot); + const r = (await call(this.provider, this.address, data)).replace(/^0x/, ''); + return { + canRead: BigInt('0x' + r.slice(0, 64)) === 1n, + canWrite: BigInt('0x' + r.slice(64, 128)) === 1n, + validUntil: BigInt('0x' + r.slice(128, 192)), + exists: BigInt('0x' + r.slice(192, 256)) === 1n, + }; + } + + /** User grants per-slot permission. validUntil=0n means no expiry. */ + grant(from: string, agent: string, slot: string, canRead: boolean, canWrite: boolean, validUntil: bigint): Promise { + const data = MEM_SEL.grant + encA(agent) + encB(slot) + + pad32(canRead ? '1' : '0') + pad32(canWrite ? '1' : '0') + encU(validUntil); + return send(this.provider, from, this.address, data); + } + revoke(from: string, agent: string, slot: string): Promise { + return send(this.provider, from, this.address, MEM_SEL.revoke + encA(agent) + encB(slot)); + } + /** Agent writes memory entry. contentHash = SHA256(encrypted blob). */ + write(from: string, user: string, slot: string, contentHash: string, blobUri: string): Promise { + const u = encString(blobUri); + const head = encA(user) + encB(slot) + encB(contentHash) + encU(0x80n); + return send(this.provider, from, this.address, MEM_SEL.write + head + u.body); + } +} diff --git a/src/agentic/index.ts b/src/agentic/index.ts new file mode 100644 index 0000000..9be63be --- /dev/null +++ b/src/agentic/index.ts @@ -0,0 +1,18 @@ +export { AereAgentClient, AERE402SettlementClient, buildAuthTypedData } from './AereAgentClient.js'; +export type { Eip1193Provider, PaymentAuth as AgentPaymentAuth } from './AereAgentClient.js'; +export { createAere402Middleware, buildAere402TypedData } from './AERE402Middleware.js'; +export type { Aere402MiddlewareConfig, PaymentAuth } from './AERE402Middleware.js'; + +// 2026-06-08 ship batch — accountability + AI Act + portable memory. +export { + AereAgentBondClient, + AereAIReputationClient, + AereInferNetClient, + AereAgentMemoryVaultClient, +} from './NewAgenticClients.js'; +export type { + BondInfo, + AgentStats, + Permission as MemoryVaultPermission, + MemEntry, +} from './NewAgenticClients.js'; diff --git a/src/channels/index.ts b/src/channels/index.ts new file mode 100644 index 0000000..abe78b6 --- /dev/null +++ b/src/channels/index.ts @@ -0,0 +1,113 @@ +/** + * State Channels SDK — AereStateChannels. + * + * Contract: aerenew/contracts/contracts/channels/AereStateChannels.sol + * + * Off-chain transactions: both parties hold a current `ChannelState` and + * exchange new ones (signed by both) on every payment. On dispute, EITHER + * party submits the latest state to start a 24h challenge window; the other + * can override with a higher-nonce state. + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +export interface ChannelState { + channelId: string; // bytes32 + nonce: bigint; + balanceA: bigint; + balanceB: bigint; + htlcLockedFromA: bigint; + htlcLockedFromB: bigint; + lockHash: string; // bytes32 ('0x000...' if no HTLC active) + lockTimeout: bigint; // unix seconds +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encUint = (n: bigint) => pad32(n.toString(16)); +const encAddr = (a: string) => pad32(a.replace(/^0x/, '')); +const encBytes32 = (b: string) => pad32(b.replace(/^0x/, '')); + +async function ethCall(p: RpcProvider, to: string, data: string): Promise { + return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; +} +async function ethSend(p: RpcProvider, from: string, to: string, data: string): Promise { + return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] }) as string; +} + +function encChannelState(s: ChannelState): string { + return encBytes32(s.channelId) + encUint(s.nonce) + encUint(s.balanceA) + encUint(s.balanceB) + + encUint(s.htlcLockedFromA) + encUint(s.htlcLockedFromB) + + encBytes32(s.lockHash) + encUint(s.lockTimeout); +} + +function encDynBytes(hex: string): { offset: bigint; encoded: string } { + const clean = hex.replace(/^0x/, ''); + const bytes = Buffer.from(clean, 'hex'); + const padded = Buffer.alloc(Math.ceil(bytes.length / 32) * 32); + bytes.copy(padded); + return { offset: 0n, encoded: encUint(BigInt(bytes.length)) + padded.toString('hex') }; +} + +const SEL = { + CHALLENGE_WINDOW: '0xe9333e75', + channels: '0xb29a8140', // channels(bytes32) + open: '0x73c0d3a9', // open(address,address,address,uint256,uint256) + cooperativeClose: '0xa9fbe4b6', // cooperativeClose(ChannelState,bytes,bytes) + startChallenge: '0xd1e26000', // startChallenge(ChannelState,bytes,bytes) + overrideChallenge: '0x44b04beb', // overrideChallenge(ChannelState,bytes,bytes) + finalize: '0x6e3e98bf', // finalize(bytes32) + settleHtlc: '0xa84f3c0e', // settleHtlc(bytes32,bytes) + withdraw: '0x51cff8d9', // withdraw(bytes32) +} as const; + +export class AereStateChannelsClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async open(from: string, partyA: string, partyB: string, token: string, depositA: bigint, depositB: bigint): Promise { + const data = SEL.open + encAddr(partyA) + encAddr(partyB) + encAddr(token) + encUint(depositA) + encUint(depositB); + return ethSend(this.provider, from, this.address, data); + } + + async finalize(from: string, channelId: string): Promise { + return ethSend(this.provider, from, this.address, SEL.finalize + encBytes32(channelId)); + } + + async settleHtlc(from: string, channelId: string, preimage: string): Promise { + const dyn = encDynBytes(preimage); + const data = SEL.settleHtlc + encBytes32(channelId) + encUint(64n) + dyn.encoded; + return ethSend(this.provider, from, this.address, data); + } + + async withdraw(from: string, channelId: string): Promise { + return ethSend(this.provider, from, this.address, SEL.withdraw + encBytes32(channelId)); + } + + /** Helper: encode the EIP-712 typed-data object for off-chain signing. */ + buildStateTypedData(chainId: number, state: ChannelState): Record { + return { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + ChannelState: [ + { name: 'channelId', type: 'bytes32' }, + { name: 'nonce', type: 'uint256' }, + { name: 'balanceA', type: 'uint256' }, + { name: 'balanceB', type: 'uint256' }, + { name: 'htlcLockedFromA', type: 'uint256' }, + { name: 'htlcLockedFromB', type: 'uint256' }, + { name: 'lockHash', type: 'bytes32' }, + { name: 'lockTimeout', type: 'uint256' }, + ], + }, + primaryType: 'ChannelState', + domain: { name: 'AereStateChannels', version: '1', chainId, verifyingContract: this.address }, + message: state, + }; + } +} diff --git a/src/compliance/NewComplianceClients.ts b/src/compliance/NewComplianceClients.ts new file mode 100644 index 0000000..659a459 --- /dev/null +++ b/src/compliance/NewComplianceClients.ts @@ -0,0 +1,188 @@ +/** + * NewComplianceClients — SDK for the 2026-06-08 ship batch. + * Covers: AereAttestationGateway, AereCompliancePool. + * + * Pattern matches sibling clients in @aere/sdk-js — dependency-free + * EIP-1193 wrapper, precomputed selectors. + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encU = (n: bigint) => pad32(n.toString(16)); +const encA = (a: string) => pad32(a.replace(/^0x/, '')); +const encB = (b: string) => pad32(b.replace(/^0x/, '')); + +async function call(p: RpcProvider, to: string, data: string): Promise { + return (await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] })) as string; +} +async function send(p: RpcProvider, from: string, to: string, data: string): Promise { + return (await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] })) as string; +} +function encString(s: string): { body: string; byteLen: number } { + const b = Buffer.from(s, 'utf8'); + const padded = Buffer.alloc(Math.ceil(b.length / 32) * 32); + b.copy(padded); + return { body: encU(BigInt(b.length)) + padded.toString('hex'), byteLen: padded.length + 32 }; +} +function encBytes(hex: string): { body: string; byteLen: number } { + const clean = hex.replace(/^0x/, ''); + const len = clean.length / 2; + const padded = Buffer.alloc(Math.ceil(len / 32) * 32); + Buffer.from(clean, 'hex').copy(padded); + return { body: encU(BigInt(len)) + padded.toString('hex'), byteLen: padded.length + 32 }; +} + +/* ========================= AereAttestationGateway ========================= */ + +const ATT_SEL = { + FOUNDATION: '0x65df2e51', + MAX_INHERITANCE_DEPTH: '0xadf515af', + isAuthorisedAttestor: '0x331735f0', + latestFor: '0xfba8fcb9', + isValidNow: '0xcf65b2b5', + subjectCurrentlyAttested: '0x57e74a61', + publishSchema: '0xce3f3aa3', + setAttestor: '0xe65b98ba', + attest: '0xc35b1106', + revoke: '0xb75c7dc6', +} as const; + +export class AereAttestationGatewayClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async isValidNow(attestationId: string): Promise { + const r = await call(this.provider, this.address, ATT_SEL.isValidNow + encB(attestationId)); + return BigInt(r) === 1n; + } + + async subjectCurrentlyAttested(subject: string, schemaId: string): Promise { + const data = ATT_SEL.subjectCurrentlyAttested + encB(subject) + encB(schemaId); + return BigInt(await call(this.provider, this.address, data)) === 1n; + } + + async latestFor(subject: string, schemaId: string): Promise { + return await call(this.provider, this.address, ATT_SEL.latestFor + encB(subject) + encB(schemaId)); + } + + async isAuthorisedAttestor(schemaId: string, attestor: string): Promise { + const data = ATT_SEL.isAuthorisedAttestor + encB(schemaId) + encA(attestor); + return BigInt(await call(this.provider, this.address, data)) === 1n; + } + + /** Foundation-only. */ + publishSchema(from: string, schemaId: string, specHash: string, name: string, semver: string, specUri: string): Promise { + const n = encString(name), sv = encString(semver), su = encString(specUri); + // head: schemaId(32) + specHash(32) + offset_n + offset_sv + offset_su = 5 slots + const offN = 5n * 32n; + const offSv = offN + BigInt(n.byteLen); + const offSu = offSv + BigInt(sv.byteLen); + const head = encB(schemaId) + encB(specHash) + encU(offN) + encU(offSv) + encU(offSu); + return send(this.provider, from, this.address, ATT_SEL.publishSchema + head + n.body + sv.body + su.body); + } + + setAttestor(from: string, schemaId: string, attestor: string, authorised: boolean): Promise { + const data = ATT_SEL.setAttestor + encB(schemaId) + encA(attestor) + pad32(authorised ? '1' : '0'); + return send(this.provider, from, this.address, data); + } + + /** Authorised-attestor-only. parentId = 0x00...00 for no parent. */ + attest( + from: string, schemaId: string, subject: string, parentId: string, payloadHash: string, + validFrom: bigint, validUntil: bigint, payloadUri: string + ): Promise { + const p = encString(payloadUri); + // 7 head slots + string tail + const head = encB(schemaId) + encB(subject) + encB(parentId) + encB(payloadHash) + + encU(validFrom) + encU(validUntil) + encU(7n * 32n); + return send(this.provider, from, this.address, ATT_SEL.attest + head + p.body); + } + + revoke(from: string, attestationId: string): Promise { + return send(this.provider, from, this.address, ATT_SEL.revoke + encB(attestationId)); + } +} + +/* ============================= AereCompliancePool ============================ */ + +const POOL_SEL = { + TOKEN: '0x82bfefc8', + DENOMINATION: '0x8b01429d', + FOUNDATION: '0x65df2e51', + VERIFIER: '0x08c84e70', + POOL_ID: '0xe0d7d0e9', + latestDepositRoot: '0x4e574c55', + depositCount: '0x2dfdf0b5', + isComplianceProvider: '0xff3d4ba1', + complianceProviders: '0x05195996', + isAssociationRootValid: '0x992d8d62', + spentNullifiers: '0x1c70406a', + associationRootPublishedAt: '0x68c46b72', + deposit: '0xca0c3ad5', + setComplianceProvider: '0xecc19f6c', + publishDepositRoot: '0x0ce9659f', + publishAssociationRoot: '0x9f92a5b1', + withdraw: '0xb7433691', +} as const; + +export class AereCompliancePoolClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async denomination(): Promise { + return BigInt(await call(this.provider, this.address, POOL_SEL.DENOMINATION)); + } + async depositCount(): Promise { + return BigInt(await call(this.provider, this.address, POOL_SEL.depositCount)); + } + async latestDepositRoot(): Promise { + return await call(this.provider, this.address, POOL_SEL.latestDepositRoot); + } + async isAssociationRootValid(root: string): Promise { + return BigInt(await call(this.provider, this.address, POOL_SEL.isAssociationRootValid + encB(root))) === 1n; + } + async isSpent(nullifierHash: string): Promise { + return BigInt(await call(this.provider, this.address, POOL_SEL.spentNullifiers + encB(nullifierHash))) === 1n; + } + + /** User deposit. Caller must have approved TOKEN to this address for DENOMINATION first. */ + deposit(from: string, commitment: string, travelRuleHash: string, complianceProvider: string, travelRuleUri: string): Promise { + const u = encString(travelRuleUri); + const head = encB(commitment) + encB(travelRuleHash) + encA(complianceProvider) + encU(4n * 32n); + return send(this.provider, from, this.address, POOL_SEL.deposit + head + u.body); + } + + publishAssociationRoot(from: string, root: string): Promise { + return send(this.provider, from, this.address, POOL_SEL.publishAssociationRoot + encB(root)); + } + publishDepositRoot(from: string, root: string): Promise { + return send(this.provider, from, this.address, POOL_SEL.publishDepositRoot + encB(root)); + } + setComplianceProvider(from: string, provider: string, allowed: boolean): Promise { + return send(this.provider, from, this.address, POOL_SEL.setComplianceProvider + encA(provider) + pad32(allowed ? '1' : '0')); + } + + /** Withdraw via zk proof. Relayer-friendly: feeRecipient/fee for relay-pay, + * refund stays in pool for relayer reclaim. */ + withdraw( + from: string, + proof: string, + depositRoot: string, + associationRoot: string, + nullifierHash: string, + recipient: string, + feeRecipient: string, + fee: bigint, + refund: bigint + ): Promise { + // 8 head slots: proof_offset, depositRoot, associationRoot, nullifierHash, + // recipient, feeRecipient, fee, refund + const offProof = 8n * 32n; + const p = encBytes(proof); + const head = + encU(offProof) + encB(depositRoot) + encB(associationRoot) + encB(nullifierHash) + + encA(recipient) + encA(feeRecipient) + encU(fee) + encU(refund); + return send(this.provider, from, this.address, POOL_SEL.withdraw + head + p.body); + } +} diff --git a/src/compliance/index.ts b/src/compliance/index.ts new file mode 100644 index 0000000..452baa6 --- /dev/null +++ b/src/compliance/index.ts @@ -0,0 +1,185 @@ +/** + * Compliance SDK clients — AereProof v0 stack + ZKScreen + AIProof. + * + * Contracts: + * aerenew/contracts/contracts/compliance/AereSanctionsRegistry.sol + * aerenew/contracts/contracts/compliance/ChainalysisOracleWrapper.sol + * aerenew/contracts/contracts/compliance/AereTravelRuleHashRegistry.sol + * aerenew/contracts/contracts/compliance/AereForensicEventRegistry.sol + * aerenew/contracts/contracts/compliance/AereZKScreen.sol + * aerenew/contracts/contracts/compliance/AereAIProof.sol + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encUint = (n: bigint) => pad32(n.toString(16)); +const encAddr = (a: string) => pad32(a.replace(/^0x/, '')); +const encBytes32 = (b: string) => pad32(b.replace(/^0x/, '')); + +async function ethCall(p: RpcProvider, to: string, data: string): Promise { + return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; +} +async function ethSend(p: RpcProvider, from: string, to: string, data: string): Promise { + return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] }) as string; +} + +function encodeDynamicBytes(hex: string): { offset: string; body: string } { + const clean = hex.replace(/^0x/, ''); + const bytes = Buffer.from(clean, 'hex'); + const padded = Buffer.alloc(Math.ceil(bytes.length / 32) * 32); + bytes.copy(padded); + return { offset: encUint(BigInt(bytes.length)), body: padded.toString('hex') }; +} + +/* ===================== AereSanctionsRegistry ====================== */ + +const SANCT_SEL = { + currentRoot: '0x6f29ed46', // currentRoot() + isSanctioned: '0x7ad8e02b', // isSanctioned(address,uint16,bytes32[]) + commitRoot: '0x9b30b9ae', // commitRoot(bytes32,uint16,string) — placeholder +} as const; + +export class AereSanctionsRegistryClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async currentRoot(): Promise { + return await ethCall(this.provider, this.address, SANCT_SEL.currentRoot); + } +} + +/* ===================== ChainalysisOracleWrapper =================== */ + +const CHAIN_SEL = { + isSanctioned: '0xdf592f7d', // isSanctioned(address) + upstream: '0x6c0360eb', // upstream() + locked: '0xcf30901', // locked() +} as const; + +export class ChainalysisOracleWrapperClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + /** Returns true iff `addr` is sanctioned (fails closed — reverts also return true). */ + async isSanctioned(addr: string): Promise { + try { + const res = await ethCall(this.provider, this.address, CHAIN_SEL.isSanctioned + encAddr(addr)); + return BigInt(res) === 1n; + } catch { + return true; // fails closed + } + } +} + +/* ===================== AereTravelRuleHashRegistry ================= */ + +const TRR_SEL = { + committedHashes: '0x6c5e6e95', // committedHashes(bytes32) + status: '0x200d2ed2', // status(bytes32) +} as const; + +export class AereTravelRuleHashRegistryClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + /** State enum: 0=Unknown, 1=Committed, 2=Acknowledged, 3=Disputed. */ + async status(hash: string): Promise { + const res = await ethCall(this.provider, this.address, TRR_SEL.status + encBytes32(hash)); + return Number(BigInt(res)); + } +} + +/* ===================== AereForensicEventRegistry ================== */ + +const FORENSIC_SEL = { + subjectAlertCount: '0x7c1d3d0a', // subjectAlertCount(address) + recentConfirmedAlertCount: '0x44e3a39d', // recentConfirmedAlertCount(address,uint8,uint256) + nextBotId: '0xf4cae09e', // nextBotId() + nextAlertId: '0xa0d49d3a', // nextAlertId() +} as const; + +export class AereForensicEventRegistryClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async subjectAlertCount(subject: string): Promise { + return BigInt(await ethCall(this.provider, this.address, FORENSIC_SEL.subjectAlertCount + encAddr(subject))); + } + + /** Returns count of CONFIRMED alerts of `minSeverity` or higher within last `windowSeconds`. */ + async recentConfirmedAlertCount(subject: string, minSeverity: number, windowSeconds: bigint): Promise { + const data = FORENSIC_SEL.recentConfirmedAlertCount + encAddr(subject) + + pad32(minSeverity.toString(16)) + encUint(windowSeconds); + return BigInt(await ethCall(this.provider, this.address, data)); + } +} + +/* ===================== AereZKScreen =============================== */ + +const ZK_SEL = { + isCleared: '0xb6a5d7de', // isCleared(address,bytes32,uint256) + clearedAt: '0xc5f4dec0', // clearedAt(address,bytes32) + submitProof: '0x77a91da7', // submitProof(bytes32,bytes,bytes) +} as const; + +export class AereZKScreenClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async isCleared(user: string, programVKey: string, minTimestamp: bigint): Promise { + const data = ZK_SEL.isCleared + encAddr(user) + encBytes32(programVKey) + encUint(minTimestamp); + const res = await ethCall(this.provider, this.address, data); + return BigInt(res) === 1n; + } + + async clearedAt(user: string, programVKey: string): Promise { + return BigInt(await ethCall(this.provider, this.address, ZK_SEL.clearedAt + encAddr(user) + encBytes32(programVKey))); + } + + /** Submit an SP1 proof. Caller must equal the `user` encoded in publicValues. */ + async submitProof(from: string, programVKey: string, publicValues: string, proof: string): Promise { + const pv = encodeDynamicBytes(publicValues); + const pr = encodeDynamicBytes(proof); + // 3-arg encoding: vKey(32) + offset_pv(32) + offset_proof(32) + len_pv(32) + body_pv + len_proof(32) + body_proof + const pvBodyLen = Math.ceil(publicValues.replace(/^0x/, '').length / 64) * 32; // bytes after length + const offsetPv = 3n * 32n; + const offsetProof = offsetPv + 32n + BigInt(pvBodyLen); + const data = ZK_SEL.submitProof + + encBytes32(programVKey) + + encUint(offsetPv) + + encUint(offsetProof) + + pv.offset + pv.body + + pr.offset + pr.body; + return ethSend(this.provider, from, this.address, data); + } +} + +/* ===================== AereAIProof ================================ */ + +const AI_SEL = { + models: '0x88e98b22', // models(bytes32) + attestationCount: '0x8b2c3a99', // attestationCount(bytes32) + registerModel: '0xa9b5a9eb', // registerModel(bytes32,address,string) + anchor: '0x5b9babe0', // anchor(bytes32,bytes32,bytes32,bytes32,uint256,uint256,bytes) +} as const; + +export class AereAIProofClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async attestationCount(modelId: string): Promise { + return BigInt(await ethCall(this.provider, this.address, AI_SEL.attestationCount + encBytes32(modelId))); + } + + async registerModel(from: string, modelId: string, signer: string, description: string): Promise { + const desc = Buffer.from(description, 'utf8'); + const padded = Buffer.alloc(Math.ceil(desc.length / 32) * 32); + desc.copy(padded); + const head = encBytes32(modelId) + encAddr(signer) + encUint(0x60n); // offset to string + const dyn = encUint(BigInt(desc.length)) + padded.toString('hex'); + return ethSend(this.provider, from, this.address, AI_SEL.registerModel + head + dyn); + } +} + +// 2026-06-08 ship batch — multi-regulator attestation + privacy pools. +export { + AereAttestationGatewayClient, + AereCompliancePoolClient, +} from './NewComplianceClients.js'; diff --git a/src/index.ts b/src/index.ts index cab8231..a003a13 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,3 +34,21 @@ export type { ChannelState } from './channels/index.js'; // MiCA Article 78 best-execution receipts — on-chain CASP commitments. export { BestExClient, OrderType, ClientClass } from './bestex/index.js'; export type { BestExConfig, Receipt as BestExReceipt, CaspRegistration } from './bestex/index.js'; + +// 2026-06-08 ship batch — agentic accountability + EU AI Act + portable memory. +export { + AereAgentBondClient, + AereAIReputationClient, + AereInferNetClient, + AereAgentMemoryVaultClient, +} from './agentic/index.js'; +export type { + BondInfo, + AgentStats, + MemoryVaultPermission, + MemEntry, +} from './agentic/index.js'; +export { + AereAttestationGatewayClient, + AereCompliancePoolClient, +} from './compliance/index.js'; diff --git a/src/lending/InsuranceFundClient.ts b/src/lending/InsuranceFundClient.ts new file mode 100644 index 0000000..6d000f5 --- /dev/null +++ b/src/lending/InsuranceFundClient.ts @@ -0,0 +1,71 @@ +/** + * InsuranceFundClient — AereInsuranceFund helpers. + * Permissionless donate; Foundation-only coverBadDebt. + * + * Contract: aerenew/contracts/contracts/lending/AereInsuranceFund.sol + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encUint = (n: bigint) => pad32(n.toString(16)); +const encAddr = (a: string) => pad32(a.replace(/^0x/, '')); + +async function ethCall(p: RpcProvider, to: string, data: string): Promise { + return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; +} +async function ethSend(p: RpcProvider, from: string, to: string, data: string): Promise { + return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] }) as string; +} + +const SEL = { + FOUNDATION: '0x4d09f93e', // FOUNDATION() + COOLDOWN_SECONDS: '0xb47d5cf5', // COOLDOWN_SECONDS() + markets: '0x2c43cab8', // markets(address) + balances: '0x27e235e3', // balances(address) + coverageRemaining: '0x1d29df0e', // coverageRemaining(address) + cooldownReady: '0x3a09a73e', // cooldownReady(address) + registerMarket: '0xc6da18d4', // registerMarket(address,uint256) + donate: '0xf14fcbc8', // donate(address,uint256) + coverBadDebt: '0xa90c1e1c', // coverBadDebt(address,address,uint256) +} as const; + +export class AereInsuranceFundClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async getFoundation(): Promise { + return '0x' + (await ethCall(this.provider, this.address, SEL.FOUNDATION)).slice(-40); + } + async getCooldownSeconds(): Promise { + return BigInt(await ethCall(this.provider, this.address, SEL.COOLDOWN_SECONDS)); + } + async balanceOf(token: string): Promise { + return BigInt(await ethCall(this.provider, this.address, SEL.balances + encAddr(token))); + } + async coverageRemaining(market: string): Promise { + return BigInt(await ethCall(this.provider, this.address, SEL.coverageRemaining + encAddr(market))); + } + async cooldownReady(market: string): Promise { + const r = await ethCall(this.provider, this.address, SEL.cooldownReady + encAddr(market)); + return BigInt(r) === 1n; + } + + /** + * Permissionless donate. Caller must approve `token` for `amount` on the fund first. + */ + async donate(from: string, token: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.donate + encAddr(token) + encUint(amount)); + } + + /** Foundation-only: register a market with a lifetime coverage cap. */ + async registerMarket(from: string, market: string, lifetimeCap: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.registerMarket + encAddr(market) + encUint(lifetimeCap)); + } + + /** Foundation-only: pay down `amount` of `borrower`'s debt on `market`. */ + async coverBadDebt(from: string, market: string, borrower: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.coverBadDebt + encAddr(market) + encAddr(borrower) + encUint(amount)); + } +} diff --git a/src/lending/LendingMarketClient.ts b/src/lending/LendingMarketClient.ts new file mode 100644 index 0000000..990276d --- /dev/null +++ b/src/lending/LendingMarketClient.ts @@ -0,0 +1,166 @@ +/** + * LendingMarketClient — typed read/write helpers for AereLendingMarket + * (Wave 1 isolated single-pair money markets). + * + * Contract: aerenew/contracts/contracts/lending/AereLendingMarket.sol + * Audited 2026-06-07 (5 HIGH + 1 MED bugs fixed pre-deploy). + * + * Pattern: dependency-free EIP-1193 wrapper with precomputed 4-byte + * selectors. Read methods just `eth_call`; write methods build calldata + * + `eth_sendTransaction`. Caller handles signer + gas estimation. + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +export interface LendingMarketConfig { + address: `0x${string}`; + provider: RpcProvider; +} + +const SEL = { + // immutable views + COLLATERAL: '0xfb4cd02f', // COLLATERAL() + DEBT_TOKEN: '0xea5b5ddf', // DEBT_TOKEN() + SINK: '0xf4f3b200', // SINK() + ORACLE: '0x7dc0d1d0', // ORACLE() + LTV_BPS: '0xcbc4cdc7', // LTV_BPS() + LIQ_THRESHOLD_BPS: '0xc0c5294d', // LIQ_THRESHOLD_BPS() + LIQ_BONUS_BPS: '0x3a86d9b3', // LIQ_BONUS_BPS() + BORROW_FEE_BPS: '0x47bf8b54', // BORROW_FEE_BPS() + MARKET_DEBT_CAP: '0xcca5e09e', // MARKET_DEBT_CAP() + COLLATERAL_DECIMALS: '0x99e9f4ad', // COLLATERAL_DECIMALS() + DEBT_DECIMALS: '0x4d9e7826', // DEBT_DECIMALS() + + // state views + collateralOf: '0x8aef7ddf', // collateralOf(address) + debtOf: '0xcc4d2f49', // debtOf(address) + supplierScaled: '0x9d7d6f0d', // supplierScaled(address) + supplierBalance: '0x9d9f3ff6', // supplierBalance(address) + debtBalance: '0x33ed81e4', // debtBalance(address) + borrowIndex: '0xaa5af0fd', // borrowIndex() + supplyIndex: '0xb02f7d5e', // supplyIndex() + totalBorrowsScaled: '0xc0c44d6b', // totalBorrowsScaled() + totalSuppliedScaled: '0x7b0b3da7', // totalSuppliedScaled() + totalBorrowsCurrent: '0xc26c9116', // totalBorrowsCurrent() + totalSuppliedCurrent: '0x57e3c7fc', // totalSuppliedCurrent() + pendingSinkFee: '0x99c50fcb', // pendingSinkFee() + borrowRatePerSecond: '0x9e6dadce', // borrowRatePerSecond() + borrowPaused: '0x42b6e3bf', // borrowPaused() + depositPaused: '0x6a5db0fa', // depositPaused() + healthFactor: '0xe8b9c2dd', // healthFactor(address) + + // write + accrueInterest: '0x59601e8a', // accrueInterest() + flushSinkFee: '0x71a5ba73', // flushSinkFee() + supply: '0xf2b9fdb8', // supply(uint256) + redeem: '0xdb006a75', // redeem(uint256) + depositCollateral: '0x80aebec5', // depositCollateral(uint256) + withdrawCollateral: '0xeb0aaa4b', // withdrawCollateral(uint256) + borrow: '0xc5ebeaec', // borrow(uint256) + repay: '0x0e752702', // repay(uint256) + repayOnBehalfOf: '0x8f1a3bf0', // repayOnBehalfOf(address,uint256) + liquidate: '0xf5e3c462', // liquidate(address,uint256) +} as const; + +/* ----------------------------- helpers ---------------------------------- */ + +function pad32(hex: string): string { + const h = hex.toLowerCase().replace(/^0x/, ''); + return h.padStart(64, '0'); +} +function encUint(n: bigint): string { return pad32(n.toString(16)); } +function encAddr(addr: string): string { return pad32(addr.replace(/^0x/, '')); } + +async function ethCall(p: RpcProvider, to: string, data: string): Promise { + return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; +} +async function ethSend(p: RpcProvider, from: string, to: string, data: string, value = '0x0'): Promise { + return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value }] }) as string; +} + +/* --------------------------- client ------------------------------------- */ + +export class LendingMarketClient { + readonly address: `0x${string}`; + readonly provider: RpcProvider; + + constructor(cfg: LendingMarketConfig) { + this.address = cfg.address; + this.provider = cfg.provider; + } + + /* ---- immutable risk params (cached one-shot reads) ------------------- */ + + async getCollateral(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.COLLATERAL)).slice(-40); } + async getDebtToken(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.DEBT_TOKEN)).slice(-40); } + async getOracle(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.ORACLE)).slice(-40); } + async getSink(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.SINK)).slice(-40); } + async getLtvBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LTV_BPS))); } + async getLiqThresholdBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LIQ_THRESHOLD_BPS))); } + async getLiqBonusBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LIQ_BONUS_BPS))); } + async getBorrowFeeBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.BORROW_FEE_BPS))); } + async getMarketDebtCap(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.MARKET_DEBT_CAP)); } + + /* ---- live state ------------------------------------------------------- */ + + async totalBorrowsCurrent(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.totalBorrowsCurrent)); } + async totalSuppliedCurrent(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.totalSuppliedCurrent)); } + async pendingSinkFee(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.pendingSinkFee)); } + async borrowRatePerSecond(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.borrowRatePerSecond)); } + async borrowPaused(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.borrowPaused)) === 1n; } + async depositPaused(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.depositPaused)) === 1n; } + + /* ---- per-user views --------------------------------------------------- */ + + async collateralOf(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.collateralOf + encAddr(user))); } + async debtBalance(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.debtBalance + encAddr(user))); } + async supplierBalance(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.supplierBalance + encAddr(user))); } + async healthFactor(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.healthFactor + encAddr(user))); } + + /* ---- write methods ---------------------------------------------------- */ + + async accrueInterest(from: string): Promise { + return ethSend(this.provider, from, this.address, SEL.accrueInterest); + } + async flushSinkFee(from: string): Promise { + return ethSend(this.provider, from, this.address, SEL.flushSinkFee); + } + async supply(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.supply + encUint(amount)); + } + async redeem(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.redeem + encUint(amount)); + } + async depositCollateral(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.depositCollateral + encUint(amount)); + } + async withdrawCollateral(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.withdrawCollateral + encUint(amount)); + } + async borrow(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.borrow + encUint(amount)); + } + async repay(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.repay + encUint(amount)); + } + async repayOnBehalfOf(from: string, account: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.repayOnBehalfOf + encAddr(account) + encUint(amount)); + } + async liquidate(from: string, borrower: string, repayAmount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.liquidate + encAddr(borrower) + encUint(repayAmount)); + } + + /* ---- helpers ---------------------------------------------------------- */ + + /** + * Returns the APY as a decimal (0.05 = 5%) computed from `borrowRatePerSecond`. + * Linear approximation; accurate at low rates. + */ + async borrowApy(): Promise { + const rate = await this.borrowRatePerSecond(); + const r = Number(rate) / 1e27; + return r * 365 * 86400; + } +} diff --git a/src/lending/RWATransferAdapterClient.ts b/src/lending/RWATransferAdapterClient.ts new file mode 100644 index 0000000..467b745 --- /dev/null +++ b/src/lending/RWATransferAdapterClient.ts @@ -0,0 +1,52 @@ +/** + * RWATransferAdapterClient — permissioned RWA → USDC.e redemption. + * Contract: aerenew/contracts/contracts/lending/RWATransferAdapter.sol + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encUint = (n: bigint) => pad32(n.toString(16)); +const encAddr = (a: string) => pad32(a.replace(/^0x/, '')); + +async function ethCall(p: RpcProvider, to: string, data: string): Promise { + return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; +} +async function ethSend(p: RpcProvider, from: string, to: string, data: string): Promise { + return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] }) as string; +} + +const SEL = { + PAYOUT_TOKEN: '0x935ca8e6', // PAYOUT_TOKEN() + ORACLE: '0x7dc0d1d0', // ORACLE() + FOUNDATION: '0x4d09f93e', // FOUNDATION() + configs: '0x1d12be96', // configs(address) + states: '0xb95da3ec', // states(address) + periodRemaining: '0x3aff0d34', // periodRemaining(address) + payoutLiquidity: '0x8b8a55be', // payoutLiquidity() + redeem: '0x1e9a6950', // redeem(address,uint256) +} as const; + +export class RWATransferAdapterClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async getPayoutToken(): Promise { + return '0x' + (await ethCall(this.provider, this.address, SEL.PAYOUT_TOKEN)).slice(-40); + } + async getFoundation(): Promise { + return '0x' + (await ethCall(this.provider, this.address, SEL.FOUNDATION)).slice(-40); + } + async periodRemaining(asset: string): Promise { + return BigInt(await ethCall(this.provider, this.address, SEL.periodRemaining + encAddr(asset))); + } + async payoutLiquidity(): Promise { + return BigInt(await ethCall(this.provider, this.address, SEL.payoutLiquidity)); + } + + /** Permissionless redeem. Caller must approve `asset` for `rwaAmount` on this contract first. */ + async redeem(from: string, asset: string, rwaAmount: bigint): Promise { + return ethSend(this.provider, from, this.address, SEL.redeem + encAddr(asset) + encUint(rwaAmount)); + } +} diff --git a/src/lending/index.ts b/src/lending/index.ts new file mode 100644 index 0000000..b73fe1b --- /dev/null +++ b/src/lending/index.ts @@ -0,0 +1,4 @@ +export { LendingMarketClient } from './LendingMarketClient.js'; +export type { LendingMarketConfig } from './LendingMarketClient.js'; +export { AereInsuranceFundClient } from './InsuranceFundClient.js'; +export { RWATransferAdapterClient } from './RWATransferAdapterClient.js'; diff --git a/src/raas/RaaSClient.ts b/src/raas/RaaSClient.ts new file mode 100644 index 0000000..2c5bc0b --- /dev/null +++ b/src/raas/RaaSClient.ts @@ -0,0 +1,179 @@ +/** + * RaaSClient — AereRaaSFactory + AereRollupSettlement helpers. + * + * Contracts: + * aerenew/contracts/contracts/raas/AereRaaSFactory.sol + * aerenew/contracts/contracts/raas/AereRollupSettlement.sol + */ + +export interface RpcProvider { + request(args: { method: string; params: unknown[] }): Promise; +} + +const pad32 = (h: string) => h.toLowerCase().replace(/^0x/, '').padStart(64, '0'); +const encUint = (n: bigint) => pad32(n.toString(16)); +const encAddr = (a: string) => pad32(a.replace(/^0x/, '')); +const encBytes32 = (b: string) => pad32(b.replace(/^0x/, '')); +const encBool = (b: boolean) => pad32(b ? '1' : '0'); + +async function ethCall(p: RpcProvider, to: string, data: string): Promise { + return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; +} +async function ethSend(p: RpcProvider, from: string, to: string, data: string, value = '0x0'): Promise { + return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value }] }) as string; +} + +/* ============================== Factory =============================== */ + +const FAC_SEL = { + // immutable views + BOND_TOKEN: '0x18b13f55', // BOND_TOKEN() + SINK: '0xf4f3b200', // SINK() + REGISTRATION_BOND: '0x4b094c4b', // REGISTRATION_BOND() + MIN_SINK_BPS: '0x9dabd040', // MIN_SINK_BPS() + DEFAULT_CHALLENGE_WINDOW: '0x8e8a6f3a', // DEFAULT_CHALLENGE_WINDOW() + FOUNDATION: '0x4d09f93e', // FOUNDATION() + // state views + rollups: '0xdcecb8e3', // rollups(bytes32) + rollupCount: '0xff19a3a4', // rollupCount() + // writes + registerRollup: '0xbb4ab86c', // registerRollup(bytes32,address,address,address,uint16,uint16,uint16,uint256,uint64,string) + updateMetadata: '0xa1ae9b0e', // updateMetadata(bytes32,string) + exitRollup: '0x73db2bba', // exitRollup(bytes32,address,uint64,bytes) +} as const; + +export class AereRaaSFactoryClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async rollupCount(): Promise { + return BigInt(await ethCall(this.provider, this.address, FAC_SEL.rollupCount)); + } + async getSink(): Promise { + return '0x' + (await ethCall(this.provider, this.address, FAC_SEL.SINK)).slice(-40); + } + async getRegistrationBond(): Promise { + return BigInt(await ethCall(this.provider, this.address, FAC_SEL.REGISTRATION_BOND)); + } + async getMinSinkBps(): Promise { + return Number(BigInt(await ethCall(this.provider, this.address, FAC_SEL.MIN_SINK_BPS))); + } + + /** + * Register a rollup. Requires the caller to have approved BOND_TOKEN for + * REGISTRATION_BOND on the factory beforehand. + */ + async registerRollup( + from: string, + opts: { + rollupId: string; // bytes32 hex + rollupOwner: string; + sequencer: string; + debtToken: string; + sinkBps: number; + rollupBps: number; + sequencerBps: number; + minChallengeBond: bigint; + challengeWindow: number; + metadataUri: string; + } + ): Promise { + const meta = Buffer.from(opts.metadataUri, 'utf8'); + const head = + encBytes32(opts.rollupId) + + encAddr(opts.rollupOwner) + + encAddr(opts.sequencer) + + encAddr(opts.debtToken) + + encUint(BigInt(opts.sinkBps)) + + encUint(BigInt(opts.rollupBps)) + + encUint(BigInt(opts.sequencerBps)) + + encUint(opts.minChallengeBond) + + encUint(BigInt(opts.challengeWindow)) + + encUint(0x140n); // 10 head slots * 32 = 320 = 0x140 + const padded = Buffer.alloc(Math.ceil(meta.length / 32) * 32); + meta.copy(padded); + const dyn = encUint(BigInt(meta.length)) + padded.toString('hex'); + const data = FAC_SEL.registerRollup + head + dyn; + return ethSend(this.provider, from, this.address, data); + } +} + +/* ============================ Settlement ============================= */ + +const SET_SEL = { + ROLLUP_ID: '0x7b0a4eb1', // ROLLUP_ID() + SINK: '0xf4f3b200', // SINK() + DEBT_TOKEN: '0xea5b5ddf', // DEBT_TOKEN() + CHALLENGE_WINDOW: '0x7a6a4abe', // CHALLENGE_WINDOW() + SINK_BPS: '0xb3f54bdd', // SINK_BPS() + ROLLUP_BPS: '0xc70aa727', // ROLLUP_BPS() + SEQUENCER_BPS: '0xe35d8b85', // SEQUENCER_BPS() + rollupOwner: '0x0e8d63c0', // rollupOwner() + sequencer: '0x42d1de27', // sequencer() + latestEpoch: '0x6f6db00f', // latestEpoch() + latestFinalisedEpoch:'0x3aac4670', // latestFinalisedEpoch() + isFinalised: '0x3a8a4a3e', // isFinalised(uint256) + totalRevenueSettled: '0xf3b6db95', // totalRevenueSettled() + + proposeStateRoot: '0x9e706b08', // proposeStateRoot(uint256,bytes32,bytes32) + advanceFinalisation: '0xb78f2ebd', // advanceFinalisation() + challenge: '0x8da548fa', // challenge(uint256,uint256,bytes32) + resolveChallenge: '0x437f2ba0', // resolveChallenge(uint256,bool) + resolveChallengeExpired: '0xb1cc5a3d', // resolveChallengeExpired(uint256) + settleRevenue: '0xc7c1aa8c', // settleRevenue(uint256) + setSequencer: '0xa6f9dae1', // setSequencer(address) + transferOwner: '0xc6dad082', // transferOwner(address) +} as const; + +export class AereRollupSettlementClient { + constructor(public readonly provider: RpcProvider, public readonly address: string) {} + + async sequencer(): Promise { + return '0x' + (await ethCall(this.provider, this.address, SET_SEL.sequencer)).slice(-40); + } + async rollupOwner(): Promise { + return '0x' + (await ethCall(this.provider, this.address, SET_SEL.rollupOwner)).slice(-40); + } + async latestEpoch(): Promise { + return BigInt(await ethCall(this.provider, this.address, SET_SEL.latestEpoch)); + } + async isFinalised(epoch: bigint): Promise { + const res = await ethCall(this.provider, this.address, SET_SEL.isFinalised + encUint(epoch)); + return BigInt(res) === 1n; + } + async totalRevenueSettled(): Promise { + return BigInt(await ethCall(this.provider, this.address, SET_SEL.totalRevenueSettled)); + } + + /* sequencer-only */ + async proposeStateRoot(from: string, epoch: bigint, root: string, l2BlockHash: string): Promise { + const data = SET_SEL.proposeStateRoot + encUint(epoch) + encBytes32(root) + encBytes32(l2BlockHash); + return ethSend(this.provider, from, this.address, data); + } + + async resolveChallenge(from: string, epoch: bigint, accepted: boolean): Promise { + return ethSend(this.provider, from, this.address, SET_SEL.resolveChallenge + encUint(epoch) + encBool(accepted)); + } + + /* permissionless */ + async challenge(from: string, epoch: bigint, bond: bigint, reasonHash: string): Promise { + const data = SET_SEL.challenge + encUint(epoch) + encUint(bond) + encBytes32(reasonHash); + return ethSend(this.provider, from, this.address, data); + } + async resolveChallengeExpired(from: string, epoch: bigint): Promise { + return ethSend(this.provider, from, this.address, SET_SEL.resolveChallengeExpired + encUint(epoch)); + } + async advanceFinalisation(from: string): Promise { + return ethSend(this.provider, from, this.address, SET_SEL.advanceFinalisation); + } + + /* rollupOwner-only */ + async settleRevenue(from: string, amount: bigint): Promise { + return ethSend(this.provider, from, this.address, SET_SEL.settleRevenue + encUint(amount)); + } + async setSequencer(from: string, newSequencer: string): Promise { + return ethSend(this.provider, from, this.address, SET_SEL.setSequencer + encAddr(newSequencer)); + } + async transferOwner(from: string, newOwner: string): Promise { + return ethSend(this.provider, from, this.address, SET_SEL.transferOwner + encAddr(newOwner)); + } +} diff --git a/src/raas/index.ts b/src/raas/index.ts new file mode 100644 index 0000000..26cdf65 --- /dev/null +++ b/src/raas/index.ts @@ -0,0 +1 @@ +export { AereRaaSFactoryClient, AereRollupSettlementClient } from './RaaSClient.js';