@iris-credit/core-sdk whose derived values mirror the Iris contracts exactly. The math is framework-agnostic and runs offline: positions can be projected to arbitrary timestamps without an RPC; viem is only needed to hydrate entities from the chain.
Reading through the entity
Theclient.iris.core(chainId) entity exposes one getter per input a flow takes. Flows are fetch-first, so one fetch can feed several builds and a stale build never hides a fetch:
FetchParameters (blockNumber, blockTag, stateOverride) for historical or state-overridden reads.
getVenueData reads the venue as it stands for pod, so a venue the pod has not entered comes back holding nothing: the shape refinance expects of its target venue. pod defaults to the zero address, the pod-less view take expects, and a solver-signed Quote carries every other field, so a quote satisfies the params directly.
Outside the entity, every fetcher is also exported standalone from @iris-credit/core-sdk (fetchLoan, fetchAccrualPosition, fetchVenue, fetchBlm, …), or, by importing @iris-credit/core-sdk/augment once, attached to each entity class as a fetch static:
Offline math
AccrualPosition carries the whole position model. Health, repay pricing and withdraw ceilings are plain property reads, and accrueLegs(timestamp) projects the position (venue indices included, using the venue’s own rate model) to any timestamp:
repayAmount is flat until maturity, so a closing cost needs no projection, while a future floatingLeg is the venue rate model’s estimate of the venue cost by that time, the number a solver nets against the fixed leg. Flat is not exact to the unit: the fixed total is settled as two separately rounded-down parts, the interest accrued to the settlement timestamp and the residual from there to maturity, so repayAmount can shift by one unit with that timestamp. Fund a repay with headroom rather than the figure read from the position, as repay and close do.
Onchain operations are mirrored as pure transitions returning a new position (repay, liquidate, liquidateBond, supplyCollateral, withdrawCollateral, supplyBond, withdrawBond and refinance), each accruing and rebasing exactly as Iris does:
Venue rates and bounds
Venue also answers what the venue underneath would accept right now. Each answer is computed with that venue’s own validation math rather than from the normalised view, so an amount at the bound clears the venue’s checks and one wei more does not:
getMaxBorrowAmount takes its options as an object, { maxLtv, timestamp }, where maxLtv (WAD-scaled) defaults to, and is capped by, the venue’s own max borrow LTV, so a caller can tighten the bound but never exceed the venue’s. It folds getMaxBorrowCapacity in, so a paused, frozen or dry venue answers 0n rather than an amount the venue would reject. On Morpho Blue, getMaxBorrowCapacity takes an optional target utilisation as a second argument, and getMaxSupplyCapacity is unbounded.
A bound reads undefined rather than a number when its inputs are missing: on Morpho Blue when the venue price is unknown, on Aave V3 when the venue was constructed by hand instead of fetched, since the Aave bounds read reserve state that the fetchers hydrate.
borrowApy is the rate the venue quotes at the snapshot’s timestamp. On Morpho Blue, getBorrowApy(timestamp) projects the Adaptive Curve IRM’s adaptation forward to a later one, and an idle market (zero IRM) reads 0n, matching its zero-rate accrual. A market on any other interest-rate model throws UnsupportedVenueIrmError from both, and from any accrual to a later timestamp while the market has debt, rather than projecting it at a zero rate. Aave V3’s rate only moves when the reserve is touched, so there is nothing to project.
Units & precision
The SDK follows the same conventions as the Iris API:- Rates, LLTVs and fees are WAD-scaled (
1e18). AfixedRateof81000000000000000nis 8.1% simple annual interest. - Amounts are in the token’s own decimals. Read
decimalsoff a fetchedToken. - Everything is
bigint. No floats anywhere; format for display with@iris-credit/iris-ts’sformathelpers.
The contracts store rates and LLTVs BP-compressed (
1e14) to fit small integer slots. The fetchers rescale them to WAD on the way in; only a raw Iris.getLoan call returns the compressed values.Protocol constants
The bounds and parameters Iris enforces ship as exported constants, so quoting and validation never hardcode them:
These are the immutable contract’s own parameters, mirrored from its
ConstantsLib, so they change only with a protocol redeployment.
Bond requirements
Blm computes the bond a quote requires without a bondRequirement contract call:
bondRequirement throws IrisCoreErrors.ZeroBondRequirement instead of returning 0n. Two cases reach it: the BLM has no parameters set for the debt token, or the debt is small enough that the requirement rounds down to zero. Both are quoting-time conditions, so catch the error and drop the quote rather than returning terms no borrower can open.
This is the BLM’s requirement for a prospective quote, not
position.bondRequirement, which is the requirement recorded on an open loan and legitimately reads 0n once that loan is resolved.Addresses & registries
Two static, per-chain sources ship with the SDK, both narrowed to the exact chain by their getter:getChainAddresses(chainId)holds what is deployed: the Iris core, BLMs, bundler adapters, venue adapters and common tokens.getChainRegistry(chainId)holds what is enabled on the Iris contract: venue ids, accepted bond LLTVs, and the market data payloads. The contract only storeskeccak256(data), so each payload is recorded as a preimage keyed by that enabled hash, which for a Morpho Blue payload is the Morpho market id.
getMarketData matches the hash case-insensitively and throws UnknownDataHashError when the chain’s registry does not record it (a payload enabled after the SDK release, for instance). Indexing marketDatas directly still works, but its keys are exact literals, so a hash only known at runtime has to go through the getter.
Enablement is append-only onchain, so registry entries can only ever be stale-incomplete, never stale-wrong. Solvers can quote from the registry offline, while the fetchers re-verify mutable state (BLM params, whitelist entries, fee) at runtime. The pairs, venues and Morpho markets behind these entries are listed in Supported markets.
Both sources are compile-time constants: supporting a new chain means a new SDK release. Fetchers that resolve a contract from the registry throw
UnsupportedChainIdError on an unknown chain.EIP-712 payloads
The signature builders return typed data ready for viem’ssignTypedData, the same payloads Iris verifies onchain:
getQuoteTypedData(chainId, quote): a solver’sQuote, consumed bytakegetAuthorizationTypedData(chainId, authorization): granting or revoking a manager on IrisgetPermitTypedData(args, chainId): an ERC-2612 permit, taking its EIP-712 domainversionfrom the per-chainSIMPLE_PERMIT_TOKENSallowlist and falling back to the"1"most ERC-2612 tokens signgetPermit2PermitTypedData(args, chainId): a Permit2 allowance (PermitSingle), clampingallowancetouint160, the width ofPermitDetails.amountgetPermit2TransferFromTypedData(args, chainId): a Permit2 signature transfer (PermitTransferFrom), clampingallowancetouint256, the width ofTokenPermissions.amount
signResponse wraps the solver side.
