@1delta/margin-fetcher
v0.0.218
Published
Multi-protocol lending data fetcher supporting Morpho Blue, Aave V2/V3, Compound V2/V3, Euler, Init, and Lista DAO. Provides public market data (rates, TVL, configs) and per-user position data (balances, shares, collateral) in a unified format.
Readme
@1delta/margin-fetcher
Multi-protocol lending data fetcher supporting Morpho Blue, Aave V2/V3, Compound V2/V3, Euler, Init, and Lista DAO. Provides public market data (rates, TVL, configs) and per-user position data (balances, shares, collateral) in a unified format.
How Morpho Blue Works
Protocol overview
Morpho Blue is an isolated-market lending protocol. Unlike pooled protocols (Aave, Compound) where all assets share a single pool, each Morpho market is a standalone pair defined by five parameters:
| Parameter | Description |
|-----------|-------------|
| loanToken | The asset that can be supplied and borrowed |
| collateralToken | The asset deposited as collateral |
| oracle | Price oracle for the pair |
| irm | Interest Rate Model contract |
| lltv | Liquidation Loan-To-Value ratio (18-decimal WAD) |
These five parameters are hashed into a uniqueKey (bytes32) that identifies the market on-chain.
Interest rate model
Morpho uses an adaptive curve IRM that adjusts rates based on utilization relative to a 90% target:
utilization = totalBorrowAssets / totalSupplyAssets
If utilization > 90%: rateAtTarget increases over time (via exponential adjustment)
If utilization < 90%: rateAtTarget decreases over time
borrowRate = curve(rateAtTarget, utilization)
supplyRate = borrowRate * utilization * (1 - fee)The curve uses a steepness factor of 4x, meaning at full utilization the rate is 4x the rate-at-target. Rates are stored as per-second WAD values and compounded continuously.
Key constants:
- Target utilization: 90%
- Curve steepness: 4x
- Min rate at target: ~0.1% APY
- Max rate at target: ~200% APY
- Adjustment speed: 50x/year (exponential)
Share-based accounting
Positions are tracked via shares rather than raw assets. This allows interest to accrue without storage updates per-user:
assets = shares * (totalAssets + 1) / (totalShares + VIRTUAL_SHARES)Where VIRTUAL_SHARES = 1e6 prevents share inflation attacks.
Market data representation
Each Morpho market produces two entries in the normalized output:
Loan entry — the borrowable asset
- Has
borrowingEnabled: true,collateralActive: false - Contains deposit/borrow APRs, total supply/debt, liquidity
- Rewards (MORPHO token incentives) attached here
- Has
Collateral entry — the collateral asset
- Has
borrowingEnabled: false,collateralActive: true - LTV factors derived from the market's
lltv - No interest rates (collateral doesn't earn yield in Morpho)
- Has
Whitelisted vs unlisted markets
Morpho markets can be whitelisted (curated, visible in the Morpho UI) or unlisted (removed from curation). The includeUnlistedMorphoMarkets flag controls whether unlisted markets are fetched from the API. Unlisted markets are tagged with isListed: false on the MorphoMarket params.
Architecture
Data fetching strategy
The fetcher uses a hybrid approach that routes each lender to either an API or on-chain path:
getLenderPublicDataAll(chainId, lenders, ...)
├── lenderCanUseApi(lender, chainId)?
│ ├── YES → getLenderPublicDataViaApi() → Morpho GraphQL API
│ └── NO → getLenderPublicData() → On-chain multicall via Morpho Lens
└── Promise.all([onChain, api]) → merged resultMorpho Blue uses the API on most chains. On-chain fallback is used for chains without API support (OP Mainnet, Soneium, Hemi, Berachain, Sei).
API path (GraphQL)
Fetches from https://blue-api.morpho.org/graphql:
fetchMorphoMarkets(chainId, includeUnlisted)
→ GraphQL query (paginated: 200/page, 2 pages for Ethereum mainnet)
→ GetMarketsResponse
→ convertMarketsToMorphoResponse()
→ { [marketId]: MorphoGeneralPublicResponse }The API provides pre-computed APYs, USD values, and reward data. APY→APR conversion is applied during normalization.
On-chain path (Morpho Lens)
Uses a custom lens contract that returns compact binary-encoded market data:
buildMorphoCall(chainId)
→ multicall to MORPHO_LENS.getMarketDataCompact(morpho, marketHashes[])
→ chunks of 50 markets per call (calldata size limit)
→ raw bytes response
getMorphoMarketDataConverter()
→ decodeMarkets(bytes) // 256-byte records → Market[]
→ MathLib.getBorrowApy(...) // compute rates from rateAtTarget + utilization
→ MathLib.getSupplyApy(...)
→ { [marketId]: MorphoGeneralPublicResponse }Binary encoding (256 bytes per market)
Offset Size Field
0 20 loanToken (address)
20 20 collateralToken (address)
40 20 oracle (address)
60 20 irm (address)
80 16 lltv (uint128)
96 32 price (uint256) — collateral/loan exchange rate in WAD
128 32 rateAtTarget (uint256) — per-second interest rate
160 16 totalSupplyAssets (uint128)
176 16 totalSupplyShares (uint128)
192 16 totalBorrowAssets (uint128)
208 16 totalBorrowShares (uint128)
224 16 lastUpdate (uint128) — timestamp
240 16 fee (uint128)User data path
buildMorphoUserCallWithLens(chainId, account, lender, marketIds)
→ MORPHO_LENS.getUserDataCompact(marketHashes[], account, morpho)
→ chunks of 100 markets per call
decodePackedMorphoUserDataset(hex)
→ [uint16 count] + [130-byte records × count]
→ BalanceInfo[] { index, supplyShares, borrowShares, supplyAssets, borrowAssets, collateral }
getMorphoUserDataConverterWithLens()
→ map balances to market IDs via index
→ separate loan positions (supply/borrow) from collateral positions
→ format to UserData with USD valuesFile structure
src/lending/
├── public-data/
│ ├── fetchLenderAll.ts # Hybrid router (API vs on-chain)
│ ├── fetchLender.ts # On-chain multicall dispatcher
│ ├── fetchLenderExt.ts # API dispatcher
│ └── morpho/
│ ├── fetchPublic.ts # GraphQL query + fetch logic
│ ├── convertPublic.ts # API response → normalized format
│ ├── publicCallBuild.ts # Morpho Lens call builder
│ ├── getMarketsFromChain.ts # On-chain response → normalized format
│ └── utils/
│ ├── evmParser.ts # Binary decoder (256-byte records)
│ ├── mathLib.ts # WAD math, rate calculations
│ └── parsers.ts # LTV parsing, number formatting
├── user-data/
│ └── morpho/
│ ├── userCallBuild.ts # User position call builder
│ ├── decoder.ts # User data binary decoder
│ ├── userCallParse.ts # Balance → UserData conversion
│ ├── morphoLib.ts # Share ↔ asset conversion
│ └── types.ts # Parameter position enums
└── ...
src/types/lender/
└── morpho-types.ts # GetMarketsResponse, MorphoMarket, MorphoGeneralPublicResponseLista DAO extension
Lista is a Morpho Blue fork with additional per-market fields. Uses a 357-byte binary record instead of 256:
| Extra field | Type | Description |
|-------------|------|-------------|
| minLoan | uint128 | Minimum loan amount |
| hasWhitelist | bool | Whether the market has access control |
| loanProvider | address | Yield source for loan token |
| collateralProvider | address | Yield source for collateral |
| broker | address | Authorized broker contract |
User data also includes per-market whitelist flags prepended before the balance records.
