From 41db9813a9171c1948b86eb8605a580e556b4687 Mon Sep 17 00:00:00 2001 From: Vladimir Mirzoyan Date: Thu, 7 May 2026 07:54:52 -0400 Subject: [PATCH 1/5] =?UTF-8?q?Add=20Unblock=20Equity=20yield=20adapter=20?= =?UTF-8?q?=E2=80=94=2024=20vaults=20on=20Base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UnblockEquity is a tokenized junior-lien home-equity lending protocol on Base. Depositors fund 24 segmented MetaMorpho vaults (verification × recovery × escrow tier); borrowers (homeowners) collateralize tokenized property liens (ERC-1155). TVL adapter for the protocol was merged in DefiLlama-Adapters#19102. This PR adds the matching yield-server adapter so APYs surface on /yields. - TVL: live from each vault's totalAssets() (ERC4626) - APY: modeled equilibrium yield from each vault's published risk model (PD × LGD → net yield). Matches the displayed APY in the protocol's earn page and the published risk whitepaper. Live IRM rates are intentionally NOT used because Morpho's adaptive-curve IRM drifts toward zero on idle high-utilization markets, which would misrepresent the long-run yield depositors actually earn. - Verified: 24/24 vaults addressable, total TVL $11.8K (matches on-chain). - APY range across 24 pools: 7.50%–9.26% Adapter URL: https://app.unblockequity.com/earn DefiLlama protocol page: https://defillama.com/protocol/unblock-equity --- src/adaptors/unblock-equity/index.js | 83 ++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/adaptors/unblock-equity/index.js diff --git a/src/adaptors/unblock-equity/index.js b/src/adaptors/unblock-equity/index.js new file mode 100644 index 0000000000..e6a2ae353c --- /dev/null +++ b/src/adaptors/unblock-equity/index.js @@ -0,0 +1,83 @@ +// UnblockEquity yield adapter for DefiLlama yield-server +// 24 MetaMorpho V2 vaults on Base — USDC lending against tokenized residential property liens +// +// TVL: live from each vault's totalAssets() (ERC4626 standard) +// APY: modeled equilibrium yield from each vault's risk model (PD × LGD → net yield). +// Matches what depositors see in https://app.unblockequity.com/earn and what's +// documented in the published risk whitepaper. Live IRM rates are intentionally +// NOT used — Morpho's adaptive curve IRM drifts toward zero on idle markets, +// which would misrepresent the long-run yield depositors actually earn. + +const { ethers } = require("ethers"); + +const BASE_RPC = "https://mainnet.base.org"; +const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; + +// 24 vaults: id (verification-recovery-breathingRoom), vaultAddress, name, modeled net yield +const VAULTS = [ + { id: "V-LO-NONE", vault: "0x287397Fd29aBCdb1f514179099121895A2f5bEAF", netYield: 7.76, name: "Verified Lien-Only" }, + { id: "V-LO-BR3", vault: "0x376736A69B8F9c350F76E0b2802466Eaee7E058f", netYield: 7.58, name: "Verified Lien BR3" }, + { id: "V-LO-BR6", vault: "0xd6313868B5CeBAd6fDc3aE48F80917B385C01c71", netYield: 7.60, name: "Verified Lien BR6" }, + { id: "V-LO-BR12", vault: "0x2FFcbDEa42311515E3dB1F873A1Cea0D463B5Ced", netYield: 7.58, name: "Verified Lien BR12" }, + { id: "V-FC-NONE", vault: "0x0eD4c2cfff2Ec06079e723F51aeFC8cdF073ea68", netYield: 7.66, name: "Verified Foreclosure" }, + { id: "V-FC-BR3", vault: "0x34dDd63FEA2868EeF439279D6FeCa7d5AcFc4F53", netYield: 7.61, name: "Verified FC BR3" }, + { id: "V-FC-BR6", vault: "0xc24C630D27CBF1Da6A78B09821212eb9c5e1be40", netYield: 7.58, name: "Verified FC BR6" }, + { id: "V-FC-BR12", vault: "0x8E246a89a7F8ffD4efD7d037bD585F7741C0C482", netYield: 7.55, name: "Verified FC BR12" }, + { id: "P-LO-NONE", vault: "0x13D2E770cefB62A8Aa4e3393d59F88707AbD4dd5", netYield: 7.91, name: "Prime Lien-Only" }, + { id: "P-LO-BR3", vault: "0xCc19805E91C66Ca6a3dd437E8F6d579ca9727804", netYield: 7.63, name: "Prime Lien BR3" }, + { id: "P-LO-BR6", vault: "0x2018963CA1e5ACeb88B7fA8738e4AEC846beD752", netYield: 7.67, name: "Prime Lien BR6" }, + { id: "P-LO-BR12", vault: "0x618fFcf6fF74dC3766B892B7913BF5074B913eF2", netYield: 7.62, name: "Prime Lien BR12" }, + { id: "P-FC-NONE", vault: "0x12d6bA2c11Bbb96F8f91b0412593b87dB4E2ABE2", netYield: 7.70, name: "Prime Foreclosure" }, + { id: "P-FC-BR3", vault: "0xFC274721AFdd37dB10419d08bd0db59E5Fcfb219", netYield: 7.64, name: "Prime FC BR3" }, + { id: "P-FC-BR6", vault: "0x098A23332008Cffaf283E3b0e8EcDEcfDeb6849c", netYield: 7.60, name: "Prime FC BR6" }, + { id: "P-FC-BR12", vault: "0xf6EA5C33F0D33B56AAda9AF7Dd1C4203BB83C82F", netYield: 7.56, name: "Prime FC BR12" }, + { id: "S-LO-NONE", vault: "0x2BE1d9ddBbd70E7b148E8AdE884600268a0B28BD", netYield: 9.26, name: "Standard Lien-Only" }, + { id: "S-LO-BR3", vault: "0x060b5d11B1303FaB362bAF100EB37601F04C2AFD", netYield: 8.01, name: "Standard Lien BR3" }, + { id: "S-LO-BR6", vault: "0x8Dfc0CaF025E62C634Ea179Ce04015f3ae51938a", netYield: 8.21, name: "Standard Lien BR6" }, + { id: "S-LO-BR12", vault: "0x012f6f383F13BD437DFBfCBe94D1A8C5fC40E650", netYield: 8.03, name: "Standard Lien BR12" }, + { id: "S-FC-NONE", vault: "0xef7EEeed223a45EB09808F98cA2B15cA16C7306D", netYield: 7.72, name: "Standard Foreclosure" }, + { id: "S-FC-BR3", vault: "0x4d390F54327b8d4ca6DFaF8db58BCFdF0270697b", netYield: 7.50, name: "Standard FC BR3" }, + { id: "S-FC-BR6", vault: "0xE6dfc9b8057135165B1aAAA741FbbBe0aF416104", netYield: 7.61, name: "Standard FC BR6" }, + { id: "S-FC-BR12", vault: "0x01EB25A573F1e86f46326B0DD1b4AB344ccB168E", netYield: 7.57, name: "Standard FC BR12" }, +]; + +const ERC4626_ABI = ["function totalAssets() view returns (uint256)"]; + +async function apy() { + const provider = new ethers.providers.JsonRpcProvider(BASE_RPC); + + const pools = []; + for (let i = 0; i < VAULTS.length; i += 6) { + const batch = VAULTS.slice(i, i + 6); + const results = await Promise.all(batch.map(async (v) => { + let tvlUsd = 0; + try { + const totalAssets = await new ethers.Contract(v.vault, ERC4626_ABI, provider).totalAssets(); + tvlUsd = parseFloat(ethers.utils.formatUnits(totalAssets, 6)); + } catch { + // Vault unreachable — list with zero TVL, modeled APY still informative + } + return { + pool: `${v.vault.toLowerCase()}-base`, + chain: "Base", + project: "unblock-equity", + symbol: "USDC", + tvlUsd, + apyBase: v.netYield, + underlyingTokens: [USDC], + url: "https://app.unblockequity.com/earn", + poolMeta: v.name, + }; + })); + pools.push(...results); + if (i + 6 < VAULTS.length) await new Promise((r) => setTimeout(r, 300)); + } + + return pools; +} + +module.exports = { + timetravel: false, + apy, + url: "https://app.unblockequity.com/earn", +}; From 9abf97aef81418d5e52bed9906ea3b401c6a4321 Mon Sep 17 00:00:00 2001 From: Vladimir Mirzoyan Date: Mon, 18 May 2026 13:57:45 -0400 Subject: [PATCH 2/5] unblock-equity: address maintainer review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch from raw ethers to @defillama/sdk multicall pattern - Drop hardcoded netYield; compute supply APY dynamically from on-chain Morpho IRM (borrowRateView × utilization × (1 - fee)) - Add RPC-outage guard: throw if totalAssets() fails for all 24 vaults - IRM call skipped when supply = 0 (avoids unnecessary calls for empty markets) Per @0xkr3p review feedback in PR #2659. --- src/adaptors/unblock-equity/index.js | 274 ++++++++++++++++++++------- 1 file changed, 206 insertions(+), 68 deletions(-) diff --git a/src/adaptors/unblock-equity/index.js b/src/adaptors/unblock-equity/index.js index e6a2ae353c..6010c1d7c0 100644 --- a/src/adaptors/unblock-equity/index.js +++ b/src/adaptors/unblock-equity/index.js @@ -1,83 +1,221 @@ // UnblockEquity yield adapter for DefiLlama yield-server -// 24 MetaMorpho V2 vaults on Base — USDC lending against tokenized residential property liens +// 24 MetaMorpho V2 vaults on Base — USDC lending against tokenized residential property liens. // -// TVL: live from each vault's totalAssets() (ERC4626 standard) -// APY: modeled equilibrium yield from each vault's risk model (PD × LGD → net yield). -// Matches what depositors see in https://app.unblockequity.com/earn and what's -// documented in the published risk whitepaper. Live IRM rates are intentionally -// NOT used — Morpho's adaptive curve IRM drifts toward zero on idle markets, -// which would misrepresent the long-run yield depositors actually earn. +// TVL : live totalAssets() per vault (ERC4626), summed via SDK multicall. +// APY : live supply APY computed from each market's Morpho Blue IRM: +// supply APY = (1 + borrowRatePerSec)^(secondsPerYear) - 1, scaled by +// utilization × (1 - fee). Mirrors Morpho's own SDK math. +// Vaults are NOT yet whitelisted in Morpho's GraphQL API, so this +// adapter goes directly to chain via DefiLlama SDK. -const { ethers } = require("ethers"); +const sdk = require('@defillama/sdk'); -const BASE_RPC = "https://mainnet.base.org"; -const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; +const CHAIN = 'base'; +const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; +const MORPHO_BLUE = '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb'; +const ADAPTIVE_CURVE_IRM = '0x46415998764C29aB2a25CbeA6254146D50D22687'; +const SECONDS_PER_YEAR = 31_536_000; -// 24 vaults: id (verification-recovery-breathingRoom), vaultAddress, name, modeled net yield +// 24 vaults: { id, vault, marketId, name } +// id format: {Verification}-{Recovery}-{BreathingRoom} const VAULTS = [ - { id: "V-LO-NONE", vault: "0x287397Fd29aBCdb1f514179099121895A2f5bEAF", netYield: 7.76, name: "Verified Lien-Only" }, - { id: "V-LO-BR3", vault: "0x376736A69B8F9c350F76E0b2802466Eaee7E058f", netYield: 7.58, name: "Verified Lien BR3" }, - { id: "V-LO-BR6", vault: "0xd6313868B5CeBAd6fDc3aE48F80917B385C01c71", netYield: 7.60, name: "Verified Lien BR6" }, - { id: "V-LO-BR12", vault: "0x2FFcbDEa42311515E3dB1F873A1Cea0D463B5Ced", netYield: 7.58, name: "Verified Lien BR12" }, - { id: "V-FC-NONE", vault: "0x0eD4c2cfff2Ec06079e723F51aeFC8cdF073ea68", netYield: 7.66, name: "Verified Foreclosure" }, - { id: "V-FC-BR3", vault: "0x34dDd63FEA2868EeF439279D6FeCa7d5AcFc4F53", netYield: 7.61, name: "Verified FC BR3" }, - { id: "V-FC-BR6", vault: "0xc24C630D27CBF1Da6A78B09821212eb9c5e1be40", netYield: 7.58, name: "Verified FC BR6" }, - { id: "V-FC-BR12", vault: "0x8E246a89a7F8ffD4efD7d037bD585F7741C0C482", netYield: 7.55, name: "Verified FC BR12" }, - { id: "P-LO-NONE", vault: "0x13D2E770cefB62A8Aa4e3393d59F88707AbD4dd5", netYield: 7.91, name: "Prime Lien-Only" }, - { id: "P-LO-BR3", vault: "0xCc19805E91C66Ca6a3dd437E8F6d579ca9727804", netYield: 7.63, name: "Prime Lien BR3" }, - { id: "P-LO-BR6", vault: "0x2018963CA1e5ACeb88B7fA8738e4AEC846beD752", netYield: 7.67, name: "Prime Lien BR6" }, - { id: "P-LO-BR12", vault: "0x618fFcf6fF74dC3766B892B7913BF5074B913eF2", netYield: 7.62, name: "Prime Lien BR12" }, - { id: "P-FC-NONE", vault: "0x12d6bA2c11Bbb96F8f91b0412593b87dB4E2ABE2", netYield: 7.70, name: "Prime Foreclosure" }, - { id: "P-FC-BR3", vault: "0xFC274721AFdd37dB10419d08bd0db59E5Fcfb219", netYield: 7.64, name: "Prime FC BR3" }, - { id: "P-FC-BR6", vault: "0x098A23332008Cffaf283E3b0e8EcDEcfDeb6849c", netYield: 7.60, name: "Prime FC BR6" }, - { id: "P-FC-BR12", vault: "0xf6EA5C33F0D33B56AAda9AF7Dd1C4203BB83C82F", netYield: 7.56, name: "Prime FC BR12" }, - { id: "S-LO-NONE", vault: "0x2BE1d9ddBbd70E7b148E8AdE884600268a0B28BD", netYield: 9.26, name: "Standard Lien-Only" }, - { id: "S-LO-BR3", vault: "0x060b5d11B1303FaB362bAF100EB37601F04C2AFD", netYield: 8.01, name: "Standard Lien BR3" }, - { id: "S-LO-BR6", vault: "0x8Dfc0CaF025E62C634Ea179Ce04015f3ae51938a", netYield: 8.21, name: "Standard Lien BR6" }, - { id: "S-LO-BR12", vault: "0x012f6f383F13BD437DFBfCBe94D1A8C5fC40E650", netYield: 8.03, name: "Standard Lien BR12" }, - { id: "S-FC-NONE", vault: "0xef7EEeed223a45EB09808F98cA2B15cA16C7306D", netYield: 7.72, name: "Standard Foreclosure" }, - { id: "S-FC-BR3", vault: "0x4d390F54327b8d4ca6DFaF8db58BCFdF0270697b", netYield: 7.50, name: "Standard FC BR3" }, - { id: "S-FC-BR6", vault: "0xE6dfc9b8057135165B1aAAA741FbbBe0aF416104", netYield: 7.61, name: "Standard FC BR6" }, - { id: "S-FC-BR12", vault: "0x01EB25A573F1e86f46326B0DD1b4AB344ccB168E", netYield: 7.57, name: "Standard FC BR12" }, + { id: 'V-LO-NONE', vault: '0x287397Fd29aBCdb1f514179099121895A2f5bEAF', marketId: '0x986fdbe36a48d06b417e2478df6a36749b6bc3007d213d64a663717457dfc145', name: 'Verified Lien-Only' }, + { id: 'V-LO-BR3', vault: '0x376736A69B8F9c350F76E0b2802466Eaee7E058f', marketId: '0x7a829ded90784f87677ce6c3937e3be72fe50dff94b88c270801297bfee2a1d1', name: 'Verified Lien BR3' }, + { id: 'V-LO-BR6', vault: '0xd6313868B5CeBAd6fDc3aE48F80917B385C01c71', marketId: '0x2200d499132537a83f7ef3819523ace5be21c56ac34db532e3d90d9b2fdb4994', name: 'Verified Lien BR6' }, + { id: 'V-LO-BR12', vault: '0x2FFcbDEa42311515E3dB1F873A1Cea0D463B5Ced', marketId: '0x15161eae104e3be16ae1396a92fd36edfd8f68915e309a4268f2d224243b77fc', name: 'Verified Lien BR12' }, + { id: 'V-FC-NONE', vault: '0x0eD4c2cfff2Ec06079e723F51aeFC8cdF073ea68', marketId: '0xbceb193d1d38c2175cbbb67150adea23faa59bea039bf98ff24ea87c1f519dd9', name: 'Verified Foreclosure' }, + { id: 'V-FC-BR3', vault: '0x34dDd63FEA2868EeF439279D6FeCa7d5AcFc4F53', marketId: '0xf0ef2374ca4e72dcbaafb121a5b2fa6fa37c90aef94285d94f0c585a50f8dffc', name: 'Verified FC BR3' }, + { id: 'V-FC-BR6', vault: '0xc24C630D27CBF1Da6A78B09821212eb9c5e1be40', marketId: '0xbcdbaf745449618885bc9b191b08b9eae0a89b03bd6cc33934e8a978dba4d23a', name: 'Verified FC BR6' }, + { id: 'V-FC-BR12', vault: '0x8E246a89a7F8ffD4efD7d037bD585F7741C0C482', marketId: '0x363c5d37ecf3d6aaf3a2c863d4af15bb776ddb618d9251c290367af2b4f56d4d', name: 'Verified FC BR12' }, + { id: 'P-LO-NONE', vault: '0x13D2E770cefB62A8Aa4e3393d59F88707AbD4dd5', marketId: '0xf0ff0e31eac71ca3b145e4f8b37c2b0d6d594e7c234216c36e1f7c5ce6561f14', name: 'Prime Lien-Only' }, + { id: 'P-LO-BR3', vault: '0xCc19805E91C66Ca6a3dd437E8F6d579ca9727804', marketId: '0x6b78a3ac1937aa4a4c3177e870b93676f9227d3a8d4ea220dcf3e6123d836e6c', name: 'Prime Lien BR3' }, + { id: 'P-LO-BR6', vault: '0x2018963CA1e5ACeb88B7fA8738e4AEC846beD752', marketId: '0xcbe9f0f9be21b71a1e0d8c37a3b1b63a43516191e912219a9ae2d1e23ec602aa', name: 'Prime Lien BR6' }, + { id: 'P-LO-BR12', vault: '0x618fFcf6fF74dC3766B892B7913BF5074B913eF2', marketId: '0x8a6e7b507ba9fe2b2a0fc57ee53b024ebd4cc5352ce53c97e6f794d7a1d1fb9b', name: 'Prime Lien BR12' }, + { id: 'P-FC-NONE', vault: '0x12d6bA2c11Bbb96F8f91b0412593b87dB4E2ABE2', marketId: '0xa156f1200b5619a67d78a694b59d3f6c2327fedd68d4faf712c0f9aea66a9eff', name: 'Prime Foreclosure' }, + { id: 'P-FC-BR3', vault: '0xFC274721AFdd37dB10419d08bd0db59E5Fcfb219', marketId: '0x90153d38066e766ed2253d015e9dc4992b4dc1ae3d7f01b17b9fe6ab48da38d7', name: 'Prime FC BR3' }, + { id: 'P-FC-BR6', vault: '0x098A23332008Cffaf283E3b0e8EcDEcfDeb6849c', marketId: '0xd0aab26e360613fa8d1bdbea06dedda8d10a2942269569715640be4475f4d3ce', name: 'Prime FC BR6' }, + { id: 'P-FC-BR12', vault: '0xf6EA5C33F0D33B56AAda9AF7Dd1C4203BB83C82F', marketId: '0xed7346873bf45e21d2e623ba36fc004ad7c2c968a9f81375666aa06cd7b4e190', name: 'Prime FC BR12' }, + { id: 'S-LO-NONE', vault: '0x2BE1d9ddBbd70E7b148E8AdE884600268a0B28BD', marketId: '0x39ed69b74b75b2c1487c7f96e6a744d0fa5369a7f3bacb0b8a253687650e107c', name: 'Standard Lien-Only' }, + { id: 'S-LO-BR3', vault: '0x060b5d11B1303FaB362bAF100EB37601F04C2AFD', marketId: '0xb992bed8501e65df324a530cf0aefc1f8f6928dcb18c8c44014f165138f92058', name: 'Standard Lien BR3' }, + { id: 'S-LO-BR6', vault: '0x8Dfc0CaF025E62C634Ea179Ce04015f3ae51938a', marketId: '0x463ec5463398aafe277de322c600a3fd21ed4c9c24a395bc4c332043bfbf8cf1', name: 'Standard Lien BR6' }, + { id: 'S-LO-BR12', vault: '0x012f6f383F13BD437DFBfCBe94D1A8C5fC40E650', marketId: '0xf8c7456ba569730162a499aa3cbc8023a23d619863d7ccb5a64a0b48730a5561', name: 'Standard Lien BR12' }, + { id: 'S-FC-NONE', vault: '0xef7EEeed223a45EB09808F98cA2B15cA16C7306D', marketId: '0xa215e2d5bf02bb47558b24ddb1f23c41d03dd257d3d371f9c0162abafdacc0c8', name: 'Standard Foreclosure' }, + { id: 'S-FC-BR3', vault: '0x4d390F54327b8d4ca6DFaF8db58BCFdF0270697b', marketId: '0xc50b36a93f2d0315344f0de53665b7d795bc7b887a324ec0191c9a9e41570923', name: 'Standard FC BR3' }, + { id: 'S-FC-BR6', vault: '0xE6dfc9b8057135165B1aAAA741FbbBe0aF416104', marketId: '0x3cff87edbc5b52ce33155df9503915f84a9f2515911d33430863c61d4442f1fb', name: 'Standard FC BR6' }, + { id: 'S-FC-BR12', vault: '0x01EB25A573F1e86f46326B0DD1b4AB344ccB168E', marketId: '0x2f72395ca8aaaee20aad0a52c0ae379b76c47006d7b86c516c25d5fc46c6ccd7', name: 'Standard FC BR12' }, ]; -const ERC4626_ABI = ["function totalAssets() view returns (uint256)"]; - -async function apy() { - const provider = new ethers.providers.JsonRpcProvider(BASE_RPC); - - const pools = []; - for (let i = 0; i < VAULTS.length; i += 6) { - const batch = VAULTS.slice(i, i + 6); - const results = await Promise.all(batch.map(async (v) => { - let tvlUsd = 0; - try { - const totalAssets = await new ethers.Contract(v.vault, ERC4626_ABI, provider).totalAssets(); - tvlUsd = parseFloat(ethers.utils.formatUnits(totalAssets, 6)); - } catch { - // Vault unreachable — list with zero TVL, modeled APY still informative - } - return { - pool: `${v.vault.toLowerCase()}-base`, - chain: "Base", - project: "unblock-equity", - symbol: "USDC", - tvlUsd, - apyBase: v.netYield, - underlyingTokens: [USDC], - url: "https://app.unblockequity.com/earn", - poolMeta: v.name, - }; - })); - pools.push(...results); - if (i + 6 < VAULTS.length) await new Promise((r) => setTimeout(r, 300)); +// Morpho market struct: (uint128 supplyAssets, uint128 supplyShares, uint128 borrowAssets, +// uint128 borrowShares, uint128 lastUpdate, uint128 fee) +const MARKET_ABI = { + name: 'market', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'bytes32', name: 'id' }], + outputs: [ + { type: 'uint128', name: 'totalSupplyAssets' }, + { type: 'uint128', name: 'totalSupplyShares' }, + { type: 'uint128', name: 'totalBorrowAssets' }, + { type: 'uint128', name: 'totalBorrowShares' }, + { type: 'uint128', name: 'lastUpdate' }, + { type: 'uint128', name: 'fee' }, + ], +}; + +const MARKET_PARAMS_ABI = { + name: 'idToMarketParams', + type: 'function', + stateMutability: 'view', + inputs: [{ type: 'bytes32', name: 'id' }], + outputs: [ + { type: 'address', name: 'loanToken' }, + { type: 'address', name: 'collateralToken' }, + { type: 'address', name: 'oracle' }, + { type: 'address', name: 'irm' }, + { type: 'uint256', name: 'lltv' }, + ], +}; + +// IRM.borrowRateView((MarketParams), (Market)) -> uint256 ratePerSecondWad +const IRM_BORROW_RATE_VIEW_ABI = { + name: 'borrowRateView', + type: 'function', + stateMutability: 'view', + inputs: [ + { + type: 'tuple', + name: 'marketParams', + components: [ + { type: 'address', name: 'loanToken' }, + { type: 'address', name: 'collateralToken' }, + { type: 'address', name: 'oracle' }, + { type: 'address', name: 'irm' }, + { type: 'uint256', name: 'lltv' }, + ], + }, + { + type: 'tuple', + name: 'market', + components: [ + { type: 'uint128', name: 'totalSupplyAssets' }, + { type: 'uint128', name: 'totalSupplyShares' }, + { type: 'uint128', name: 'totalBorrowAssets' }, + { type: 'uint128', name: 'totalBorrowShares' }, + { type: 'uint128', name: 'lastUpdate' }, + { type: 'uint128', name: 'fee' }, + ], + }, + ], + outputs: [{ type: 'uint256', name: '' }], +}; + +function computeSupplyApy(ratePerSecWad, supply, borrow, feeWad) { + if (!supply || supply === 0n) return 0; + const rate = Number(ratePerSecWad) / 1e18; + const borrowApy = (Math.pow(1 + rate, SECONDS_PER_YEAR) - 1) * 100; + const utilization = Number(borrow) / Number(supply); + const feeFactor = 1 - Number(feeWad) / 1e18; + return borrowApy * utilization * feeFactor; +} + +const poolsFunction = async () => { + // 1. TVL — multicall totalAssets() across all 24 vaults + const totalAssetsRes = await sdk.api.abi.multiCall({ + abi: 'uint256:totalAssets', + calls: VAULTS.map((v) => ({ target: v.vault })), + chain: CHAIN, + }); + + // 2. Market state — multicall market(id) across all 24 markets + const marketRes = await sdk.api.abi.multiCall({ + abi: MARKET_ABI, + calls: VAULTS.map((v) => ({ target: MORPHO_BLUE, params: [v.marketId] })), + chain: CHAIN, + }); + + // 3. Market params — multicall idToMarketParams(id) + const paramsRes = await sdk.api.abi.multiCall({ + abi: MARKET_PARAMS_ABI, + calls: VAULTS.map((v) => ({ target: MORPHO_BLUE, params: [v.marketId] })), + chain: CHAIN, + }); + + // 4. Borrow rate per market — only call if market has supply + const irmCalls = VAULTS.map((v, i) => { + const m = marketRes.output[i]?.output; + const p = paramsRes.output[i]?.output; + if (!m || !p || BigInt(m.totalSupplyAssets || 0) === 0n) return null; + return { + target: ADAPTIVE_CURVE_IRM, + params: [ + [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv], + [m.totalSupplyAssets, m.totalSupplyShares, m.totalBorrowAssets, m.totalBorrowShares, m.lastUpdate, m.fee], + ], + }; + }); + + const irmRes = await sdk.api.abi.multiCall({ + abi: IRM_BORROW_RATE_VIEW_ABI, + calls: irmCalls.filter((c) => c !== null), + chain: CHAIN, + permitFailure: true, + }); + + // Map borrow-rate responses back to vault indices + const rateByIndex = new Map(); + let rateCursor = 0; + for (let i = 0; i < VAULTS.length; i++) { + if (irmCalls[i] !== null) { + rateByIndex.set(i, irmRes.output[rateCursor]?.output); + rateCursor += 1; + } + } + + // 5. Assemble pools + count failures for RPC-outage guard + let tvlFailures = 0; + const pools = VAULTS.map((v, i) => { + const totalAssetsRaw = totalAssetsRes.output[i]?.output; + let tvlUsd = 0; + if (totalAssetsRaw == null) { + tvlFailures += 1; + } else { + tvlUsd = Number(totalAssetsRaw) / 1e6; + } + + const m = marketRes.output[i]?.output; + const rate = rateByIndex.get(i); + let apyBase = 0; + if (m && rate != null) { + apyBase = computeSupplyApy( + BigInt(rate), + BigInt(m.totalSupplyAssets || 0), + BigInt(m.totalBorrowAssets || 0), + BigInt(m.fee || 0), + ); + } + + return { + pool: `${v.vault.toLowerCase()}-base`, + chain: 'Base', + project: 'unblock-equity', + symbol: 'USDC', + tvlUsd, + apyBase, + underlyingTokens: [USDC], + url: 'https://app.unblockequity.com/earn', + poolMeta: v.name, + }; + }); + + if (tvlFailures === VAULTS.length) { + throw new Error('unblock-equity: failed to fetch totalAssets() for all vaults'); } return pools; -} +}; module.exports = { timetravel: false, - apy, - url: "https://app.unblockequity.com/earn", + apy: poolsFunction, + url: 'https://app.unblockequity.com/earn', }; From 7fd254a4bcd40e3796ceded2f42c8f5d26cf28e9 Mon Sep 17 00:00:00 2001 From: Vladimir Mirzoyan Date: Mon, 18 May 2026 19:28:39 -0400 Subject: [PATCH 3/5] unblock-equity: use per-market irm address (future-proof per CodeRabbit nit) Read each market's IRM address from the on-chain market params (p.irm) instead of hardcoding the AdaptiveCurve IRM constant. If any market ever switches IRM, the adapter still calls the correct contract. --- src/adaptors/unblock-equity/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/adaptors/unblock-equity/index.js b/src/adaptors/unblock-equity/index.js index 6010c1d7c0..e8598dd26c 100644 --- a/src/adaptors/unblock-equity/index.js +++ b/src/adaptors/unblock-equity/index.js @@ -13,7 +13,6 @@ const sdk = require('@defillama/sdk'); const CHAIN = 'base'; const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const MORPHO_BLUE = '0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb'; -const ADAPTIVE_CURVE_IRM = '0x46415998764C29aB2a25CbeA6254146D50D22687'; const SECONDS_PER_YEAR = 31_536_000; // 24 vaults: { id, vault, marketId, name } @@ -140,13 +139,15 @@ const poolsFunction = async () => { chain: CHAIN, }); - // 4. Borrow rate per market — only call if market has supply + // 4. Borrow rate per market — only call if market has supply. + // Target the market's own IRM address (from market params) so this stays + // correct if any market ever uses a non-AdaptiveCurve IRM in the future. const irmCalls = VAULTS.map((v, i) => { const m = marketRes.output[i]?.output; const p = paramsRes.output[i]?.output; if (!m || !p || BigInt(m.totalSupplyAssets || 0) === 0n) return null; return { - target: ADAPTIVE_CURVE_IRM, + target: p.irm, params: [ [p.loanToken, p.collateralToken, p.oracle, p.irm, p.lltv], [m.totalSupplyAssets, m.totalSupplyShares, m.totalBorrowAssets, m.totalBorrowShares, m.lastUpdate, m.fee], From 5abe271f53a4602e78885f43eefe537d4a458b73 Mon Sep 17 00:00:00 2001 From: Vladimir Mirzoyan Date: Tue, 19 May 2026 09:14:37 -0400 Subject: [PATCH 4/5] unblock-equity: add pricePerShare per @0xkr3p review Multicalls each vault's totalSupply() alongside totalAssets(), then computes pricePerShare = (totalAssets / 10^assetDec) / (totalSupply / 10^shareDec). Assets are USDC (6 dec), shares are 18 dec. Empty vaults default to pricePerShare = 1.0. Live genesis vault currently shows pps = 1.00055278. --- src/adaptors/unblock-equity/index.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/adaptors/unblock-equity/index.js b/src/adaptors/unblock-equity/index.js index e8598dd26c..1a93a08e7d 100644 --- a/src/adaptors/unblock-equity/index.js +++ b/src/adaptors/unblock-equity/index.js @@ -118,13 +118,20 @@ function computeSupplyApy(ratePerSecWad, supply, borrow, feeWad) { } const poolsFunction = async () => { - // 1. TVL — multicall totalAssets() across all 24 vaults + // 1a. TVL — multicall totalAssets() across all 24 vaults const totalAssetsRes = await sdk.api.abi.multiCall({ abi: 'uint256:totalAssets', calls: VAULTS.map((v) => ({ target: v.vault })), chain: CHAIN, }); + // 1b. Share supply — needed for pricePerShare + const totalSharesRes = await sdk.api.abi.multiCall({ + abi: 'erc20:totalSupply', + calls: VAULTS.map((v) => ({ target: v.vault })), + chain: CHAIN, + }); + // 2. Market state — multicall market(id) across all 24 markets const marketRes = await sdk.api.abi.multiCall({ abi: MARKET_ABI, @@ -173,6 +180,9 @@ const poolsFunction = async () => { } // 5. Assemble pools + count failures for RPC-outage guard + // pricePerShare: assets-per-share, in human units. + // assets = USDC (6 dec), shares = vault token (18 dec). + // pricePerShare = (totalAssets * 10^12) / totalSupply, with 1.0 fallback for empty vaults. let tvlFailures = 0; const pools = VAULTS.map((v, i) => { const totalAssetsRaw = totalAssetsRes.output[i]?.output; @@ -183,6 +193,14 @@ const poolsFunction = async () => { tvlUsd = Number(totalAssetsRaw) / 1e6; } + const totalSharesRaw = totalSharesRes.output[i]?.output; + let pricePerShare = 1; + if (totalAssetsRaw != null && totalSharesRaw != null && BigInt(totalSharesRaw) > 0n) { + // shares are 18-dec, assets are 6-dec → scale by 10^12 to express pps in assets + const scaled = (BigInt(totalAssetsRaw) * 10n ** 12n * 10n ** 9n) / BigInt(totalSharesRaw); + pricePerShare = Number(scaled) / 1e9; + } + const m = marketRes.output[i]?.output; const rate = rateByIndex.get(i); let apyBase = 0; @@ -202,6 +220,7 @@ const poolsFunction = async () => { symbol: 'USDC', tvlUsd, apyBase, + pricePerShare, underlyingTokens: [USDC], url: 'https://app.unblockequity.com/earn', poolMeta: v.name, From 3f1a8b78585313225e0c09126c569b9968366c9a Mon Sep 17 00:00:00 2001 From: Vladimir Mirzoyan Date: Tue, 19 May 2026 10:08:46 -0400 Subject: [PATCH 5/5] unblock-equity: add IRM-outage guard (symmetric with TVL guard) If every funded market's borrowRateView() call fails, abort instead of publishing 24 pools with apyBase = 0 (misleading-data parity with the existing totalAssets() guard). Per CodeRabbit review in PR #2659. --- src/adaptors/unblock-equity/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/adaptors/unblock-equity/index.js b/src/adaptors/unblock-equity/index.js index 1a93a08e7d..b9bf5dc493 100644 --- a/src/adaptors/unblock-equity/index.js +++ b/src/adaptors/unblock-equity/index.js @@ -169,6 +169,16 @@ const poolsFunction = async () => { permitFailure: true, }); + // Symmetric guard with the TVL check: if every funded market's borrowRateView + // failed, all pools would silently publish apyBase = 0 — abort instead. + const expectedRateCalls = irmCalls.filter((c) => c !== null).length; + const successfulRateCalls = irmRes.output.filter( + (r) => r?.success !== false && r?.output != null + ).length; + if (expectedRateCalls > 0 && successfulRateCalls === 0) { + throw new Error('unblock-equity: failed to fetch borrowRateView() for all funded markets'); + } + // Map borrow-rate responses back to vault indices const rateByIndex = new Map(); let rateCursor = 0;