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>
This commit is contained in:
parent
99abfff98a
commit
fbc6d4f68a
@ -122,9 +122,26 @@ const POOL_SEL = {
|
|||||||
associationRootPublishedAt: '0x68c46b72',
|
associationRootPublishedAt: '0x68c46b72',
|
||||||
deposit: '0xca0c3ad5',
|
deposit: '0xca0c3ad5',
|
||||||
setComplianceProvider: '0xecc19f6c',
|
setComplianceProvider: '0xecc19f6c',
|
||||||
publishDepositRoot: '0x0ce9659f',
|
|
||||||
publishAssociationRoot: '0x9f92a5b1',
|
publishAssociationRoot: '0x9f92a5b1',
|
||||||
withdraw: '0xb7433691',
|
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;
|
} as const;
|
||||||
|
|
||||||
export class AereCompliancePoolClient {
|
export class AereCompliancePoolClient {
|
||||||
@ -156,13 +173,68 @@ export class AereCompliancePoolClient {
|
|||||||
publishAssociationRoot(from: string, root: string): Promise<string> {
|
publishAssociationRoot(from: string, root: string): Promise<string> {
|
||||||
return send(this.provider, from, this.address, POOL_SEL.publishAssociationRoot + encB(root));
|
return send(this.provider, from, this.address, POOL_SEL.publishAssociationRoot + encB(root));
|
||||||
}
|
}
|
||||||
publishDepositRoot(from: string, root: string): Promise<string> {
|
|
||||||
return send(this.provider, from, this.address, POOL_SEL.publishDepositRoot + encB(root));
|
|
||||||
}
|
|
||||||
setComplianceProvider(from: string, provider: string, allowed: boolean): Promise<string> {
|
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'));
|
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,
|
/** Withdraw via zk proof. Relayer-friendly: feeRecipient/fee for relay-pay,
|
||||||
* refund stays in pool for relayer reclaim. */
|
* refund stays in pool for relayer reclaim. */
|
||||||
withdraw(
|
withdraw(
|
||||||
|
|||||||
219
src/corebook/AereCoreBookClient.ts
Normal file
219
src/corebook/AereCoreBookClient.ts
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
bestBid: '0xeae2ea7e', // bestBid()
|
||||||
|
bestAsk: '0x86b07c1d', // bestAsk()
|
||||||
|
pendingSinkFee: '0x9c2f4d12', // pendingSinkFee()
|
||||||
|
totalClaimableQuote: '0x17554eb7',
|
||||||
|
totalClaimableBase: '0x200d51bc',
|
||||||
|
claimableQuote: '0x2d9cf134',
|
||||||
|
claimableBase: '0x22046da1',
|
||||||
|
TAKER_FEE_BPS: '0x35b25c5d',
|
||||||
|
PRICE_TICK: '0x6e716bdb',
|
||||||
|
LOT_SIZE: '0x49bf3edb',
|
||||||
|
QUOTE_DECIMALS_FACTOR: '0x8b4ad97e',
|
||||||
|
BASE_DECIMALS_FACTOR: '0xa2d5d92b',
|
||||||
|
PRICE_SCALE: '0xb6d8b8e9',
|
||||||
|
MAX_CANCEL_DELAY_BLOCKS: '0x77ec5e2a',
|
||||||
|
DEFAULT_CANCEL_DELAY_BLOCKS: '0x29c8bc8e',
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/corebook/index.ts
Normal file
1
src/corebook/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './AereCoreBookClient.js';
|
||||||
@ -10,6 +10,9 @@ export {
|
|||||||
export * from './sink/index.js';
|
export * from './sink/index.js';
|
||||||
export * from './saere/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.
|
// Wave-1 lending — isolated single-pair money markets + BadDebt insurance.
|
||||||
export { LendingMarketClient, AereInsuranceFundClient, RWATransferAdapterClient } from './lending/index.js';
|
export { LendingMarketClient, AereInsuranceFundClient, RWATransferAdapterClient } from './lending/index.js';
|
||||||
export type { LendingMarketConfig } from './lending/index.js';
|
export type { LendingMarketConfig } from './lending/index.js';
|
||||||
|
|||||||
@ -30,6 +30,14 @@ const SEL = {
|
|||||||
totalSupply: '0x18160ddd', // totalSupply()
|
totalSupply: '0x18160ddd', // totalSupply()
|
||||||
pricePerShare: '0x99530b06', // pricePerShare()
|
pricePerShare: '0x99530b06', // pricePerShare()
|
||||||
decimals: '0x313ce567', // decimals()
|
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;
|
} as const;
|
||||||
|
|
||||||
const BALANCE_OF_SELECTOR = '0x70a08231'; // balanceOf(address)
|
const BALANCE_OF_SELECTOR = '0x70a08231'; // balanceOf(address)
|
||||||
@ -119,8 +127,61 @@ export class SAereClient {
|
|||||||
return hexToBigInt(await this._call(data));
|
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 --------------------------- */
|
/* --------------------------- 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)`. */
|
/** Encode `deposit(assets, receiver)`. */
|
||||||
encodeDeposit(assets: bigint, receiver: `0x${string}`): `0x${string}` {
|
encodeDeposit(assets: bigint, receiver: `0x${string}`): `0x${string}` {
|
||||||
return (DEPOSIT_SELECTOR + padUint(assets) + padAddress(receiver)) as `0x${string}`;
|
return (DEPOSIT_SELECTOR + padUint(assets) + padAddress(receiver)) as `0x${string}`;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user