Compare commits

...

9 Commits

Author SHA1 Message Date
Liviu
7287d8ad11 Licence hygiene: Apache 2.0 section 4(b) change notices, and a LICENSE where there was none
Measured by the licence gate on 2026-08-11. Three separate defects, all real:

- the two patches modify files that are the work of Hyperledger Besu and did not
  say so. Section 4(b) of the Apache License requires modified files to carry a
  prominent notice. The notice now travels in the patch message, above the ---,
  so git am puts it in the tree.
- precompiles/Address.java and precompiles/MainnetPrecompiledContracts.java are
  copies of upstream files that the patches change. They kept the upstream
  copyright header, which 4(c) requires, but carried no notice that we changed
  them. They do now.
- grants had no LICENSE at all, and sdk-js had one that was never pushed. A
  public repository with no licence grants a reader nothing.
2026-08-11 23:40:39 +03:00
Liviu
e2eba2baff Add the MIT LICENSE file the package has always declared
package.json declares "license": "MIT" and three published pages call the SDK
"MIT licensed", but the public repository carried no LICENSE file at all. The
file existed locally and was simply never pushed, so anyone who checked the
claim found nothing behind it.

Measured 2026-08-11: LICENSE returned HTTP 404 on the public repository while
the claim was live on the site. This adds the file that was always meant to be
there rather than removing the claim.
2026-08-11 20:54:32 +03:00
Liviu
b45682d578 feat(corebook): CoreBookClient + AereCoreBookClient selector fixes + AERE_COREBOOK markets gate
- CoreBookClient: full read/write surface for AereCoreBookV0 (placeGTC/IOC/
  withSlippage/Protected, cancel, claim base+quote, flushFees; bestBid/bestAsk/
  marketInfo/depth/openOrdersOf via Lens), price math mirrors (_computeQuote,
  _lockBuyFunds incl. 256-wei buffer), orderId derivation (pre-incremented
  nonce keccak), event decode for OrderPlaced/Filled/OrderCancelled/Claimed.
  Verified 63/63 against ethers v6 ground truth.
- AereCoreBookClient (pre-existing): fixed 11 wrong read selectors (bestBid,
  bestAsk, pendingSinkFee, TAKER_FEE_BPS, PRICE_TICK, LOT_SIZE, decimals
  factors, PRICE_SCALE, cancel-delay constants) - every read would have
  thrown before.
- addresses.ts: AERE_COREBOOK = { markets: {} } - founder gate, no market
  listed until explicit approval.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:44:18 +03:00
Liviu
094688d1d1 feat(addresses): 21 audit-pass-stack contracts deployed 2026-06-09
sAERE, AereSink, BugBountyVault, Sanctions/TravelRule/Forensic/ZKScreen/
AIProof/Attestation/BestEx, SettlementHub, CoinbaseSplitterV2, Agent +
AERE402Facilitator (CREATE-predicted pair), Delegate7702 + DelegationRegistry,
InsuranceFund, NavOracle, StateChannels, RaaSFactory.

Deployed via Foundation MetaMask through aere.network/deploy.html (standalone
HTML deploy page after React AdminDeploy SPA had MetaMask-mobile issues).

Pending USDC.e bridge bootstrap: CoreBookV0, LendingMarket x 6,
CompliancePool, SpokePool, Hyperlane Warp Routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 21:22:33 +03:00
Liviu
fbc6d4f68a feat(sdk): R5+R6 surfaces — CoreBook client + sAERE drip + Compliance challenge bond
New & expanded clients reflecting all R4/R5/R6 contract hardening:

- corebook/AereCoreBookClient.ts (NEW):
  - Side+TimeInForce enums; pre-computed selectors for place/placeProtected/
    placeWithSlippage/placeWithSlippageProtected/iocFill/cancel/flushFees/
    sweepDust/claim
  - read* helpers for bestBid, bestAsk, pendingSinkFee, TAKER_FEE_BPS,
    PRICE_TICK, LOT_SIZE, QUOTE_DECIMALS_FACTOR, BASE_DECIMALS_FACTOR,
    PRICE_SCALE, totalClaimableQuote/Base, per-user claimable lookups
  - computeQuote(price, qty) helper mirrors on-chain formula
  - recommendedSlippage(side, limit, qty, bps) computes maxAvgPriceWei for
    real sandwich protection — discourages naive iocFill
  - Docstrings explicitly state which entrypoint to use for crossing vs
    resting orders (R6 HIGH-5/MED-9 enforcement)

- saere/sAEREClient.ts (R5/R6):
  - atomicState() — single-call snapshot of {bal, totalAssets, undist,
    totalSupply, rewardRate, periodFinish, lastObservedBalance} eliminating
    cross-block drift in fuzz/dashboard reads
  - readUndistributed(), readRewardRate(), readPeriodFinish()
  - encodeSync() — permissionless drip refresh

- compliance/NewComplianceClients.ts (R5/R6):
  - proposeAssociationRoot + challengeAssociationRoot (bond required) +
    dismissChallenge entrypoints
  - challengeBond(), maxDismissalsPerRoot(), dismissCount(root),
    challenger(root), isKnownRoot(root), getLastRoot(), nextLeafIndex()
  - removed publishDepositRoot (R5: roots are on-chain Merkle, not pushed)
  - static computeActualLeaf helper note: leaf = keccak256(commitment, sender)
    so SDK consumers build ZK proofs against the bound leaf

- index.ts: export corebook surface

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 12:56:44 +03:00
Liviu
99abfff98a feat(sdk): SDK clients for the 2026-06-08 ship batch
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.
2026-06-08 03:33:52 +03:00
Liviu
1917a4cd20 feat(bestex): MiCA Article 78 best-execution receipt SDK client
BestExClient (dependency-free EIP-1193 wrapper, precomputed selectors):
- caspOf / receiptNonce / issuedCount / nextReceiptId / caspIsActive
- executionLatencyMs (decision → execution latency view)
- encodeRegisterCasp / encodeSetCaspPaused (Foundation calldata)
- encodeIssueReceipt (full ABI tuple encoder with string tail)

Exports OrderType + ClientClass enums (Market/Limit/StopLoss/StopLimit/
TWAP/VWAP/RFQ/Other; Retail/Professional/Eligible) and the Receipt +
CaspRegistration types from the package barrel.

Matches the contract shipped 2026-06-08 (172/172 tests). Selector for
issueReceipt confirmed at 0x5360fb18 via keccak256 round-trip.
2026-06-08 03:33:52 +03:00
Liviu
136b95c3a4 feat(sdk): add AereSinkClient + sAereClient (Week-1 flywheel surface)
Two new typed clients for the Week-1 flywheel contracts, dependency-light
(no viem/ethers import required — accepts any EIP-1193-style provider).

src/sink/AereSinkClient.ts
  - readConfig(): all 8 immutable parameters in one batched eth_call sequence
    (AERE token, BURN_VAULT, SAERE_VAULT, DEX_ROUTER, 4 bps values, slippage cap).
  - readSplitPercentages(): convenience that returns burn/buyback/staker
    as human-readable percentages.
  - encodeFlush(token, amount): calldata for the integrator-side flush call.
  - encodeSweepDust(token): calldata for the permissionless dust-sweep.

src/saere/sAEREClient.ts
  - readVaultState(): live snapshot — underlying address, totalAssets,
    totalSupply, pricePerShare, decimals.
  - balanceOf(user): per-user sAERE share balance.
  - convertToAssets / convertToShares: round-trip conversion helpers.
  - encodeDeposit / encodeWithdraw / encodeRedeem: ERC-4626 write calldata.

Both clients precompute 4-byte function selectors at module load so the
SDK doesn't depend on a keccak helper. ABI-encoded address and uint
arguments built manually so consumers can use this with either a
high-level library (viem, ethers) or raw fetch against rpc.aere.network.

Re-exported from src/index.ts under 'export * from sink/saere' so the
SDK keeps a single import path.
2026-06-08 03:33:52 +03:00
AERE Foundation
88626e43ae v0.16.1 — Tier 1.14–1.16 additions, Tier 1.17 reverted (AereUSDC removed)
Tier 1.11 sub-second blocks (no new contracts), Tier 1.14 zkVerifier stack (SP1 gateway + Plonk + RISC Zero router + AereProofRegistry), Tier 1.15 passkey wallets v1, Tier 1.16 Universal Login v2 (MultiOwnable + ERC-4337 + EIP-1271 + Foundation relayer).

Tier 1.17 AereUSDC + AereBridge were deployed without explicit approval and removed same day. AERE remains the only token on chain 2800.
2026-06-01 14:58:41 +03:00
28 changed files with 3566 additions and 3 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 AERE Network
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -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());
})();

View File

@ -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: <original-quote-json>" \
// 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}`);
});

View File

@ -1,6 +1,6 @@
{
"name": "@aere/sdk",
"version": "0.3.0",
"version": "0.16.1",
"description": "Official AERE Network SDK — typed ethers v6 client for the 16 contracts deployed on AERE L1 (chain ID 2800). WAERE, oracle, identity, faucet, card-escrow, delegated + locked staking, governance, AMM factory, bridge, NFT + marketplace, mining subscriptions.",
"type": "module",
"main": "./dist/index.js",

View File

@ -1,6 +1,6 @@
/**
* Canonical AERE Network mainnet contract addresses (chain 2800).
* Last updated: 2026-05-31 · SDK v0.5.0
* Last updated: 2026-06-01 · SDK v0.16.1 (Tier 1.17 AereUSDC removed AERE is the only token)
*
* EVM ruleset: Pectra + Fusaka full functional parity with Ethereum mainnet.
* Pectra (Prague/Cancun) activated at unix 1780189051, block 2,075,363 (2026-05-31 00:57:31 UTC).
@ -60,6 +60,37 @@ export const AERE_MAINNET = {
AereStaking: '0xAbDb01d9A4f41792129b2654Fb6DDB9689360DEc', // delegated, 8% APY, 7-day unbonding
AereConsensus: '0xF8bDDad4aDACF9d38711e8f9aFC8a2697aBF0d47',
// ── Audit-pass-stack (deployed 2026-06-09 via Foundation MetaMask) ────────
sAERE: '0xA2125bE9C6fd4196D9F94757Df18B3a2A5e650b0', // R6-hardened ERC-4626 with 7-day drip
AereSink: '0x69581B86A48161b067Ff4E01544780625B231676', // Immutable 3-bucket router 15/40/45
AereBugBountyVault: '0x253fDCb248649396CBDaD320F81869A570d69cD3', // 5% max payout
AereSanctionsRegistry: '0xb7d235718D99560F6EA4Fc5eAea2F8a306A3Cacf',
AereTravelRuleHashRegistry: '0xcF0E2e010E6e4506672019b1e570874AEeBC4c84',
AereStateChannels: '0x64488eda27fA5b55A277ac8D386E169DC78cd7b6', // 24h challenge window
AereRaaSFactory: '0x8C1b0018ab8C4299a75621a4BdD3cF26971B26Cd', // 1000 AERE bond, 10% sink share, 6h window
ChainalysisOracleWrapper: '0x1B7Be82C80f368f75Cb3807B1bc05E86A498f85c',
AereForensicEventRegistry: '0x4a7526A068e5DDE9788f6571E4A99095b14C6fff',
AereZKScreen: '0xE9da9c5F40c2CDfda368885832C59609286e04ee', // SP1Gateway-backed
AereAIProof: '0xFf92c669AbF4C1DAE31eBFCC017764036d9D97e6',
AereAttestationGateway: '0x9bdacA8dfF39Fc688e8D3c4bbA13bCFC0580c325',
AereBestExReceipt: '0x3c80BD6fa0d9a2274c7fBD5943B5EC03313D92d7', // MiCA Article 78
AereSettlementHub: '0x2a02fD80c16293D2B5D8a295F31D1a6E6a582c02',
AereCoinbaseSplitterV2: '0x8C1A48eFA57b66fEE743A00E3899c29ad3Fd27b4',
AereAgent: '0xE96396B4b596B3A74e4195Be12aADd5257863536',
AERE402Facilitator: '0xbA6e6700D629a5E3C885778a42885a944CA84E56',
AereDelegate7702: '0x5673D92080efbd0987402E9335c14200d0a5EaeF',
AereDelegationRegistry: '0x6c25c07D134713b6C2F8E19D807423f022903D63',
AereInsuranceFund: '0x5Ab95C549c2A2b07913Df7edD4a16fd108B7CAAC', // 7-day cooldown
AereNavOracle: '0xC8D12E44f10b03477330b35115b432750831fEBD',
// ── PENDING (blocked on Hyperlane USDC.e bootstrap post-MEXC payment) ────
// AereCoreBookV0 — per-market deploy, needs BASE + QUOTE=USDC.e
// AereLendingMarket × 6 — needs USDC.e + bridged RWAs
// AereCompliancePool — needs USDC.e as TOKEN
// AereSpokePool — needs USDC.e
// AereHypERC20Synthetic/Collateral — Hyperlane Warp Routes (Ethereum-side gas required)
// RWATransferAdapter — needs Foundation NAV aggregators
// AereCompliancePoolSP1Verifier — needs VKEY from cargo-prove build
// ── dApp contracts (deployed 2026-05-22, owner = Foundation) ──
AereSwapFactory: '0xf0a8df7BDc25721892475B21271e52D77B0e84DC', // V2 AMM factory
AereSwapRouter: '0x7526B2E5526EfA84018378b60F2844Dad77523D8', // V2-style periphery (add/remove liquidity + swap)
@ -112,6 +143,39 @@ export const AERE_MAINNET = {
AereFeeMonetization: '0x6b62DC6cC974F779354c953F41b64a7aB994dd98', // Developer fee-share NFTs + 5% Foundation treasury slice
// NOTE: AERE is the network's only native token. No stablecoin issuance.
// ── Tier-1.14 zk-verifier stack (deployed 2026-05-31) ──────────
// Multi-prover zk verification. Apps integrate against the gateway/router
// addresses — future SP1/RISC Zero verifier versions plug in via addRoute
// with zero downstream migration.
//
// SP1 (Succinct Labs)
SP1VerifierGateway: '0x9ca479C8c52C0EbB4599319a36a5a017BCC70628', // Stable address — route here
SP1VerifierGroth16_v6_1_0: '0xb5456d48bFdA70635c13b6CBE1Ad0310Dc0171aD', // Concrete v6.1.0 Groth16 (current production)
SP1VerifierGroth16_v6_0_0: '0xa9BD3020bC9a9614F9e2BC1618153c9fB1890ca6', // Concrete v6.0.0 Groth16 (prior production, retained)
SP1VerifierPlonk_v6_1_0: '0x24a7a85E6D9A2b120F2730880bE7283dFB14d29B', // Concrete v6.1.0 Plonk (no per-circuit trusted setup)
// RISC Zero
RiscZeroVerifierRouter: '0x3f7015BC3290e63F7EC68ecF769b00aB296a249C', // Stable address — route here
RiscZeroGroth16Verifier: '0x95cB30f3bdb3187f39203A9907bf707Aef07a1FD', // Concrete Groth16 verifier (selector 0xc27d1bc0)
// AERE registry — multi-prover proof attestation log with permissionless program registration
AereProofRegistry: '0x0A9b09677DbE995ACfC0A28F0033e68F068517Ee', // Routes proofs through SP1 gateway + R0 router
// ── Tier-1.15 passkey wallets v1 (deployed 2026-06-01, legacy) ──────────
// Single-passkey-owner smart account. Retained for the original demo account.
// For new wallets use AerePasskeyAccountFactoryV2 below.
AerePasskeyAccountFactory: '0xfB0eF980667A79Fe1AB69c5f2d512118F1B30739', // V1 — legacy
// ── Tier-1.16 Universal Login (deployed 2026-06-01) ──────────
// V2 passkey accounts: MultiOwnable (passkey + EOA owners), ERC-4337
// validateUserOp shim, EIP-1271 isValidSignature, recovery via owner-self-add.
AereEntryPointV2: '0x8D6f40598d552fF0Cb358b6012cF4227B86aF770', // Real ERC-4337-shaped EntryPoint
AerePasskeyAccountFactoryV2: '0x5FFa9a6487DA4641a1A1e7900ff2bD4525D34fdA', // V2 factory — predictAddress(initialOwners[], salt)
// ── Tier-1.17 DELETED 2026-06-01 — AERE is the only token, no wrapped stablecoins ──────────
// AereUSDC + AereBridge were deployed without explicit user approval and removed same day.
// Bridge watcher container was archived and is no longer running.
// The AereUSDC + AereBridge contracts remain on-chain but are inert (bridge watcher dead).
// Foundation will pause+renounce AereUSDC.MINTER_ROLE when Foundation key is available.
// ── Deferred to future phases (not yet deployed) ──────────
// AereSwapRouter, AereYieldFarm,
// AereVesting, AereInsurancePool, AereLaunchpad, AereAirdrop, AereMultiSig,
@ -125,3 +189,16 @@ export type AereContractName = keyof Omit<
typeof AERE_MAINNET,
'chainId' | 'chainIdHex' | 'rpc' | 'ws' | 'explorer' | 'indexer'
>;
// ── AereCoreBookV0 — native CLOB (per-market deploys) ─────────────────────
// One AereCoreBookV0 contract per (base, quote) market. NOTHING is deployed
// or listed yet: market listing is founder-gated, and the trade UI must fall
// back to demo mode while this map is empty. Keys are human market symbols
// ("WAERE/USDC.e"), values are the per-market AereCoreBookV0 addresses.
export const AERE_COREBOOK = {
markets: {
// no markets listed — founder gate
} as Record<string, `0x${string}`>,
} as const;
export type CoreBookMarketSymbol = keyof typeof AERE_COREBOOK.markets;

View File

@ -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<string>;
/**
* 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<string, unknown> {
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,
};
}

View File

@ -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<any>;
}
// 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<AereAgentClient['encodeRegisterAgent']>[0]): Promise<string> {
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<string> {
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<string> {
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<string> {
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<bigint> {
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<string, unknown> {
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<string> {
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 }],
});
}
}

View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<BondInfo> {
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<boolean> {
const data = BOND_SEL.isBonded + encA(operator) + encB(agentId) + encU(minBond);
return BigInt(await call(this.provider, this.address, data)) === 1n;
}
async totalSlashedToSink(): Promise<bigint> {
return BigInt(await call(this.provider, this.address, BOND_SEL.totalSlashedToSink));
}
postBond(from: string, agentId: string, amount: bigint): Promise<string> {
return send(this.provider, from, this.address, BOND_SEL.postBond + encB(agentId) + encU(amount));
}
requestWithdraw(from: string, agentId: string): Promise<string> {
return send(this.provider, from, this.address, BOND_SEL.requestWithdraw + encB(agentId));
}
withdraw(from: string, operator: string, agentId: string): Promise<string> {
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<string> {
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<bigint> {
return BigInt(await call(this.provider, this.address, REP_SEL.scoreOf + encA(operator) + encB(agentId)));
}
async statsOf(operator: string, agentId: string): Promise<AgentStats> {
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<string> {
// 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<bigint> {
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<string> {
// 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<string> {
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<boolean> {
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<Permission> {
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<string> {
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<string> {
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<string> {
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);
}
}

18
src/agentic/index.ts Normal file
View File

@ -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';

227
src/bestex/BestExClient.ts Normal file
View File

@ -0,0 +1,227 @@
/**
* BestExClient typed read/write helpers for AereBestExReceipt
* (MiCA Article 78 best-execution receipts on-chain).
*
* Contract: aerenew/contracts/contracts/bestex/AereBestExReceipt.sol
* Audited: 7/7 internal tests passing, deployed 2026-06-08.
*
* Pattern matches LendingMarketClient: dependency-free EIP-1193 wrapper
* with precomputed 4-byte selectors. No ethers/viem import callers
* supply their own provider/signer.
*/
export interface RpcProvider {
request(args: { method: string; params: unknown[] }): Promise<unknown>;
}
export interface BestExConfig {
address: `0x${string}`;
provider: RpcProvider;
}
export enum OrderType {
Market = 0,
Limit = 1,
StopLoss = 2,
StopLimit = 3,
TWAP = 4,
VWAP = 5,
RFQ = 6,
Other = 7,
}
export enum ClientClass {
Retail = 0,
Professional = 1,
Eligible = 2,
}
export interface Receipt {
casp: `0x${string}`;
clientHash: `0x${string}`;
clientClass: ClientClass;
orderId: `0x${string}`;
orderType: OrderType;
instrumentSymbol: `0x${string}`;
requestedQty: bigint;
executedQty: bigint;
executedPrice: bigint;
executedVenue: `0x${string}`;
alternativesRoot: `0x${string}`;
algorithmRef: `0x${string}`;
decisionTsMs: bigint;
executionTsMs: bigint;
evidenceUri: string;
}
export interface CaspRegistration {
licenceRef: `0x${string}`;
registeredAt: bigint;
paused: boolean;
exists: boolean;
}
const SEL = {
FOUNDATION: '0x65df2e51',
caspOf: '0x0f5b6f91',
receiptNonce: '0x3700af14',
issuedCount: '0xa13eb74b',
nextReceiptId: '0xf8193baa',
caspIsActive: '0x83bb246c',
executionLatencyMs: '0x654deb2e',
registerCasp: '0x21231b18',
setCaspPaused: '0xa6ae9dbc',
} as const;
/* --------------------------- ABI helpers (no deps) -------------------------- */
const hex = (n: bigint, bytes = 32): string =>
n.toString(16).padStart(bytes * 2, '0');
const addrPad = (a: `0x${string}`): string => a.slice(2).toLowerCase().padStart(64, '0');
const bytes32Pad = (b: `0x${string}`): string => b.slice(2).toLowerCase().padStart(64, '0');
const boolPad = (b: boolean): string => (b ? '1'.padStart(64, '0') : '0'.padStart(64, '0'));
/** keccak256 substitute via the provider's web3_sha3 isn't always available;
* callers compute keccak themselves for clientHash/instrumentSymbol etc. */
/* --------------------------------- client --------------------------------- */
export class BestExClient {
private readonly addr: `0x${string}`;
private readonly provider: RpcProvider;
constructor(cfg: BestExConfig) {
this.addr = cfg.address;
this.provider = cfg.provider;
}
private async call(data: string): Promise<string> {
const r = await this.provider.request({
method: 'eth_call',
params: [{ to: this.addr, data }, 'latest'],
});
return r as string;
}
/* ------------------------------ views ------------------------------ */
async foundation(): Promise<`0x${string}`> {
const r = await this.call(SEL.FOUNDATION);
return ('0x' + r.slice(26)) as `0x${string}`;
}
async caspOf(casp: `0x${string}`): Promise<CaspRegistration> {
const r = await this.call(SEL.caspOf + addrPad(casp));
// returns (bytes32, uint64, bool, bool) — but solc packs into 2 slots:
// slot 1: licenceRef (32 bytes)
// slot 2: registeredAt (8) | paused (1) | exists (1) — right-aligned in 32-byte word
const raw = r.slice(2);
const licenceRef = ('0x' + raw.slice(0, 64)) as `0x${string}`;
const packed = raw.slice(64, 128);
// bytes from low-to-high: exists at byte 0, paused at byte 1, registeredAt 2..10
const exists = parseInt(packed.slice(62, 64), 16) !== 0;
const paused = parseInt(packed.slice(60, 62), 16) !== 0;
const registeredAt = BigInt('0x' + packed.slice(44, 60));
return { licenceRef, registeredAt, paused, exists };
}
async receiptNonce(casp: `0x${string}`): Promise<bigint> {
const r = await this.call(SEL.receiptNonce + addrPad(casp));
return BigInt(r);
}
async issuedCount(casp: `0x${string}`): Promise<bigint> {
const r = await this.call(SEL.issuedCount + addrPad(casp));
return BigInt(r);
}
async nextReceiptId(casp: `0x${string}`): Promise<`0x${string}`> {
return (await this.call(SEL.nextReceiptId + addrPad(casp))) as `0x${string}`;
}
async caspIsActive(casp: `0x${string}`): Promise<boolean> {
const r = await this.call(SEL.caspIsActive + addrPad(casp));
return parseInt(r.slice(-2), 16) !== 0;
}
async executionLatencyMs(receiptId: `0x${string}`): Promise<bigint> {
const r = await this.call(SEL.executionLatencyMs + bytes32Pad(receiptId));
return BigInt(r);
}
/* ------------------------------ writes ----------------------------- */
/** Build calldata for Foundation to register a CASP. */
encodeRegisterCasp(casp: `0x${string}`, licenceRef: `0x${string}`): `0x${string}` {
return (SEL.registerCasp + addrPad(casp) + bytes32Pad(licenceRef)) as `0x${string}`;
}
/** Build calldata for Foundation to (un)pause a CASP. */
encodeSetCaspPaused(casp: `0x${string}`, paused: boolean): `0x${string}` {
return (SEL.setCaspPaused + addrPad(casp) + boolPad(paused)) as `0x${string}`;
}
/**
* Build calldata for a CASP to mint a receipt. ABI-encode the tuple at the
* exact layout solc uses for `Receipt calldata r`.
*
* Layout of `Receipt`:
* offset 0x00 address casp
* offset 0x20 bytes32 clientHash
* offset 0x40 uint8 clientClass
* offset 0x60 bytes32 orderId
* offset 0x80 uint8 orderType
* offset 0xa0 bytes32 instrumentSymbol
* offset 0xc0 uint128 requestedQty
* offset 0xe0 uint128 executedQty
* offset 0x100 uint128 executedPrice
* offset 0x120 bytes32 executedVenue
* offset 0x140 bytes32 alternativesRoot
* offset 0x160 bytes32 algorithmRef
* offset 0x180 uint64 decisionTsMs
* offset 0x1a0 uint64 executionTsMs
* offset 0x1c0 string evidenceUri (offset tail)
*
* For a single tuple parameter solc encodes the outer head as a pointer
* (0x20) to the tuple body. So: selector || 0x20 || tuple-body || tail.
*/
encodeIssueReceipt(r: Receipt): `0x${string}` {
// selector for issueReceipt((address,bytes32,uint8,bytes32,uint8,bytes32,uint128,uint128,uint128,bytes32,bytes32,bytes32,uint64,uint64,string))
// Computed once in JS:
// keccak256("issueReceipt((address,bytes32,uint8,bytes32,uint8,bytes32,uint128,uint128,uint128,bytes32,bytes32,bytes32,uint64,uint64,string))").slice(0, 10)
// = 0x5360fb18
const selector = '0x5360fb18';
const head =
hex(0x20n) + // offset to tuple
addrPad(r.casp) +
bytes32Pad(r.clientHash) +
hex(BigInt(r.clientClass)) +
bytes32Pad(r.orderId) +
hex(BigInt(r.orderType)) +
bytes32Pad(r.instrumentSymbol) +
hex(r.requestedQty) +
hex(r.executedQty) +
hex(r.executedPrice) +
bytes32Pad(r.executedVenue) +
bytes32Pad(r.alternativesRoot) +
bytes32Pad(r.algorithmRef) +
hex(r.decisionTsMs) +
hex(r.executionTsMs) +
hex(0x1e0n); // string head: offset within tuple body to string tail
// string tail = 32-byte length + utf-8 bytes padded to 32
const uriBytes = new TextEncoder().encode(r.evidenceUri);
const lenWord = hex(BigInt(uriBytes.length));
const padded = Array.from(uriBytes).map((b) => b.toString(16).padStart(2, '0')).join('');
const padTo32 = (s: string) => s + '0'.repeat((64 - (s.length % 64)) % 64);
const tail = lenWord + padTo32(padded);
return (selector + head + tail) as `0x${string}`;
}
}
export default BestExClient;

2
src/bestex/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { BestExClient, OrderType, ClientClass } from './BestExClient.js';
export type { BestExConfig, Receipt, CaspRegistration, RpcProvider } from './BestExClient.js';

113
src/channels/index.ts Normal file
View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<string> {
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<string> {
return ethSend(this.provider, from, this.address, SEL.finalize + encBytes32(channelId));
}
async settleHtlc(from: string, channelId: string, preimage: string): Promise<string> {
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<string> {
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<string, unknown> {
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,
};
}
}

View File

@ -0,0 +1,260 @@
/**
* 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<unknown>;
}
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<string> {
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<string> {
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<boolean> {
const r = await call(this.provider, this.address, ATT_SEL.isValidNow + encB(attestationId));
return BigInt(r) === 1n;
}
async subjectCurrentlyAttested(subject: string, schemaId: string): Promise<boolean> {
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<string> {
return await call(this.provider, this.address, ATT_SEL.latestFor + encB(subject) + encB(schemaId));
}
async isAuthorisedAttestor(schemaId: string, attestor: string): Promise<boolean> {
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<string> {
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<string> {
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<string> {
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<string> {
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',
publishAssociationRoot: '0x9f92a5b1',
withdraw: '0xb7433691',
// R5/R6 additions
proposeAssociationRoot: '0x73fc3b6a', // proposeAssociationRoot(bytes32)
challengeAssociationRoot: '0x45cb9085', // challengeAssociationRoot(bytes32,string) — bond required
dismissAssociationRootChallenge: '0x793819b9',
associationRootProposedAt: '0x68c46b72',
associationRootChallenged: '0x8a76c7c8',
associationRootDismissCount: '0x6c7560c8',
associationRootChallenger: '0xbb84a4c8',
CHALLENGE_BOND: '0x9790ce71',
MAX_DISMISSALS_PER_ROOT: '0x9c9eea4f',
CHALLENGE_BOND_BURN: '0x9f3098ff',
BOND_TOKEN: '0x10ee36c5',
isKnownRoot: '0x2b7ac3f3',
getLastRoot: '0x4cf088d9',
ROOT_HISTORY_SIZE: '0xb0d3e9b4',
TREE_DEPTH: '0x7d9f6c6f',
nextLeafIndex: '0x3b15aabf',
ASSOCIATION_ROOT_CHALLENGE_WINDOW: '0x37dd9e6c',
} as const;
export class AereCompliancePoolClient {
constructor(public readonly provider: RpcProvider, public readonly address: string) {}
async denomination(): Promise<bigint> {
return BigInt(await call(this.provider, this.address, POOL_SEL.DENOMINATION));
}
async depositCount(): Promise<bigint> {
return BigInt(await call(this.provider, this.address, POOL_SEL.depositCount));
}
async latestDepositRoot(): Promise<string> {
return await call(this.provider, this.address, POOL_SEL.latestDepositRoot);
}
async isAssociationRootValid(root: string): Promise<boolean> {
return BigInt(await call(this.provider, this.address, POOL_SEL.isAssociationRootValid + encB(root))) === 1n;
}
async isSpent(nullifierHash: string): Promise<boolean> {
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<string> {
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<string> {
return send(this.provider, from, this.address, POOL_SEL.publishAssociationRoot + encB(root));
}
setComplianceProvider(from: string, provider: string, allowed: boolean): Promise<string> {
return send(this.provider, from, this.address, POOL_SEL.setComplianceProvider + encA(provider) + pad32(allowed ? '1' : '0'));
}
/* R5/R6 additions — challenge-bond + on-chain Merkle tree views. */
/** Foundation proposes a new association root. Anyone can publishAssociationRoot
* after the 24h challenge window expires + no active challenge. */
proposeAssociationRoot(from: string, root: string): Promise<string> {
return send(this.provider, from, this.address, POOL_SEL.proposeAssociationRoot + encB(root));
}
/** Challenge a proposed root. Caller MUST have approved CHALLENGE_BOND of
* BOND_TOKEN (= same TOKEN as DENOMINATION) to the pool first. If
* Foundation dismisses the challenge, bond is burned. */
challengeAssociationRoot(from: string, root: string, reason: string): Promise<string> {
const r = encString(reason);
// head: root(32) + offset(32) = 2 slots
const head = encB(root) + encU(2n * 32n);
return send(this.provider, from, this.address, POOL_SEL.challengeAssociationRoot + head + r.body);
}
/** Foundation dismisses a challenge — burns bond, resets 24h window. */
dismissChallenge(from: string, root: string): Promise<string> {
return send(this.provider, from, this.address, POOL_SEL.dismissAssociationRootChallenge + encB(root));
}
async challengeBond(): Promise<bigint> {
return BigInt(await call(this.provider, this.address, POOL_SEL.CHALLENGE_BOND));
}
async maxDismissalsPerRoot(): Promise<number> {
return Number(BigInt(await call(this.provider, this.address, POOL_SEL.MAX_DISMISSALS_PER_ROOT)));
}
async dismissCount(root: string): Promise<number> {
return Number(BigInt(await call(this.provider, this.address, POOL_SEL.associationRootDismissCount + encB(root))));
}
async challenger(root: string): Promise<string> {
const r = await call(this.provider, this.address, POOL_SEL.associationRootChallenger + encB(root));
return '0x' + r.slice(-40);
}
async isKnownRoot(root: string): Promise<boolean> {
return BigInt(await call(this.provider, this.address, POOL_SEL.isKnownRoot + encB(root))) === 1n;
}
async getLastRoot(): Promise<string> {
return await call(this.provider, this.address, POOL_SEL.getLastRoot);
}
async nextLeafIndex(): Promise<number> {
return Number(BigInt(await call(this.provider, this.address, POOL_SEL.nextLeafIndex)));
}
/**
* Compute the actual leaf as the contract does (R6 fix): leaf =
* keccak256(commitment || depositor). SDK consumers MUST build their ZK
* proof against this leaf, not against the raw commitment.
*/
static computeActualLeaf(commitment: string, depositor: string): string {
// simple wrapper — ethers/viem caller computes keccak256 over the
// packed encoding. Pure helper returns the input format the verifier
// public-input slot expects.
return JSON.stringify({ commitment, depositor, note: 'leaf = keccak256(commitment || depositor) — compute via your hashing lib' });
}
/** 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<string> {
// 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);
}
}

185
src/compliance/index.ts Normal file
View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<string> {
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<boolean> {
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<number> {
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<bigint> {
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<bigint> {
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<boolean> {
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<bigint> {
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<string> {
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<bigint> {
return BigInt(await ethCall(this.provider, this.address, AI_SEL.attestationCount + encBytes32(modelId)));
}
async registerModel(from: string, modelId: string, signer: string, description: string): Promise<string> {
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';

View File

@ -0,0 +1,220 @@
/**
* AereCoreBookClient typed helpers for AereCoreBookV0 native CLOB.
*
* Contract: aerenew/contracts/contracts/corebook/AereCoreBookV0.sol
*
* Post-R6 hardening notes:
* - `place()` is the GTC limit entrypoint; refuses CROSSING orders (use
* iocFill / placeWithSlippage for those).
* - `iocFill(side, worstPrice, qty)` naive IOC sweep capped at worstPrice.
* - `placeWithSlippage(side, price, qty, tif, minBaseFilled, maxQuoteSpent,
* maxAvgPriceWei)` — REAL slippage protection. SDKs MUST prefer this.
* - `placeProtected(...)` adds a per-order cancelDelayBlocks so mempool
* races cannot front-run a maker's cancel.
* - BUY taker pays `principal + maxFee + 256-wei buffer`; maker receives
* FULL `quoteAmount` (no fee skim).
* - On transfer-revert (USDC blacklist, ERC777 hook) the recipient gets a
* claimable credit. Pull via `claim(wantBase)`.
* - When the book is fully empty, `sweepDust()` forwards leftover QUOTE
* (excluding pendingSinkFee + totalClaimableQuote) to AereSink.
*/
import type { RpcProvider } from '../sink/AereSinkClient.js';
export interface AereCoreBookConfig {
address: `0x${string}`;
provider: RpcProvider;
}
export type Side = 0 | 1; // BUY=0, SELL=1
export type TimeInForce = 0 | 1; // GTC=0, IOC=1
/* --------------------------------- selectors --------------------------------- */
const SEL = {
// entrypoints
place: '0x8121866a', // place(uint8,uint128,uint128,uint8)
placeProtected: '0x8af26684', // placeProtected(uint8,uint128,uint128,uint8,uint16)
placeWithSlippage: '0xf4526bbc', // placeWithSlippage(uint8,uint128,uint128,uint8,uint128,uint256,uint128)
placeWithSlippageProtected: '0xc0efac90', // placeWithSlippageProtected(uint8,uint128,uint128,uint8,uint128,uint256,uint16,uint128)
iocFill: '0x5bbb981a', // iocFill(uint8,uint128,uint128)
cancel: '0x81649d06', // cancel(uint128)
flushFees: '0x313d8cb9', // flushFees()
sweepDust: '0xa53df2e2', // sweepDust()
claim: '0x2d81a78e', // claim(bool)
// reads (selectors recomputed 2026-06-11 against AereCoreBookV0.sol — the
// previous values for bestBid/bestAsk/pendingSinkFee + immutables were wrong)
bestBid: '0x20c1614b', // bestBid()
bestAsk: '0xcffb7d15', // bestAsk()
pendingSinkFee: '0x20a0e28c', // pendingSinkFee()
totalClaimableQuote: '0x17554eb7', // totalClaimableQuote()
totalClaimableBase: '0x200d51bc', // totalClaimableBase()
claimableQuote: '0x2d9cf134', // claimableQuote(address)
claimableBase: '0x22046da1', // claimableBase(address)
TAKER_FEE_BPS: '0x2057bfa0', // TAKER_FEE_BPS()
PRICE_TICK: '0xaffabe45', // PRICE_TICK()
LOT_SIZE: '0x7fee67a2', // LOT_SIZE()
QUOTE_DECIMALS_FACTOR: '0x3da2ad02', // QUOTE_DECIMALS_FACTOR()
BASE_DECIMALS_FACTOR: '0x43878833', // BASE_DECIMALS_FACTOR()
PRICE_SCALE: '0xc33f59d3', // PRICE_SCALE()
MAX_CANCEL_DELAY_BLOCKS: '0xef8045a2', // MAX_CANCEL_DELAY_BLOCKS()
DEFAULT_CANCEL_DELAY_BLOCKS: '0x3d81cedd', // DEFAULT_CANCEL_DELAY_BLOCKS()
} as const;
/* ------------------------------ encode helpers ------------------------------ */
function padUint(value: bigint | number, bytes: number = 32): string {
const hex = BigInt(value).toString(16);
return hex.padStart(bytes * 2, '0');
}
function padAddress(addr: `0x${string}`): string {
return addr.slice(2).toLowerCase().padStart(64, '0');
}
function hexToBigInt(hex: string): bigint {
if (!/^0x[0-9a-fA-F]+$/.test(hex)) throw new Error(`CoreBookClient: bad hex: ${hex}`);
return BigInt(hex);
}
/* --------------------------------- client --------------------------------- */
export class AereCoreBookClient {
readonly address: `0x${string}`;
private readonly provider: RpcProvider;
constructor(cfg: AereCoreBookConfig) {
this.address = cfg.address;
this.provider = cfg.provider;
}
/* ----------------------------- read methods ----------------------------- */
async readBestBid(): Promise<bigint> { return hexToBigInt(await this._call(SEL.bestBid)); }
async readBestAsk(): Promise<bigint> { return hexToBigInt(await this._call(SEL.bestAsk)); }
async readPendingSinkFee(): Promise<bigint> { return hexToBigInt(await this._call(SEL.pendingSinkFee)); }
async readTakerFeeBps(): Promise<bigint> { return hexToBigInt(await this._call(SEL.TAKER_FEE_BPS)); }
async readPriceTick(): Promise<bigint> { return hexToBigInt(await this._call(SEL.PRICE_TICK)); }
async readLotSize(): Promise<bigint> { return hexToBigInt(await this._call(SEL.LOT_SIZE)); }
async readQuoteFactor(): Promise<bigint> { return hexToBigInt(await this._call(SEL.QUOTE_DECIMALS_FACTOR)); }
async readBaseFactor(): Promise<bigint> { return hexToBigInt(await this._call(SEL.BASE_DECIMALS_FACTOR)); }
async readPriceScale(): Promise<bigint> { return hexToBigInt(await this._call(SEL.PRICE_SCALE)); }
async readTotalClaimableQuote(): Promise<bigint> { return hexToBigInt(await this._call(SEL.totalClaimableQuote)); }
async readTotalClaimableBase(): Promise<bigint> { return hexToBigInt(await this._call(SEL.totalClaimableBase)); }
async readClaimableQuote(user: `0x${string}`): Promise<bigint> {
return hexToBigInt(await this._call(SEL.claimableQuote + padAddress(user)));
}
async readClaimableBase(user: `0x${string}`): Promise<bigint> {
return hexToBigInt(await this._call(SEL.claimableBase + padAddress(user)));
}
/** Compute quote = price * qty * QUOTE_FACTOR / (PRICE_SCALE * BASE_FACTOR). */
async computeQuote(price: bigint, qty: bigint): Promise<bigint> {
const [quoteFactor, baseFactor, priceScale] = await Promise.all([
this.readQuoteFactor(), this.readBaseFactor(), this.readPriceScale(),
]);
return (price * qty * quoteFactor) / (priceScale * baseFactor);
}
/* -------------------------- write calldata builders -------------------------- */
/**
* GTC limit entrypoint REFUSES crossing orders. For crossing use
* `iocFill` or `placeWithSlippage`. Applies DEFAULT_CANCEL_DELAY_BLOCKS=2.
*/
encodePlace(side: Side, price: bigint, quantity: bigint, tif: TimeInForce): `0x${string}` {
return (SEL.place
+ padUint(side, 32) + padUint(price, 32) + padUint(quantity, 32) + padUint(tif, 32)) as `0x${string}`;
}
/** GTC + custom cancel-delay. Use placeWithSlippageProtected for crossing. */
encodePlaceProtected(side: Side, price: bigint, quantity: bigint, tif: TimeInForce, cancelDelayBlocks: number): `0x${string}` {
return (SEL.placeProtected
+ padUint(side, 32) + padUint(price, 32) + padUint(quantity, 32)
+ padUint(tif, 32) + padUint(cancelDelayBlocks, 32)) as `0x${string}`;
}
/**
* IOC sweep capped at worstPrice. NO avg-price protection use
* placeWithSlippage with explicit maxAvgPriceWei for surgical-sandwich
* protection.
*/
encodeIocFill(side: Side, worstPrice: bigint, quantity: bigint): `0x${string}` {
return (SEL.iocFill
+ padUint(side, 32) + padUint(worstPrice, 32) + padUint(quantity, 32)) as `0x${string}`;
}
/**
* Recommended entrypoint for any crossing order. Caller MUST set
* `maxAvgPriceWei` strictly tighter than `price` to get real sandwich
* protection (e.g. price*0.995 for 50bps band).
*/
encodePlaceWithSlippage(
side: Side, price: bigint, quantity: bigint, tif: TimeInForce,
minBaseFilled: bigint, maxQuoteSpent: bigint, maxAvgPriceWei: bigint,
): `0x${string}` {
return (SEL.placeWithSlippage
+ padUint(side, 32) + padUint(price, 32) + padUint(quantity, 32) + padUint(tif, 32)
+ padUint(minBaseFilled, 32) + padUint(maxQuoteSpent, 32) + padUint(maxAvgPriceWei, 32)) as `0x${string}`;
}
/** Full protection: crossing + slippage + cancel-delay. */
encodePlaceWithSlippageProtected(
side: Side, price: bigint, quantity: bigint, tif: TimeInForce,
minBaseFilled: bigint, maxQuoteSpent: bigint, cancelDelayBlocks: number, maxAvgPriceWei: bigint,
): `0x${string}` {
return (SEL.placeWithSlippageProtected
+ padUint(side, 32) + padUint(price, 32) + padUint(quantity, 32) + padUint(tif, 32)
+ padUint(minBaseFilled, 32) + padUint(maxQuoteSpent, 32)
+ padUint(cancelDelayBlocks, 32) + padUint(maxAvgPriceWei, 32)) as `0x${string}`;
}
encodeCancel(orderId: bigint): `0x${string}` {
return (SEL.cancel + padUint(orderId, 32)) as `0x${string}`;
}
encodeFlushFees(): `0x${string}` { return SEL.flushFees as `0x${string}`; }
/** Permissionless dust sweep — ONLY succeeds when book is fully empty. */
encodeSweepDust(): `0x${string}` { return SEL.sweepDust as `0x${string}`; }
/**
* Pull accumulated claimable balance after a failed transfer.
* @param wantBase true claim BASE side; false claim QUOTE side.
*/
encodeClaim(wantBase: boolean): `0x${string}` {
return (SEL.claim + padUint(wantBase ? 1 : 0, 32)) as `0x${string}`;
}
/* --------------------------- helper: recommended bands --------------------------- */
/**
* Returns recommended slippage parameters for a crossing order. The caller
* provides desired band (default 50bps); we compute maxAvgPriceWei such
* that the average fill cannot exceed it.
*/
recommendedSlippage(
side: Side, limitPrice: bigint, quantity: bigint, bpsBand: number = 50,
): { minBaseFilled: bigint; maxQuoteSpent: bigint; maxAvgPriceWei: bigint } {
const band = BigInt(bpsBand);
const maxAvg = side === 0
? (limitPrice * (10_000n - band)) / 10_000n
: (limitPrice * (10_000n + band)) / 10_000n;
return {
minBaseFilled: 0n,
maxQuoteSpent: (1n << 256n) - 1n,
maxAvgPriceWei: maxAvg,
};
}
/* ----------------------------- internals ------------------------------ */
private async _call(data: string): Promise<string> {
const result = await this.provider.request({
method: 'eth_call',
params: [{ to: this.address, data }, 'latest'],
});
if (typeof result !== 'string') throw new Error('CoreBookClient: bad eth_call');
return result;
}
}

View File

@ -0,0 +1,724 @@
/**
* CoreBookClient full typed read/write client + off-chain lens for
* AereCoreBookV0 (native CLOB, one contract per market).
*
* Contract: aerenew/contracts/contracts/corebook/AereCoreBookV0.sol
* (R6-hardened, 289/289 tests passing source of truth for all math here).
*
* 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.
*
* Market conventions (mirrors the contract exactly):
* - Side: 0 = BUY (quote base), 1 = SELL (base quote).
* - TimeInForce: 0 = GTC, 1 = IOC.
* - price is uint128, quote-per-base scaled by PRICE_SCALE = 1e18.
* Verified against `_computeQuote`:
* quote = price * qty * QUOTE_DECIMALS_FACTOR / (1e18 * BASE_DECIMALS_FACTOR)
* For human price P (quote units per base unit) the decimals factors
* cancel, so priceWei = P * 1e18 regardless of token decimals.
* - quantity is uint128 in base smallest units, multiple of LOT_SIZE.
* - BUY locks quote principal + max taker fee + 256-wei buffer
* (`_lockBuyFunds`: MAX_OPEN_ORDERS_PER_MAKER covers worst-case per-fill
* fee-ceiling rounding). SELL locks base. ERC-20 approve required first.
* - `place()` REFUSES crossing orders (CrossingRequiresSlippage) and applies
* a default 2-block cancel delay. Crossing flow must go through
* `iocFill` / `placeWithSlippage(Protected)`.
* - orderId = uint128(keccak256(abi.encode(maker, makerNonce))) the lens
* derives historical ids client-side (see `deriveOrderId`).
*/
import type { RpcProvider } from '../sink/AereSinkClient.js';
import type { Side, TimeInForce } from './AereCoreBookClient.js';
export interface CoreBookClientConfig {
address: `0x${string}`;
provider: RpcProvider;
}
/* ------------------------------- constants ------------------------------- */
/** Contract PRICE_SCALE — price uint128 is quote-per-base scaled by 1e18. */
export const COREBOOK_PRICE_SCALE = 10n ** 18n;
/** `bestAsk()` sentinel when the ask side is empty (type(uint128).max). */
export const COREBOOK_ASK_EMPTY = (1n << 128n) - 1n;
/** `bestBid()` sentinel when the bid side is empty. */
export const COREBOOK_BID_EMPTY = 0n;
/** `_lockBuyFunds` flat buffer = MAX_OPEN_ORDERS_PER_MAKER (256 wei of quote). */
export const COREBOOK_BUY_LOCK_BUFFER = 256n;
/** Contract caps for cancel-delay (blocks). */
export const COREBOOK_DEFAULT_CANCEL_DELAY_BLOCKS = 2;
export const COREBOOK_MAX_CANCEL_DELAY_BLOCKS = 1200;
const UINT128_MAX = (1n << 128n) - 1n;
const UINT256_MAX = (1n << 256n) - 1n;
/* ------------------------------- selectors ------------------------------- */
const SEL = {
// entrypoints
place: '0x8121866a', // place(uint8,uint128,uint128,uint8)
placeProtected: '0x8af26684', // placeProtected(uint8,uint128,uint128,uint8,uint16)
iocFill: '0x5bbb981a', // iocFill(uint8,uint128,uint128)
placeWithSlippage: '0xf4526bbc', // placeWithSlippage(uint8,uint128,uint128,uint8,uint128,uint256,uint128)
placeWithSlippageProtected: '0xc0efac90', // placeWithSlippageProtected(uint8,uint128,uint128,uint8,uint128,uint256,uint16,uint128)
cancel: '0x81649d06', // cancel(uint128)
claim: '0x2d81a78e', // claim(bool)
flushFees: '0x313d8cb9', // flushFees()
sweepDust: '0xa53df2e2', // sweepDust()
// book state
bestBid: '0x20c1614b', // bestBid()
bestAsk: '0xcffb7d15', // bestAsk()
pendingSinkFee: '0x20a0e28c', // pendingSinkFee()
getOrder: '0x117d4128', // getOrder(uint128)
bidHeadAtPrice: '0xf056e2d7', // bidHeadAtPrice(uint128)
askHeadAtPrice: '0xec29c07b', // askHeadAtPrice(uint128)
nextPopulatedAsk: '0xcce549e0', // nextPopulatedAsk(uint128)
nextPopulatedBid: '0xe3340be7', // nextPopulatedBid(uint128)
claimableQuote: '0x2d9cf134', // claimableQuote(address)
claimableBase: '0x22046da1', // claimableBase(address)
makerNonce: '0xcd370bb2', // makerNonce(address)
openOrderCount: '0x639babfb', // openOrderCount(address)
// immutables
BASE: '0xec342ad0', // BASE()
QUOTE: '0x9c579839', // QUOTE()
SINK: '0xae9fc205', // SINK()
PRICE_TICK: '0xaffabe45', // PRICE_TICK()
LOT_SIZE: '0x7fee67a2', // LOT_SIZE()
TAKER_FEE_BPS: '0x2057bfa0', // TAKER_FEE_BPS()
QUOTE_DECIMALS_FACTOR: '0x3da2ad02', // QUOTE_DECIMALS_FACTOR()
BASE_DECIMALS_FACTOR: '0x43878833', // BASE_DECIMALS_FACTOR()
// ERC-20 (approve flow)
allowance: '0xdd62ed3e', // allowance(address,address)
approve: '0x095ea7b3', // approve(address,uint256)
balanceOf: '0x70a08231', // balanceOf(address)
} as const;
/* ----------------------------- event topic0 ------------------------------ */
// keccak256 of the exact event signature strings from AereCoreBookV0.sol
// (enums are uint8 in the ABI signature).
/** OrderPlaced(uint128,address,uint8,uint128,uint128) */
export const TOPIC_ORDER_PLACED =
'0x2229a00bfbf976283c0743a5a848fbf6e1a6843ac1eb8a9744a624a68b57632c';
/** OrderCancelled(uint128,address,uint128) */
export const TOPIC_ORDER_CANCELLED =
'0x5264d0657a58acd302a6fb1f840ac461d0347aae6be5019868b66ee71b1b8cab';
/** Filled(uint128,uint128,address,address,uint8,uint128,uint128,uint256) */
export const TOPIC_FILLED =
'0x39440616edbc0f927b9a918e409c20c622cd6e4570e90f80b679f6181e4f70f7';
/** Claimed(address,address,uint256) */
export const TOPIC_CLAIMED =
'0xf7a40077ff7a04c7e61f6f26fb13774259ddf1b6bce9ecf26a8276cdd3992683';
/* ----------------------------- encode helpers ---------------------------- */
function pad32(hex: string): string {
const h = hex.toLowerCase().replace(/^0x/, '');
return h.padStart(64, '0');
}
function encUint(n: bigint | number): string {
const v = BigInt(n);
if (v < 0n || v > UINT256_MAX) throw new Error(`CoreBookClient: uint out of range: ${v}`);
return pad32(v.toString(16));
}
function encAddr(addr: string): string { return pad32(addr.replace(/^0x/, '')); }
function word(data: string, i: number): bigint {
const h = data.replace(/^0x/, '');
const w = h.slice(i * 64, i * 64 + 64);
if (w.length !== 64) throw new Error('CoreBookClient: short returndata');
return BigInt('0x' + w);
}
function addrWord(data: string, i: number): string {
const h = data.replace(/^0x/, '');
return '0x' + h.slice(i * 64 + 24, i * 64 + 64);
}
function topicAddr(topic: string): string { return '0x' + topic.slice(26).toLowerCase(); }
async function ethCall(p: RpcProvider, to: string, data: string): Promise<string> {
const r = await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] });
if (typeof r !== 'string') throw new Error('CoreBookClient: bad eth_call result');
return r;
}
async function ethSend(p: RpcProvider, from: string, to: string, data: string): Promise<string> {
return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value: '0x0' }] }) as string;
}
/* --------------------- keccak-256 (vanilla, no deps) --------------------- */
// Needed ONLY for client-side orderId derivation:
// orderId = uint128(keccak256(abi.encode(maker, nonce)))
// (AereCoreBookV0._nextOrderId — nonce is PRE-incremented, so the first
// order of a maker uses nonce 1). Verified against the canonical Keccak-256
// test vector keccak256("") = 0xc5d2...a456 and ethers.keccak256.
const KECCAK_RC: bigint[] = [
0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n,
0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n,
0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an,
0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n,
0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an,
0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n,
];
// rotation offsets r[x][y], flattened as [x + 5y]
const KECCAK_ROT: number[] = [
0, 1, 62, 28, 27, 36, 44, 6, 55, 20, 3, 10, 43, 25, 39, 41, 45, 15, 21, 8, 18, 2, 61, 56, 14,
];
const M64 = (1n << 64n) - 1n;
function rotl64(v: bigint, n: number): bigint {
if (n === 0) return v;
return ((v << BigInt(n)) | (v >> BigInt(64 - n))) & M64;
}
function keccakF1600(s: bigint[]): void {
for (let r = 0; r < 24; r++) {
// theta
const c: bigint[] = new Array(5);
for (let x = 0; x < 5; x++) c[x] = s[x] ^ s[x + 5] ^ s[x + 10] ^ s[x + 15] ^ s[x + 20];
for (let x = 0; x < 5; x++) {
const d = c[(x + 4) % 5] ^ rotl64(c[(x + 1) % 5], 1);
for (let y = 0; y < 25; y += 5) s[x + y] ^= d;
}
// rho + pi
const b: bigint[] = new Array(25);
for (let x = 0; x < 5; x++) {
for (let y = 0; y < 5; y++) {
b[y + 5 * ((2 * x + 3 * y) % 5)] = rotl64(s[x + 5 * y], KECCAK_ROT[x + 5 * y]);
}
}
// chi
for (let x = 0; x < 5; x++) {
for (let y = 0; y < 25; y += 5) {
s[x + y] = b[x + y] ^ ((~b[((x + 1) % 5) + y] & M64) & b[((x + 2) % 5) + y]);
}
}
// iota
s[0] ^= KECCAK_RC[r];
}
}
function keccak256Bytes(input: Uint8Array): Uint8Array {
const rate = 136; // 1088-bit rate for keccak-256
const s: bigint[] = new Array(25).fill(0n);
const padLen = rate - (input.length % rate);
const padded = new Uint8Array(input.length + padLen);
padded.set(input);
padded[input.length] = 0x01; // keccak (NOT sha3 0x06) domain padding
padded[padded.length - 1] |= 0x80;
for (let off = 0; off < padded.length; off += rate) {
for (let i = 0; i < rate / 8; i++) {
let lane = 0n;
for (let b2 = 7; b2 >= 0; b2--) lane = (lane << 8n) | BigInt(padded[off + i * 8 + b2]);
s[i] ^= lane;
}
keccakF1600(s);
}
const out = new Uint8Array(32);
for (let i = 0; i < 4; i++) {
let lane = s[i];
for (let b2 = 0; b2 < 8; b2++) { out[i * 8 + b2] = Number(lane & 0xffn); lane >>= 8n; }
}
return out;
}
function hexToBytes(hexNo0x: string): Uint8Array {
const out = new Uint8Array(hexNo0x.length / 2);
for (let i = 0; i < out.length; i++) out[i] = parseInt(hexNo0x.slice(i * 2, i * 2 + 2), 16);
return out;
}
/**
* Derive a CoreBook orderId exactly like `_nextOrderId`:
* orderId = uint128(keccak256(abi.encode(maker, nonce)))
* The contract pre-increments, so a maker's first order uses nonce = 1 and
* the latest used nonce equals `makerNonce(maker)`.
*/
export function deriveOrderId(maker: string, nonce: bigint): bigint {
const hash = keccak256Bytes(hexToBytes(encAddr(maker) + encUint(nonce)));
let v = 0n;
for (const b of hash.subarray(16)) v = (v << 8n) | BigInt(b); // low 128 bits
return v;
}
/* --------------------------- price / qty helpers -------------------------- */
/**
* Convert a human decimal price string ("63000", "0.0125") into the uint128
* price the book expects: quote-per-base scaled by PRICE_SCALE = 1e18.
*
* Verified against `_computeQuote`:
* quote = price * qty * 10^quoteDecimals / (1e18 * 10^baseDecimals)
* For qty = 10^baseDecimals (one whole base unit) this yields exactly
* P * 10^quoteDecimals i.e. the decimals factors CANCEL and
* priceWei = P * 1e18 independent of token decimals. The decimals params
* are kept in the signature for call-site self-documentation + validation.
*/
export function humanPriceToWei(price: string, quoteDecimals: number, baseDecimals: number): bigint {
if (!Number.isInteger(quoteDecimals) || !Number.isInteger(baseDecimals)
|| quoteDecimals < 0 || baseDecimals < 0 || quoteDecimals > 77 || baseDecimals > 77) {
throw new Error('CoreBookClient: bad token decimals');
}
const m = /^(\d+)(?:\.(\d+))?$/.exec(price.trim());
if (!m) throw new Error(`CoreBookClient: bad price string: ${price}`);
const frac = (m[2] ?? '');
if (frac.length > 18) throw new Error('CoreBookClient: price has more than 18 decimal places');
return BigInt(m[1]) * COREBOOK_PRICE_SCALE + BigInt(frac.padEnd(18, '0') || '0');
}
/** Inverse of humanPriceToWei — render a 1e18-scaled price as a decimal string. */
export function weiPriceToHuman(priceWei: bigint): string {
const whole = priceWei / COREBOOK_PRICE_SCALE;
const frac = (priceWei % COREBOOK_PRICE_SCALE).toString().padStart(18, '0').replace(/0+$/, '');
return frac.length > 0 ? `${whole}.${frac}` : whole.toString();
}
/** Snap a price to a multiple of PRICE_TICK (default rounds down). */
export function snapToTick(price: bigint, priceTick: bigint, mode: 'down' | 'up' | 'nearest' = 'down'): bigint {
if (priceTick <= 0n) throw new Error('CoreBookClient: priceTick must be > 0');
const rem = price % priceTick;
if (rem === 0n) return price;
if (mode === 'up') return price + (priceTick - rem);
if (mode === 'nearest') return rem * 2n >= priceTick ? price + (priceTick - rem) : price - rem;
return price - rem;
}
/** Snap a base quantity to a multiple of LOT_SIZE (default rounds down). */
export function snapToLot(quantity: bigint, lotSize: bigint, mode: 'down' | 'up' | 'nearest' = 'down'): bigint {
return snapToTick(quantity, lotSize, mode);
}
/**
* Exact mirror of `_computeQuote` (floor division):
* quote = price * qty * quoteFactor / (PRICE_SCALE * baseFactor)
* Factors are 10**decimals (read them from `marketInfo()`).
*/
export function computeQuote(priceWei: bigint, quantity: bigint, quoteFactor: bigint, baseFactor: bigint): bigint {
return (priceWei * quantity * quoteFactor) / (COREBOOK_PRICE_SCALE * baseFactor);
}
/**
* Exact mirror of `_lockBuyFunds` the QUOTE amount a BUY order must have
* approved + available:
* principal = _computeQuote(price, qty) (must be > 0)
* feeReserve = ceil(principal * takerFeeBps / 1e4) ((p*bps + 9999) / 10000)
* locked = principal + feeReserve + 256
* The flat 256-wei buffer is MAX_OPEN_ORDERS_PER_MAKER covers worst-case
* per-fill fee-ceiling rounding across up to 256 maker fills (AUDIT FIX R6
* CRITICAL #2). Unspent lock is refunded in the same transaction.
*/
export function computeMaxBuyLock(
priceWei: bigint, quantity: bigint, takerFeeBps: number | bigint,
quoteFactor: bigint, baseFactor: bigint,
): bigint {
const principal = computeQuote(priceWei, quantity, quoteFactor, baseFactor);
if (principal === 0n) throw new Error('CoreBookClient: BUY lock principal is zero (qty*price below min quote unit)');
const feeReserve = (principal * BigInt(takerFeeBps) + 9999n) / 10_000n;
return principal + feeReserve + COREBOOK_BUY_LOCK_BUFFER;
}
/* --------------------------------- types --------------------------------- */
export interface CoreBookMarketInfo {
base: string;
quote: string;
sink: string;
priceTick: bigint;
lotSize: bigint;
takerFeeBps: number;
quoteDecimalsFactor: bigint;
baseDecimalsFactor: bigint;
}
/** Decoded `getOrder` view (field order matches the Order struct). */
export interface CoreBookOrder {
maker: string;
price: bigint;
quantity: bigint;
placedAt: bigint;
placedAtBlock: bigint;
side: Side;
exists: boolean;
cancelDelayBlocks: number;
nextAtPrice: bigint;
prevAtPrice: bigint;
}
export interface CoreBookOpenOrder extends CoreBookOrder {
orderId: bigint;
}
export interface CoreBookDepthLevel {
price: bigint;
quantity: bigint; // sum of resting base quantity at this level
orderCount: number;
truncated: boolean; // true if maxOrdersPerLevel cap stopped the walk early
}
export interface CoreBookDepth {
bids: CoreBookDepthLevel[]; // best (highest) bid first
asks: CoreBookDepthLevel[]; // best (lowest) ask first
}
/** Minimal eth_getTransactionReceipt log shape. */
export interface CoreBookLog {
address: string;
topics: string[];
data: string;
transactionHash?: string;
logIndex?: string | number;
}
export interface CoreBookEventBase {
address: string;
transactionHash?: string;
logIndex?: number;
}
export interface CoreBookOrderPlacedEvent extends CoreBookEventBase {
type: 'OrderPlaced';
orderId: bigint; maker: string; side: Side; price: bigint; quantity: bigint;
}
export interface CoreBookOrderCancelledEvent extends CoreBookEventBase {
type: 'OrderCancelled';
orderId: bigint; maker: string; unfilled: bigint;
}
export interface CoreBookFilledEvent extends CoreBookEventBase {
type: 'Filled';
takerOrderId: bigint; makerOrderId: bigint; taker: string; maker: string;
takerSide: Side; price: bigint; quantity: bigint; takerFee: bigint;
}
export interface CoreBookClaimedEvent extends CoreBookEventBase {
type: 'Claimed';
who: string; token: string; amount: bigint;
}
export type CoreBookEvent =
| CoreBookOrderPlacedEvent
| CoreBookOrderCancelledEvent
| CoreBookFilledEvent
| CoreBookClaimedEvent;
/* ----------------------------- event decoding ---------------------------- */
function logMeta(log: CoreBookLog): CoreBookEventBase {
const meta: CoreBookEventBase = { address: log.address.toLowerCase() };
if (log.transactionHash !== undefined) meta.transactionHash = log.transactionHash;
if (log.logIndex !== undefined) meta.logIndex = Number(BigInt(log.logIndex));
return meta;
}
/**
* Decode AereCoreBookV0 logs from a transaction receipt (manual topic match
* unknown topics are skipped). Pass `book` to filter to one market address.
*/
export function decodeCoreBookLogs(logs: CoreBookLog[], book?: string): CoreBookEvent[] {
const out: CoreBookEvent[] = [];
const filter = book?.toLowerCase();
for (const log of logs) {
if (filter && log.address.toLowerCase() !== filter) continue;
const t0 = log.topics[0]?.toLowerCase();
if (t0 === TOPIC_ORDER_PLACED) {
// OrderPlaced(uint128 indexed orderId, address indexed maker, uint8 side, uint128 price, uint128 quantity)
out.push({
...logMeta(log), type: 'OrderPlaced',
orderId: BigInt(log.topics[1]),
maker: topicAddr(log.topics[2]),
side: Number(word(log.data, 0)) as Side,
price: word(log.data, 1),
quantity: word(log.data, 2),
});
} else if (t0 === TOPIC_ORDER_CANCELLED) {
// OrderCancelled(uint128 indexed orderId, address indexed maker, uint128 unfilled)
out.push({
...logMeta(log), type: 'OrderCancelled',
orderId: BigInt(log.topics[1]),
maker: topicAddr(log.topics[2]),
unfilled: word(log.data, 0),
});
} else if (t0 === TOPIC_FILLED) {
// Filled(uint128 indexed takerOrderId, uint128 indexed makerOrderId, address indexed taker,
// address maker, uint8 takerSide, uint128 price, uint128 quantity, uint256 takerFee)
out.push({
...logMeta(log), type: 'Filled',
takerOrderId: BigInt(log.topics[1]),
makerOrderId: BigInt(log.topics[2]),
taker: topicAddr(log.topics[3]),
maker: addrWord(log.data, 0),
takerSide: Number(word(log.data, 1)) as Side,
price: word(log.data, 2),
quantity: word(log.data, 3),
takerFee: word(log.data, 4),
});
} else if (t0 === TOPIC_CLAIMED) {
// Claimed(address indexed who, address indexed token, uint256 amount)
out.push({
...logMeta(log), type: 'Claimed',
who: topicAddr(log.topics[1]),
token: topicAddr(log.topics[2]),
amount: word(log.data, 0),
});
}
}
return out;
}
/* --------------------------------- client -------------------------------- */
export class CoreBookClient {
readonly address: `0x${string}`;
readonly provider: RpcProvider;
constructor(cfg: CoreBookClientConfig) {
this.address = cfg.address;
this.provider = cfg.provider;
}
/* ---- market info (immutables) ----------------------------------------- */
async marketInfo(): Promise<CoreBookMarketInfo> {
const [base, quote, sink, tick, lot, fee, qf, bf] = await Promise.all([
ethCall(this.provider, this.address, SEL.BASE),
ethCall(this.provider, this.address, SEL.QUOTE),
ethCall(this.provider, this.address, SEL.SINK),
ethCall(this.provider, this.address, SEL.PRICE_TICK),
ethCall(this.provider, this.address, SEL.LOT_SIZE),
ethCall(this.provider, this.address, SEL.TAKER_FEE_BPS),
ethCall(this.provider, this.address, SEL.QUOTE_DECIMALS_FACTOR),
ethCall(this.provider, this.address, SEL.BASE_DECIMALS_FACTOR),
]);
return {
base: addrWord(base, 0), quote: addrWord(quote, 0), sink: addrWord(sink, 0),
priceTick: word(tick, 0), lotSize: word(lot, 0),
takerFeeBps: Number(word(fee, 0)),
quoteDecimalsFactor: word(qf, 0), baseDecimalsFactor: word(bf, 0),
};
}
/* ---- book state -------------------------------------------------------- */
/** Highest resting BUY price (0 = bid side empty). */
async bestBid(): Promise<bigint> { return word(await ethCall(this.provider, this.address, SEL.bestBid), 0); }
/** Lowest resting SELL price (COREBOOK_ASK_EMPTY sentinel = ask side empty). */
async bestAsk(): Promise<bigint> { return word(await ethCall(this.provider, this.address, SEL.bestAsk), 0); }
async pendingSinkFee(): Promise<bigint> { return word(await ethCall(this.provider, this.address, SEL.pendingSinkFee), 0); }
async makerNonce(maker: string): Promise<bigint> {
return word(await ethCall(this.provider, this.address, SEL.makerNonce + encAddr(maker)), 0);
}
async openOrderCount(maker: string): Promise<bigint> {
return word(await ethCall(this.provider, this.address, SEL.openOrderCount + encAddr(maker)), 0);
}
/** Failed-transfer credits pulled via claimBase/claimQuote. */
async claimableOf(user: string): Promise<{ base: bigint; quote: bigint }> {
const [b, q] = await Promise.all([
ethCall(this.provider, this.address, SEL.claimableBase + encAddr(user)),
ethCall(this.provider, this.address, SEL.claimableQuote + encAddr(user)),
]);
return { base: word(b, 0), quote: word(q, 0) };
}
async getOrder(orderId: bigint): Promise<CoreBookOrder> {
const d = await ethCall(this.provider, this.address, SEL.getOrder + encUint(orderId));
// Order struct = 10 static fields → 10 inline words.
return {
maker: addrWord(d, 0),
price: word(d, 1),
quantity: word(d, 2),
placedAt: word(d, 3),
placedAtBlock: word(d, 4),
side: Number(word(d, 5)) as Side,
exists: word(d, 6) === 1n,
cancelDelayBlocks: Number(word(d, 7)),
nextAtPrice: word(d, 8),
prevAtPrice: word(d, 9),
};
}
/* ---- lens: depth + open orders (pure view composition) ----------------- */
/**
* Aggregate the top `levels` price levels per side by walking the
* populated-level linked lists + per-level FIFO order lists. View-only;
* cost is one eth_call per pointer hop + per order, so keep `levels`
* UI-sized (e.g. 10-25).
*/
async depth(levels: number, opts: { maxOrdersPerLevel?: number } = {}): Promise<CoreBookDepth> {
const cap = opts.maxOrdersPerLevel ?? 256;
const [bb, ba] = await Promise.all([this.bestBid(), this.bestAsk()]);
const asks: CoreBookDepthLevel[] = [];
const bids: CoreBookDepthLevel[] = [];
let ap = ba;
while (ap !== COREBOOK_ASK_EMPTY && ap !== 0n && asks.length < levels) {
asks.push(await this._levelDepth(false, ap, cap));
ap = await this._readPriceMap(SEL.nextPopulatedAsk, ap); // 0 = end of list
}
let bp = bb;
while (bp !== COREBOOK_BID_EMPTY && bids.length < levels) {
bids.push(await this._levelDepth(true, bp, cap));
bp = await this._readPriceMap(SEL.nextPopulatedBid, bp); // 0 = end of list
}
return { bids, asks };
}
/**
* Enumerate a maker's resting orders WITHOUT an enumeration view on the
* contract: derive orderIds from makerNonce (latest first, see
* `deriveOrderId`) and probe `getOrder` until `openOrderCount` orders are
* found or `maxScan` nonces have been checked (high-churn makers may need
* a larger budget).
*/
async openOrdersOf(maker: string, opts: { maxScan?: number } = {}): Promise<CoreBookOpenOrder[]> {
const [nonce, open] = await Promise.all([this.makerNonce(maker), this.openOrderCount(maker)]);
if (open === 0n || nonce === 0n) return [];
const maxScan = BigInt(opts.maxScan ?? 4096);
const makerLc = maker.toLowerCase();
const found: CoreBookOpenOrder[] = [];
const BATCH = 16;
let scanned = 0n;
let n = nonce;
while (n >= 1n && scanned < maxScan && BigInt(found.length) < open) {
const batch: bigint[] = [];
while (batch.length < BATCH && n >= 1n && scanned < maxScan) { batch.push(n); n -= 1n; scanned += 1n; }
const orders = await Promise.all(batch.map(async (nn) => {
const orderId = deriveOrderId(maker, nn);
return { orderId, order: await this.getOrder(orderId) };
}));
for (const { orderId, order } of orders) {
if (order.exists && order.maker.toLowerCase() === makerLc) {
found.push({ orderId, ...order });
if (BigInt(found.length) >= open) break;
}
}
}
return found;
}
/* ---- writes (caller signs via EIP-1193 from) ---------------------------- */
/**
* GTC limit order via `place` (default 2-block cancel delay) or
* `placeProtected` when `cancelDelayBlocks` is given. REVERTS with
* CrossingRequiresSlippage if the order would cross route crossing
* orders through placeIOC / placeWithSlippage instead.
*/
async placeGTC(from: string, side: Side, price: bigint, quantity: bigint, cancelDelayBlocks?: number): Promise<string> {
if (cancelDelayBlocks === undefined) {
return ethSend(this.provider, from, this.address,
SEL.place + encUint(side) + encUint(price) + encUint(quantity) + encUint(0 /* GTC */));
}
if (cancelDelayBlocks < 0 || cancelDelayBlocks > COREBOOK_MAX_CANCEL_DELAY_BLOCKS) {
throw new Error('CoreBookClient: cancelDelayBlocks out of range (0-1200)');
}
return ethSend(this.provider, from, this.address,
SEL.placeProtected + encUint(side) + encUint(price) + encUint(quantity)
+ encUint(0 /* GTC */) + encUint(cancelDelayBlocks));
}
/**
* IOC sweep via `iocFill` fills whatever crosses up to `worstPrice`,
* discards the rest. Average price is only capped at worstPrice; for a
* tighter band use placeWithSlippage with explicit maxAvgPriceWei.
*/
async placeIOC(from: string, side: Side, worstPrice: bigint, quantity: bigint): Promise<string> {
return ethSend(this.provider, from, this.address,
SEL.iocFill + encUint(side) + encUint(worstPrice) + encUint(quantity));
}
/**
* Recommended entrypoint for crossing orders. Pass maxQuoteSpent =
* COREBOOK_UINT256_MAX-style no-cap via (1n<<256n)-1n if unused; set
* maxAvgPriceWei = 0n to disable the average-price band.
*/
async placeWithSlippage(
from: string, side: Side, price: bigint, quantity: bigint, tif: TimeInForce,
minBaseFilled: bigint, maxQuoteSpent: bigint, maxAvgPriceWei: bigint,
): Promise<string> {
return ethSend(this.provider, from, this.address,
SEL.placeWithSlippage + encUint(side) + encUint(price) + encUint(quantity) + encUint(tif)
+ encUint(minBaseFilled) + encUint(maxQuoteSpent) + encUint(maxAvgPriceWei));
}
/** Full protection: slippage bounds + per-order cancel delay. */
async placeWithSlippageProtected(
from: string, side: Side, price: bigint, quantity: bigint, tif: TimeInForce,
minBaseFilled: bigint, maxQuoteSpent: bigint, cancelDelayBlocks: number, maxAvgPriceWei: bigint,
): Promise<string> {
if (cancelDelayBlocks < 0 || cancelDelayBlocks > COREBOOK_MAX_CANCEL_DELAY_BLOCKS) {
throw new Error('CoreBookClient: cancelDelayBlocks out of range (0-1200)');
}
return ethSend(this.provider, from, this.address,
SEL.placeWithSlippageProtected + encUint(side) + encUint(price) + encUint(quantity) + encUint(tif)
+ encUint(minBaseFilled) + encUint(maxQuoteSpent) + encUint(cancelDelayBlocks) + encUint(maxAvgPriceWei));
}
async cancel(from: string, orderId: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.cancel + encUint(orderId));
}
/** Pull failed-transfer BASE credits (claim(true)). */
async claimBase(from: string): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.claim + encUint(1));
}
/** Pull failed-transfer QUOTE credits (claim(false)). */
async claimQuote(from: string): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.claim + encUint(0));
}
/** Permissionless — forwards pendingSinkFee to AereSink. */
async flushFees(from: string): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.flushFees);
}
/* ---- ERC-20 approve flow ------------------------------------------------ */
/** Current allowance granted by `owner` to THIS book for `token`. */
async allowanceForBook(token: string, owner: string): Promise<bigint> {
const d = await ethCall(this.provider, token, SEL.allowance + encAddr(owner) + encAddr(this.address));
return word(d, 0);
}
/** ERC-20 balance helper (base/quote funding checks before placing). */
async balanceOf(token: string, owner: string): Promise<bigint> {
return word(await ethCall(this.provider, token, SEL.balanceOf + encAddr(owner)), 0);
}
/** approve(book, amount) on `token` from `from`. */
async approveForBook(token: string, from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, token, SEL.approve + encAddr(this.address) + encUint(amount));
}
/**
* Checks allowance and approves only when insufficient. Returns the
* approve tx hash, or null when the existing allowance already covers
* `amount`. For BUY orders pass `computeMaxBuyLock(...)` as the amount;
* for SELL orders pass the base quantity.
*/
async ensureApprovalForBook(token: string, from: string, amount: bigint): Promise<string | null> {
const current = await this.allowanceForBook(token, from);
if (current >= amount) return null;
return this.approveForBook(token, from, amount);
}
/* ---- internals ---------------------------------------------------------- */
private async _readPriceMap(selector: string, price: bigint): Promise<bigint> {
return word(await ethCall(this.provider, this.address, selector + encUint(price)), 0);
}
private async _levelDepth(isBid: boolean, price: bigint, cap: number): Promise<CoreBookDepthLevel> {
const headSel = isBid ? SEL.bidHeadAtPrice : SEL.askHeadAtPrice;
let id = await this._readPriceMap(headSel, price);
let quantity = 0n;
let orderCount = 0;
while (id !== 0n && orderCount < cap) {
const o = await this.getOrder(id);
if (!o.exists) break; // transient inconsistency guard
quantity += o.quantity;
orderCount += 1;
id = o.nextAtPrice;
}
return { price, quantity, orderCount, truncated: id !== 0n && orderCount >= cap };
}
}

6
src/corebook/index.ts Normal file
View File

@ -0,0 +1,6 @@
// AereCoreBookV0 — native CLOB, one contract per (base, quote) market.
// CoreBookClient is the full read/write client + off-chain lens (depth,
// open-order enumeration, event decoding, price/qty/lock math helpers).
// AereCoreBookClient is the lighter calldata-builder variant.
export * from './CoreBookClient.js';
export * from './AereCoreBookClient.js';

View File

@ -1,6 +1,57 @@
export { AereClient, type AereClientOptions } from './client.js';
export { AERE_MAINNET, type AereContractName } from './addresses.js';
export { AERE_MAINNET, AERE_COREBOOK, type AereContractName, type CoreBookMarketSymbol } from './addresses.js';
export {
ERC20_ABI, WAERE_ABI, STAKING_V2_ABI, STAKING_V1_ABI, LENDING_ABI, STABLE_ABI,
IDENTITY_ABI, BRIDGE_ABI, FAUCET_ABI, SWAP_FACTORY_ABI, SWAP_PAIR_ABI, LIGHTNING_CHANNELS_ABI,
} from './abis.js';
// Week-1 flywheel clients — sAERE receipt vault + AereSink immutable router.
// Ship Q3 2026 (post audit contests).
export * from './sink/index.js';
export * from './saere/index.js';
// AereCoreBookV0 — native CLOB (R6-hardened: claim/sweepDust/protected entrypoints).
export * from './corebook/index.js';
// Wave-1 lending — isolated single-pair money markets + BadDebt insurance.
export { LendingMarketClient, AereInsuranceFundClient, RWATransferAdapterClient } from './lending/index.js';
export type { LendingMarketConfig } from './lending/index.js';
// Month 2-4 — Rollup-as-a-Service registry.
export { AereRaaSFactoryClient, AereRollupSettlementClient } from './raas/index.js';
// AERE402 agentic settlement — registry + facilitator + Express middleware.
export { AereAgentClient, AERE402SettlementClient, buildAuthTypedData,
createAere402Middleware, buildAere402TypedData } from './agentic/index.js';
export type { Aere402MiddlewareConfig, PaymentAuth, AgentPaymentAuth, Eip1193Provider } from './agentic/index.js';
// Compliance primitives — AereProof v0 + ZKScreen + AIProof.
export { AereSanctionsRegistryClient, ChainalysisOracleWrapperClient,
AereTravelRuleHashRegistryClient, AereForensicEventRegistryClient,
AereZKScreenClient, AereAIProofClient } from './compliance/index.js';
// AereStateChannels — bidirectional payment channels + HTLC (Lightning-EVM).
export { AereStateChannelsClient } from './channels/index.js';
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';

View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<string> {
return '0x' + (await ethCall(this.provider, this.address, SEL.FOUNDATION)).slice(-40);
}
async getCooldownSeconds(): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, SEL.COOLDOWN_SECONDS));
}
async balanceOf(token: string): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, SEL.balances + encAddr(token)));
}
async coverageRemaining(market: string): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, SEL.coverageRemaining + encAddr(market)));
}
async cooldownReady(market: string): Promise<boolean> {
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<string> {
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<string> {
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<string> {
return ethSend(this.provider, from, this.address, SEL.coverBadDebt + encAddr(market) + encAddr(borrower) + encUint(amount));
}
}

View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<string> { return '0x' + (await ethCall(this.provider, this.address, SEL.COLLATERAL)).slice(-40); }
async getDebtToken(): Promise<string> { return '0x' + (await ethCall(this.provider, this.address, SEL.DEBT_TOKEN)).slice(-40); }
async getOracle(): Promise<string> { return '0x' + (await ethCall(this.provider, this.address, SEL.ORACLE)).slice(-40); }
async getSink(): Promise<string> { return '0x' + (await ethCall(this.provider, this.address, SEL.SINK)).slice(-40); }
async getLtvBps(): Promise<number> { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LTV_BPS))); }
async getLiqThresholdBps(): Promise<number> { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LIQ_THRESHOLD_BPS))); }
async getLiqBonusBps(): Promise<number> { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LIQ_BONUS_BPS))); }
async getBorrowFeeBps(): Promise<number> { return Number(BigInt(await ethCall(this.provider, this.address, SEL.BORROW_FEE_BPS))); }
async getMarketDebtCap(): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.MARKET_DEBT_CAP)); }
/* ---- live state ------------------------------------------------------- */
async totalBorrowsCurrent(): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.totalBorrowsCurrent)); }
async totalSuppliedCurrent(): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.totalSuppliedCurrent)); }
async pendingSinkFee(): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.pendingSinkFee)); }
async borrowRatePerSecond(): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.borrowRatePerSecond)); }
async borrowPaused(): Promise<boolean> { return BigInt(await ethCall(this.provider, this.address, SEL.borrowPaused)) === 1n; }
async depositPaused(): Promise<boolean> { return BigInt(await ethCall(this.provider, this.address, SEL.depositPaused)) === 1n; }
/* ---- per-user views --------------------------------------------------- */
async collateralOf(user: string): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.collateralOf + encAddr(user))); }
async debtBalance(user: string): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.debtBalance + encAddr(user))); }
async supplierBalance(user: string): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.supplierBalance + encAddr(user))); }
async healthFactor(user: string): Promise<bigint> { return BigInt(await ethCall(this.provider, this.address, SEL.healthFactor + encAddr(user))); }
/* ---- write methods ---------------------------------------------------- */
async accrueInterest(from: string): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.accrueInterest);
}
async flushSinkFee(from: string): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.flushSinkFee);
}
async supply(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.supply + encUint(amount));
}
async redeem(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.redeem + encUint(amount));
}
async depositCollateral(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.depositCollateral + encUint(amount));
}
async withdrawCollateral(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.withdrawCollateral + encUint(amount));
}
async borrow(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.borrow + encUint(amount));
}
async repay(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.repay + encUint(amount));
}
async repayOnBehalfOf(from: string, account: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SEL.repayOnBehalfOf + encAddr(account) + encUint(amount));
}
async liquidate(from: string, borrower: string, repayAmount: bigint): Promise<string> {
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<number> {
const rate = await this.borrowRatePerSecond();
const r = Number(rate) / 1e27;
return r * 365 * 86400;
}
}

View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<string> {
return '0x' + (await ethCall(this.provider, this.address, SEL.PAYOUT_TOKEN)).slice(-40);
}
async getFoundation(): Promise<string> {
return '0x' + (await ethCall(this.provider, this.address, SEL.FOUNDATION)).slice(-40);
}
async periodRemaining(asset: string): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, SEL.periodRemaining + encAddr(asset)));
}
async payoutLiquidity(): Promise<bigint> {
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<string> {
return ethSend(this.provider, from, this.address, SEL.redeem + encAddr(asset) + encUint(rwaAmount));
}
}

4
src/lending/index.ts Normal file
View File

@ -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';

179
src/raas/RaaSClient.ts Normal file
View File

@ -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<unknown>;
}
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<string> {
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<string> {
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<bigint> {
return BigInt(await ethCall(this.provider, this.address, FAC_SEL.rollupCount));
}
async getSink(): Promise<string> {
return '0x' + (await ethCall(this.provider, this.address, FAC_SEL.SINK)).slice(-40);
}
async getRegistrationBond(): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, FAC_SEL.REGISTRATION_BOND));
}
async getMinSinkBps(): Promise<number> {
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<string> {
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<string> {
return '0x' + (await ethCall(this.provider, this.address, SET_SEL.sequencer)).slice(-40);
}
async rollupOwner(): Promise<string> {
return '0x' + (await ethCall(this.provider, this.address, SET_SEL.rollupOwner)).slice(-40);
}
async latestEpoch(): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, SET_SEL.latestEpoch));
}
async isFinalised(epoch: bigint): Promise<boolean> {
const res = await ethCall(this.provider, this.address, SET_SEL.isFinalised + encUint(epoch));
return BigInt(res) === 1n;
}
async totalRevenueSettled(): Promise<bigint> {
return BigInt(await ethCall(this.provider, this.address, SET_SEL.totalRevenueSettled));
}
/* sequencer-only */
async proposeStateRoot(from: string, epoch: bigint, root: string, l2BlockHash: string): Promise<string> {
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<string> {
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<string> {
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<string> {
return ethSend(this.provider, from, this.address, SET_SEL.resolveChallengeExpired + encUint(epoch));
}
async advanceFinalisation(from: string): Promise<string> {
return ethSend(this.provider, from, this.address, SET_SEL.advanceFinalisation);
}
/* rollupOwner-only */
async settleRevenue(from: string, amount: bigint): Promise<string> {
return ethSend(this.provider, from, this.address, SET_SEL.settleRevenue + encUint(amount));
}
async setSequencer(from: string, newSequencer: string): Promise<string> {
return ethSend(this.provider, from, this.address, SET_SEL.setSequencer + encAddr(newSequencer));
}
async transferOwner(from: string, newOwner: string): Promise<string> {
return ethSend(this.provider, from, this.address, SET_SEL.transferOwner + encAddr(newOwner));
}
}

1
src/raas/index.ts Normal file
View File

@ -0,0 +1 @@
export { AereRaaSFactoryClient, AereRollupSettlementClient } from './RaaSClient.js';

1
src/saere/index.ts Normal file
View File

@ -0,0 +1 @@
export { SAereClient, type SAereClientConfig } from './sAEREClient.js';

212
src/saere/sAEREClient.ts Normal file
View File

@ -0,0 +1,212 @@
/**
* sAEREClient typed helpers for the sAERE ERC-4626 receipt vault.
*
* Contract: aerenew/contracts/contracts/staking/sAERE.sol
*
* Phase 1 (read): live conversion rate (pricePerShare), TVL (totalAssets),
* outstanding shares (totalSupply), per-user holdings.
*
* Phase 2 (write): deposit / mint / withdraw / redeem helper calldata
* generators. Assumes integrator already obtained a signer
* and approved WAERE for the sAERE vault.
*
* Same dependency-light EIP-1193 surface as AereSinkClient.
*/
import type { RpcProvider } from '../sink/AereSinkClient.js';
export interface SAereClientConfig {
/** sAERE contract address. */
address: `0x${string}`;
/** EIP-1193-style provider. */
provider: RpcProvider;
}
/* --------------------------- selector constants --------------------------- */
const SEL = {
asset: '0x38d52e0f', // asset()
totalAssets: '0x01e1d114', // totalAssets()
totalSupply: '0x18160ddd', // totalSupply()
pricePerShare: '0x99530b06', // pricePerShare()
decimals: '0x313ce567', // decimals()
// R5/R6 drip + atomic state additions
atomicState: '0x152f7185', // atomicState()
sync: '0xfff6cae9', // sync()
undistributedRewards: '0x319ce2bb', // undistributedRewards()
rewardRate: '0x7b0a47ee', // rewardRate()
periodFinish: '0xebe2b12b', // periodFinish()
lastObservedBalance: '0xc5ab5bf0', // lastObservedBalance()
DRIP_DURATION: '0x2a129169', // DRIP_DURATION()
} as const;
const BALANCE_OF_SELECTOR = '0x70a08231'; // balanceOf(address)
const CONVERT_TO_ASSETS_SEL = '0x07a2d13a'; // convertToAssets(uint256)
const CONVERT_TO_SHARES_SEL = '0xc6e6f592'; // convertToShares(uint256)
const DEPOSIT_SELECTOR = '0x6e553f65'; // deposit(uint256,address)
const REDEEM_SELECTOR = '0xba087652'; // redeem(uint256,address,address)
const WITHDRAW_SELECTOR = '0xb460af94'; // withdraw(uint256,address,address)
/* ----------------------------- helper functions ---------------------------- */
function hexToBigInt(hex: string): bigint {
if (!/^0x[0-9a-fA-F]+$/.test(hex)) {
throw new Error(`sAEREClient: malformed integer response: ${hex}`);
}
return BigInt(hex);
}
function hexToAddress(hex: string): `0x${string}` {
if (!/^0x[0-9a-fA-F]{64}$/.test(hex)) {
throw new Error(`sAEREClient: malformed address response: ${hex}`);
}
return ('0x' + hex.slice(-40)) as `0x${string}`;
}
function padAddress(address: `0x${string}`): string {
return address.slice(2).toLowerCase().padStart(64, '0');
}
function padUint(value: bigint): string {
return value.toString(16).padStart(64, '0');
}
/* -------------------------------- client -------------------------------- */
export class SAereClient {
readonly address: `0x${string}`;
private readonly provider: RpcProvider;
constructor(cfg: SAereClientConfig) {
this.address = cfg.address;
this.provider = cfg.provider;
}
/* ---------------------------- read methods ---------------------------- */
/** Snapshot of vault state — TVL, supply, conversion rate. */
async readVaultState(): Promise<{
underlying: `0x${string}`;
totalAssetsWei: bigint;
totalSupplyWei: bigint;
pricePerShareWei: bigint;
decimals: number;
}> {
const [underlying, totalAssets, totalSupply, pricePerShare, decimals] = await Promise.all([
this._call(SEL.asset),
this._call(SEL.totalAssets),
this._call(SEL.totalSupply),
this._call(SEL.pricePerShare),
this._call(SEL.decimals),
]);
return {
underlying: hexToAddress(underlying),
totalAssetsWei: hexToBigInt(totalAssets),
totalSupplyWei: hexToBigInt(totalSupply),
pricePerShareWei: hexToBigInt(pricePerShare),
decimals: Number(hexToBigInt(decimals)),
};
}
/** Get a user's sAERE share balance, in wei (1e18 = 1 share). */
async balanceOf(user: `0x${string}`): Promise<bigint> {
const data = BALANCE_OF_SELECTOR + padAddress(user);
const result = await this._call(data);
return hexToBigInt(result);
}
/** Convert sAERE shares → underlying WAERE assets at current rate. */
async convertToAssets(shares: bigint): Promise<bigint> {
const data = CONVERT_TO_ASSETS_SEL + padUint(shares);
return hexToBigInt(await this._call(data));
}
/** Convert WAERE assets → sAERE shares at current rate. */
async convertToShares(assets: bigint): Promise<bigint> {
const data = CONVERT_TO_SHARES_SEL + padUint(assets);
return hexToBigInt(await this._call(data));
}
/**
* R6 atomicState bal + ta + undist + supply + rate + periodFinish +
* lastObserved in a single eth_call. Use this for fuzz-resistant
* snapshotting or to avoid cross-block drift in dashboards.
*/
async readAtomicState(): Promise<{
balanceWei: bigint;
totalAssetsWei: bigint;
undistributedWei: bigint;
totalSupplyWei: bigint;
rewardRatePerSec: bigint;
periodFinish: bigint;
lastObservedBalanceWei: bigint;
}> {
const raw = await this._call(SEL.atomicState);
// 7 uint256 packed. Each 32 bytes = 64 hex chars after 0x.
const body = raw.slice(2);
if (body.length < 64 * 7) throw new Error('sAEREClient: short atomicState');
const slot = (i: number) => BigInt('0x' + body.slice(i * 64, (i + 1) * 64));
return {
balanceWei: slot(0),
totalAssetsWei: slot(1),
undistributedWei: slot(2),
totalSupplyWei: slot(3),
rewardRatePerSec: slot(4),
periodFinish: slot(5),
lastObservedBalanceWei: slot(6),
};
}
/** R5 undistributed (unvested) reward reserve, wei. */
async readUndistributed(): Promise<bigint> {
return hexToBigInt(await this._call(SEL.undistributedRewards));
}
/** R5 current per-second drip rate, wei. */
async readRewardRate(): Promise<bigint> {
return hexToBigInt(await this._call(SEL.rewardRate));
}
/** Unix timestamp when the current drip schedule ends. */
async readPeriodFinish(): Promise<bigint> {
return hexToBigInt(await this._call(SEL.periodFinish));
}
/* --------------------------- write: calldata --------------------------- */
/**
* R5 sync() permissionless. Recognises any pending AERE arrivals to the
* vault as a fresh drip schedule. Idempotent within a block. Callable by
* AereSink (auto-pinged on flush) but anyone can poke if needed.
*/
encodeSync(): `0x${string}` { return SEL.sync as `0x${string}`; }
/** Encode `deposit(assets, receiver)`. */
encodeDeposit(assets: bigint, receiver: `0x${string}`): `0x${string}` {
return (DEPOSIT_SELECTOR + padUint(assets) + padAddress(receiver)) as `0x${string}`;
}
/** Encode `withdraw(assets, receiver, owner)`. */
encodeWithdraw(assets: bigint, receiver: `0x${string}`, owner: `0x${string}`): `0x${string}` {
return (WITHDRAW_SELECTOR + padUint(assets) + padAddress(receiver) + padAddress(owner)) as `0x${string}`;
}
/** Encode `redeem(shares, receiver, owner)`. */
encodeRedeem(shares: bigint, receiver: `0x${string}`, owner: `0x${string}`): `0x${string}` {
return (REDEEM_SELECTOR + padUint(shares) + padAddress(receiver) + padAddress(owner)) as `0x${string}`;
}
/* ----------------------------- internals ------------------------------ */
private async _call(data: string): Promise<string> {
const result = await this.provider.request({
method: 'eth_call',
params: [{ to: this.address, data }, 'latest'],
});
if (typeof result !== 'string') {
throw new Error(`sAEREClient: unexpected eth_call result`);
}
return result;
}
}

168
src/sink/AereSinkClient.ts Normal file
View File

@ -0,0 +1,168 @@
/**
* AereSinkClient typed read/write helpers for the AereSink immutable
* 3-bucket protocol revenue router.
*
* Contract: aerenew/contracts/contracts/sink/AereSink.sol
*
* Phase 1 (read-only): poll the immutable bucket configuration + lifetime
* stats. Integrators dashboard their share of the
* flywheel without needing a signer.
*
* Phase 2 (write): integrators that already accumulate fees in some asset
* call flush() on this contract. They must `approve`
* the sink first; flushOne() helps with that.
*
* Dependencies: any EIP-1193 JSON-RPC provider (viem, ethers, ethereum
* window, raw fetch). The SDK accepts an `eth_call` shim instead of a
* concrete provider library, keeping the dep surface small.
*/
export interface RpcProvider {
request(args: { method: string; params: unknown[] }): Promise<unknown>;
}
export interface AereSinkConfig {
/** AereSink contract address. */
address: `0x${string}`;
/** EIP-1193-style provider (viem, ethers, or window.ethereum). */
provider: RpcProvider;
}
/* --------------------------- selector constants --------------------------- */
// 4-byte function selectors. Computed once at module load and held constant.
// (Verified via cast sig-of <name>().)
const SEL = {
AERE: '0x1cc59c1b', // AERE()
BURN_VAULT: '0x4e7eee76', // BURN_VAULT()
SAERE_VAULT: '0xc46aedd7', // SAERE_VAULT()
DEX_ROUTER: '0x3b1664c1', // DEX_ROUTER()
BURN_BPS: '0x9748d6c0', // BURN_BPS()
BUYBACK_BPS: '0xb56fcecf', // BUYBACK_BPS()
STAKER_YIELD_BPS: '0xc77b3402', // STAKER_YIELD_BPS()
MAX_SLIPPAGE_BPS: '0xfb6fb1f9', // MAX_SLIPPAGE_BPS()
} as const;
// flush(address,uint256) — for write
const FLUSH_SELECTOR = '0x6cd5c39b';
// sweepDust(address) — for write
const SWEEP_DUST_SELECTOR = '0xeb1a0a96';
/* ----------------------------- helper functions ---------------------------- */
function hexToAddress(hex: string): `0x${string}` {
// 32-byte ABI-encoded address → 20-byte address
// hex is 0x + 64 hex chars; the address is the last 40 chars.
if (!/^0x[0-9a-fA-F]{64}$/.test(hex)) {
throw new Error(`AereSinkClient: malformed address response: ${hex}`);
}
return ('0x' + hex.slice(-40)) as `0x${string}`;
}
function hexToBigInt(hex: string): bigint {
if (!/^0x[0-9a-fA-F]+$/.test(hex)) {
throw new Error(`AereSinkClient: malformed integer response: ${hex}`);
}
return BigInt(hex);
}
function padAddress(address: `0x${string}`): string {
// 20-byte address → 32-byte ABI-encoded slot.
return '0x' + address.slice(2).toLowerCase().padStart(64, '0');
}
function padUint(value: bigint): string {
return value.toString(16).padStart(64, '0');
}
/* ----------------------------- the client ---------------------------- */
export class AereSinkClient {
readonly address: `0x${string}`;
private readonly provider: RpcProvider;
constructor(cfg: AereSinkConfig) {
this.address = cfg.address;
this.provider = cfg.provider;
}
/* ---------------------- read: immutable parameters --------------------- */
/** Read all 8 immutable parameters in a single batched eth_call sequence. */
async readConfig(): Promise<{
aere: `0x${string}`;
burnVault: `0x${string}`;
sAereVault: `0x${string}`;
dexRouter: `0x${string}`;
burnBps: number;
buybackBps: number;
stakerYieldBps: number;
maxSlippageBps: number;
}> {
const [aere, burnVault, sAereVault, dexRouter, burnBps, buybackBps, stakerYieldBps, maxSlippageBps] =
await Promise.all([
this._call(SEL.AERE),
this._call(SEL.BURN_VAULT),
this._call(SEL.SAERE_VAULT),
this._call(SEL.DEX_ROUTER),
this._call(SEL.BURN_BPS),
this._call(SEL.BUYBACK_BPS),
this._call(SEL.STAKER_YIELD_BPS),
this._call(SEL.MAX_SLIPPAGE_BPS),
]);
return {
aere: hexToAddress(aere),
burnVault: hexToAddress(burnVault),
sAereVault: hexToAddress(sAereVault),
dexRouter: hexToAddress(dexRouter),
burnBps: Number(hexToBigInt(burnBps)),
buybackBps: Number(hexToBigInt(buybackBps)),
stakerYieldBps: Number(hexToBigInt(stakerYieldBps)),
maxSlippageBps: Number(hexToBigInt(maxSlippageBps)),
};
}
/** Convenience: returns the human-readable bucket percentages (e.g., 15.00). */
async readSplitPercentages(): Promise<{ burn: number; buyback: number; stakerYield: number }> {
const c = await this.readConfig();
return {
burn: c.burnBps / 100,
buyback: c.buybackBps / 100,
stakerYield: c.stakerYieldBps / 100,
};
}
/* ----------------------- write: encoded calldata ----------------------- */
/**
* Encode the calldata for `flush(token, amount)`. The integrator submits this
* to the sink contract after first calling `approve(sinkAddress, amount)`
* on the token.
*/
encodeFlush(token: `0x${string}`, amount: bigint): `0x${string}` {
const data =
FLUSH_SELECTOR +
padAddress(token).slice(2) +
padUint(amount);
return data as `0x${string}`;
}
/** Encode `sweepDust(token)`. Permissionless — anyone can sweep. */
encodeSweepDust(token: `0x${string}`): `0x${string}` {
return (SWEEP_DUST_SELECTOR + padAddress(token).slice(2)) as `0x${string}`;
}
/* ----------------------------- internals ------------------------------- */
private async _call(data: string): Promise<string> {
const result = await this.provider.request({
method: 'eth_call',
params: [{ to: this.address, data }, 'latest'],
});
if (typeof result !== 'string') {
throw new Error(`AereSinkClient: unexpected eth_call result`);
}
return result;
}
}

1
src/sink/index.ts Normal file
View File

@ -0,0 +1 @@
export { AereSinkClient, type AereSinkConfig, type RpcProvider } from './AereSinkClient.js';