/** * LendingMarketClient — typed read/write helpers for AereLendingMarket * (Wave 1 isolated single-pair money markets). * * Contract: aerenew/contracts/contracts/lending/AereLendingMarket.sol * Audited 2026-06-07 (5 HIGH + 1 MED bugs fixed pre-deploy). * * Pattern: dependency-free EIP-1193 wrapper with precomputed 4-byte * selectors. Read methods just `eth_call`; write methods build calldata * + `eth_sendTransaction`. Caller handles signer + gas estimation. */ export interface RpcProvider { request(args: { method: string; params: unknown[] }): Promise; } export interface LendingMarketConfig { address: `0x${string}`; provider: RpcProvider; } const SEL = { // immutable views COLLATERAL: '0xfb4cd02f', // COLLATERAL() DEBT_TOKEN: '0xea5b5ddf', // DEBT_TOKEN() SINK: '0xf4f3b200', // SINK() ORACLE: '0x7dc0d1d0', // ORACLE() LTV_BPS: '0xcbc4cdc7', // LTV_BPS() LIQ_THRESHOLD_BPS: '0xc0c5294d', // LIQ_THRESHOLD_BPS() LIQ_BONUS_BPS: '0x3a86d9b3', // LIQ_BONUS_BPS() BORROW_FEE_BPS: '0x47bf8b54', // BORROW_FEE_BPS() MARKET_DEBT_CAP: '0xcca5e09e', // MARKET_DEBT_CAP() COLLATERAL_DECIMALS: '0x99e9f4ad', // COLLATERAL_DECIMALS() DEBT_DECIMALS: '0x4d9e7826', // DEBT_DECIMALS() // state views collateralOf: '0x8aef7ddf', // collateralOf(address) debtOf: '0xcc4d2f49', // debtOf(address) supplierScaled: '0x9d7d6f0d', // supplierScaled(address) supplierBalance: '0x9d9f3ff6', // supplierBalance(address) debtBalance: '0x33ed81e4', // debtBalance(address) borrowIndex: '0xaa5af0fd', // borrowIndex() supplyIndex: '0xb02f7d5e', // supplyIndex() totalBorrowsScaled: '0xc0c44d6b', // totalBorrowsScaled() totalSuppliedScaled: '0x7b0b3da7', // totalSuppliedScaled() totalBorrowsCurrent: '0xc26c9116', // totalBorrowsCurrent() totalSuppliedCurrent: '0x57e3c7fc', // totalSuppliedCurrent() pendingSinkFee: '0x99c50fcb', // pendingSinkFee() borrowRatePerSecond: '0x9e6dadce', // borrowRatePerSecond() borrowPaused: '0x42b6e3bf', // borrowPaused() depositPaused: '0x6a5db0fa', // depositPaused() healthFactor: '0xe8b9c2dd', // healthFactor(address) // write accrueInterest: '0x59601e8a', // accrueInterest() flushSinkFee: '0x71a5ba73', // flushSinkFee() supply: '0xf2b9fdb8', // supply(uint256) redeem: '0xdb006a75', // redeem(uint256) depositCollateral: '0x80aebec5', // depositCollateral(uint256) withdrawCollateral: '0xeb0aaa4b', // withdrawCollateral(uint256) borrow: '0xc5ebeaec', // borrow(uint256) repay: '0x0e752702', // repay(uint256) repayOnBehalfOf: '0x8f1a3bf0', // repayOnBehalfOf(address,uint256) liquidate: '0xf5e3c462', // liquidate(address,uint256) } as const; /* ----------------------------- helpers ---------------------------------- */ function pad32(hex: string): string { const h = hex.toLowerCase().replace(/^0x/, ''); return h.padStart(64, '0'); } function encUint(n: bigint): string { return pad32(n.toString(16)); } function encAddr(addr: string): string { return pad32(addr.replace(/^0x/, '')); } async function ethCall(p: RpcProvider, to: string, data: string): Promise { return await p.request({ method: 'eth_call', params: [{ to, data }, 'latest'] }) as string; } async function ethSend(p: RpcProvider, from: string, to: string, data: string, value = '0x0'): Promise { return await p.request({ method: 'eth_sendTransaction', params: [{ from, to, data, value }] }) as string; } /* --------------------------- client ------------------------------------- */ export class LendingMarketClient { readonly address: `0x${string}`; readonly provider: RpcProvider; constructor(cfg: LendingMarketConfig) { this.address = cfg.address; this.provider = cfg.provider; } /* ---- immutable risk params (cached one-shot reads) ------------------- */ async getCollateral(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.COLLATERAL)).slice(-40); } async getDebtToken(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.DEBT_TOKEN)).slice(-40); } async getOracle(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.ORACLE)).slice(-40); } async getSink(): Promise { return '0x' + (await ethCall(this.provider, this.address, SEL.SINK)).slice(-40); } async getLtvBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LTV_BPS))); } async getLiqThresholdBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LIQ_THRESHOLD_BPS))); } async getLiqBonusBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.LIQ_BONUS_BPS))); } async getBorrowFeeBps(): Promise { return Number(BigInt(await ethCall(this.provider, this.address, SEL.BORROW_FEE_BPS))); } async getMarketDebtCap(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.MARKET_DEBT_CAP)); } /* ---- live state ------------------------------------------------------- */ async totalBorrowsCurrent(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.totalBorrowsCurrent)); } async totalSuppliedCurrent(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.totalSuppliedCurrent)); } async pendingSinkFee(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.pendingSinkFee)); } async borrowRatePerSecond(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.borrowRatePerSecond)); } async borrowPaused(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.borrowPaused)) === 1n; } async depositPaused(): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.depositPaused)) === 1n; } /* ---- per-user views --------------------------------------------------- */ async collateralOf(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.collateralOf + encAddr(user))); } async debtBalance(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.debtBalance + encAddr(user))); } async supplierBalance(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.supplierBalance + encAddr(user))); } async healthFactor(user: string): Promise { return BigInt(await ethCall(this.provider, this.address, SEL.healthFactor + encAddr(user))); } /* ---- write methods ---------------------------------------------------- */ async accrueInterest(from: string): Promise { return ethSend(this.provider, from, this.address, SEL.accrueInterest); } async flushSinkFee(from: string): Promise { return ethSend(this.provider, from, this.address, SEL.flushSinkFee); } async supply(from: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.supply + encUint(amount)); } async redeem(from: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.redeem + encUint(amount)); } async depositCollateral(from: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.depositCollateral + encUint(amount)); } async withdrawCollateral(from: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.withdrawCollateral + encUint(amount)); } async borrow(from: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.borrow + encUint(amount)); } async repay(from: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.repay + encUint(amount)); } async repayOnBehalfOf(from: string, account: string, amount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.repayOnBehalfOf + encAddr(account) + encUint(amount)); } async liquidate(from: string, borrower: string, repayAmount: bigint): Promise { return ethSend(this.provider, from, this.address, SEL.liquidate + encAddr(borrower) + encUint(repayAmount)); } /* ---- helpers ---------------------------------------------------------- */ /** * Returns the APY as a decimal (0.05 = 5%) computed from `borrowRatePerSecond`. * Linear approximation; accurate at low rates. */ async borrowApy(): Promise { const rate = await this.borrowRatePerSecond(); const r = Number(rate) / 1e27; return r * 365 * 86400; } }