surf-liquid: Add V4 yield pools for Ethereum, Arbitrum, Polygon, Base#2626
surf-liquid: Add V4 yield pools for Ethereum, Arbitrum, Polygon, Base#2626rohansingh4 wants to merge 2 commits into
Conversation
V4 (UserVaultFactory 0x8fa50...) deployed March 26, 2026. Queries getAllowedAssets() from VaultRegistry, then computes APY from Morpho vault share price changes and TVL via getAssetVaultAssets(). V2/V3 Base pools unchanged.
📝 WalkthroughWalkthroughThis PR extends the SURF liquid adapter to support v4 liquidity pools across multiple chains and adds a second staking contract. The changes introduce v4 vault discovery, APY calculation from Morpho vault data, TVL aggregation, staking pool generation for both contract addresses, and integration of v4 pools into the main pool output. ChangesSURF V4 Liquidity and Staking V2 Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes The changes involve substantial new logic across multiple areas: v4 vault discovery with event filtering, multi-step APY calculation from historical vault data, TVL computation with asset weighting, parallel chain processing, and dual-contract staking pool construction. The code is dense with data transformations and external API calls requiring careful verification of calculation logic and edge-case handling. Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The surf-liquid adapter exports pools: Test Suites: 1 passed, 1 total |
|
The surf-liquid adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/adaptors/surf-liquid/index.js (1)
70-83: ⚡ Quick winResolve token decimals on-chain instead of defaulting to
18.
getAllowedAssets()is dynamic, so this fallback can misprice 6/8-decimal assets by orders of magnitude if the price API omitsdecimals, which will also make the$100TVL filter drop valid pools. Multicalling ERC-20decimals()once per asset is a much safer source of truth here.Safer pattern
+ const { output: decimalsRes } = await sdk.api.abi.multiCall({ + abi: 'erc20:decimals', + calls: assets.map((target) => ({ target })), + chain, + }); + + const assetDecimals = Object.fromEntries( + assets.map((asset, i) => [asset, Number(decimalsRes[i]?.output ?? 18)]) + ); + for (const asset of assets) { const priceKey = `${chain}:${asset}`; const price = coinData[priceKey]?.price || 0; - const decimals = coinData[priceKey]?.decimals || 18; + const decimals = assetDecimals[asset]; const symbol = coinData[priceKey]?.symbol || asset.slice(0, 6);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adaptors/surf-liquid/index.js` around lines 70 - 83, The code currently falls back to decimals = coinData[priceKey]?.decimals || 18 which can misprice assets when the price API omits decimals; update the logic in src/adaptors/surf-liquid/index.js (look for priceKeys, coinData, priceKey, decimals, symbol and assets) to resolve ERC‑20 decimals on-chain instead of defaulting to 18: perform a single multicall (or per-asset call if multicall helper not available) to token.decimals() for all entries returned by getAllowedAssets(), use those on-chain decimals to compute prices/TVL when coinData lacks decimals, and only fall back to 18 as an absolute last resort after the on-chain call fails. Ensure the new call happens before constructing pools so price/decimals are accurate for each priceKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adaptors/surf-liquid/index.js`:
- Around line 79-181: In fetchV4ChainPools(), avoid letting any per-asset
ABI/price call reject the whole chain: wrap the per-asset work (the multiCall
blocks for morphoResults, nowAssets/nowSupply/pastAssets/pastSupply, and
tvlResults plus price lookup from coinData) in a try/catch so a failure for one
asset (e.g., errors from the calls that produce morphoResults, nowAssets,
pastAssets, morphoApyMap, or tvlResults) logs the asset and error and continues
to the next asset instead of throwing; specifically, around the loop body that
builds uniqueMorpho, computes morphoApyMap, and computes
totalTvlUsd/weightedApy, catch errors and skip pushing the pool for that asset
(do the same containment for the other instance noted at the end of file),
ensuring any thrown error does not bubble up to the chain-level
Promise.allSettled.
- Around line 130-140: The loop building morphoApyMap currently converts uint256
outputs to Number (aNow, sNow, aPast, sPast) which loses precision; change to
parse outputs as BigInt for nowAssets, nowSupply, pastAssets, pastSupply,
compute share prices using integer fixed-point math (e.g. multiply numerator by
a constant SCALE BigInt before dividing to get a scaled priceNow and pricePast),
then compute the growth ratio and annualize using floating math only after
converting the normalized ratio to Number; update references to
aNow/sNow/aPast/sPast, priceNow/pricePast and apy so that BigInt math is used
until the final Number conversion when assigning
morphoApyMap[uniqueMorpho[i].toLowerCase()] = Math.max(apy, 0).
---
Nitpick comments:
In `@src/adaptors/surf-liquid/index.js`:
- Around line 70-83: The code currently falls back to decimals =
coinData[priceKey]?.decimals || 18 which can misprice assets when the price API
omits decimals; update the logic in src/adaptors/surf-liquid/index.js (look for
priceKeys, coinData, priceKey, decimals, symbol and assets) to resolve ERC‑20
decimals on-chain instead of defaulting to 18: perform a single multicall (or
per-asset call if multicall helper not available) to token.decimals() for all
entries returned by getAllowedAssets(), use those on-chain decimals to compute
prices/TVL when coinData lacks decimals, and only fall back to 18 as an absolute
last resort after the on-chain call fails. Ensure the new call happens before
constructing pools so price/decimals are accurate for each priceKey.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c5dbcb9e-44da-4146-8d92-62a250c91ea8
📒 Files selected for processing (1)
src/adaptors/surf-liquid/index.js
| for (const asset of assets) { | ||
| const priceKey = `${chain}:${asset}`; | ||
| const price = coinData[priceKey]?.price || 0; | ||
| const decimals = coinData[priceKey]?.decimals || 18; | ||
| const symbol = coinData[priceKey]?.symbol || asset.slice(0, 6); | ||
|
|
||
| // Get active Morpho vault per user vault (for APY weighting) | ||
| const { output: morphoResults } = await sdk.api.abi.multiCall({ | ||
| abi: 'function assetToVault(address) view returns (address)', | ||
| calls: userVaults.map((vault) => ({ target: vault, params: [asset] })), | ||
| chain, | ||
| }); | ||
|
|
||
| const uniqueMorpho = [ | ||
| ...new Set( | ||
| morphoResults | ||
| .map((r) => r.output) | ||
| .filter((v) => v && v !== ZERO_ADDR) | ||
| ), | ||
| ]; | ||
|
|
||
| if (uniqueMorpho.length === 0) continue; | ||
|
|
||
| // Compute APY from Morpho vault share price changes (24h window) | ||
| const [nowAssets, nowSupply, pastAssets, pastSupply] = await Promise.all([ | ||
| sdk.api.abi.multiCall({ | ||
| abi: 'uint256:totalAssets', | ||
| calls: uniqueMorpho.map((t) => ({ target: t })), | ||
| chain, | ||
| block: blockNow, | ||
| }), | ||
| sdk.api.abi.multiCall({ | ||
| abi: 'uint256:totalSupply', | ||
| calls: uniqueMorpho.map((t) => ({ target: t })), | ||
| chain, | ||
| block: blockNow, | ||
| }), | ||
| sdk.api.abi.multiCall({ | ||
| abi: 'uint256:totalAssets', | ||
| calls: uniqueMorpho.map((t) => ({ target: t })), | ||
| chain, | ||
| block: blockPast, | ||
| }), | ||
| sdk.api.abi.multiCall({ | ||
| abi: 'uint256:totalSupply', | ||
| calls: uniqueMorpho.map((t) => ({ target: t })), | ||
| chain, | ||
| block: blockPast, | ||
| }), | ||
| ]); | ||
|
|
||
| const morphoApyMap = {}; | ||
| for (let i = 0; i < uniqueMorpho.length; i++) { | ||
| const aNow = Number(nowAssets.output[i].output || '0'); | ||
| const sNow = Number(nowSupply.output[i].output || '0'); | ||
| const aPast = Number(pastAssets.output[i].output || '0'); | ||
| const sPast = Number(pastSupply.output[i].output || '0'); | ||
| if (sNow <= 0 || sPast <= 0) continue; | ||
| const priceNow = aNow / sNow; | ||
| const pricePast = aPast / sPast; | ||
| const apy = pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0; | ||
| morphoApyMap[uniqueMorpho[i].toLowerCase()] = Math.max(apy, 0); | ||
| } | ||
|
|
||
| // Get underlying asset value per user vault (TVL) | ||
| const { output: tvlResults } = await sdk.api.abi.multiCall({ | ||
| abi: 'function getAssetVaultAssets(address) view returns (uint256)', | ||
| calls: userVaults.map((vault) => ({ target: vault, params: [asset] })), | ||
| chain, | ||
| }); | ||
|
|
||
| let totalTvlUsd = 0; | ||
| let weightedApy = 0; | ||
|
|
||
| for (let i = 0; i < userVaults.length; i++) { | ||
| const amount = BigInt(tvlResults[i].output || '0'); | ||
| if (amount === 0n) continue; | ||
|
|
||
| const tvlUsd = (Number(amount) / 10 ** decimals) * price; | ||
| totalTvlUsd += tvlUsd; | ||
|
|
||
| const morphoAddr = morphoResults[i].output; | ||
| if (morphoAddr && morphoAddr !== ZERO_ADDR) { | ||
| const apy = morphoApyMap[morphoAddr.toLowerCase()] || 0; | ||
| weightedApy += apy * tvlUsd; | ||
| } | ||
| } | ||
|
|
||
| if (totalTvlUsd < 100) continue; | ||
|
|
||
| const avgApy = totalTvlUsd > 0 ? weightedApy / totalTvlUsd : 0; | ||
| const userApy = avgApy * (1 - PERFORMANCE_FEE); | ||
|
|
||
| pools.push({ | ||
| pool: `surf-v4-${asset.toLowerCase()}-${chain}`, | ||
| chain: utils.formatChain(chain), | ||
| project: 'surf-liquid', | ||
| symbol, | ||
| tvlUsd: totalTvlUsd, | ||
| apyBase: userApy * 100, | ||
| underlyingTokens: [asset], | ||
| }); | ||
| } |
There was a problem hiding this comment.
Isolate per-asset failures before the chain-level allSettled.
Any rejected ABI/price call inside fetchV4ChainPools() currently rejects the whole chain fetch, and the Promise.allSettled at the end then drops every V4 pool for that chain with no signal. A single bad asset or historical call should be skipped and surfaced, not erase the chain’s output.
Suggested containment
for (const asset of assets) {
- const priceKey = `${chain}:${asset}`;
- const price = coinData[priceKey]?.price || 0;
- const decimals = coinData[priceKey]?.decimals || 18;
- const symbol = coinData[priceKey]?.symbol || asset.slice(0, 6);
+ try {
+ const priceKey = `${chain}:${asset}`;
+ const price = coinData[priceKey]?.price || 0;
+ const decimals = coinData[priceKey]?.decimals || 18;
+ const symbol = coinData[priceKey]?.symbol || asset.slice(0, 6);
- // Get active Morpho vault per user vault (for APY weighting)
- const { output: morphoResults } = await sdk.api.abi.multiCall({
- abi: 'function assetToVault(address) view returns (address)',
- calls: userVaults.map((vault) => ({ target: vault, params: [asset] })),
- chain,
- });
+ // existing per-asset logic...
+ } catch (err) {
+ console.warn(`surf-liquid v4 skipped ${chain}:${asset}`, err);
+ continue;
+ }
}Also applies to: 458-465
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adaptors/surf-liquid/index.js` around lines 79 - 181, In
fetchV4ChainPools(), avoid letting any per-asset ABI/price call reject the whole
chain: wrap the per-asset work (the multiCall blocks for morphoResults,
nowAssets/nowSupply/pastAssets/pastSupply, and tvlResults plus price lookup from
coinData) in a try/catch so a failure for one asset (e.g., errors from the calls
that produce morphoResults, nowAssets, pastAssets, morphoApyMap, or tvlResults)
logs the asset and error and continues to the next asset instead of throwing;
specifically, around the loop body that builds uniqueMorpho, computes
morphoApyMap, and computes totalTvlUsd/weightedApy, catch errors and skip
pushing the pool for that asset (do the same containment for the other instance
noted at the end of file), ensuring any thrown error does not bubble up to the
chain-level Promise.allSettled.
| const morphoApyMap = {}; | ||
| for (let i = 0; i < uniqueMorpho.length; i++) { | ||
| const aNow = Number(nowAssets.output[i].output || '0'); | ||
| const sNow = Number(nowSupply.output[i].output || '0'); | ||
| const aPast = Number(pastAssets.output[i].output || '0'); | ||
| const sPast = Number(pastSupply.output[i].output || '0'); | ||
| if (sNow <= 0 || sPast <= 0) continue; | ||
| const priceNow = aNow / sNow; | ||
| const pricePast = aPast / sPast; | ||
| const apy = pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0; | ||
| morphoApyMap[uniqueMorpho[i].toLowerCase()] = Math.max(apy, 0); |
There was a problem hiding this comment.
❓ Verification inconclusive
Script executed:
cat -n src/adaptors/surf-liquid/index.js | head -150Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
1 const sdk = require('@defillama/sdk');
2 const axios = require('axios');
3 const utils = require('../utils');
4
5 // V2/V3 Base constants
6 const CHAIN = 'base';
7 const V2_FACTORY = '0x1D283b668F947E03E8ac8ce8DA5505020434ea0E';
8 const V3_FACTORY = '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa';
9 const V3_DEPLOY_FROM_BLOCK = 38856207;
10 const SURF_STAKING = '0xB0fDFc081310A5914c2d2c97e7582F4De12FA9d6';
11 const SURF_STAKING_V2 = '0xeBa3B16E175fD36c8b01953D9e3962AB3c575718';
12 const SURF_TOKEN = '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf';
13 const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
14 const WETH = '0x4200000000000000000000000000000000000006';
15 const CBBTC = '0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf';
16 const ASSETS = [USDC, WETH, CBBTC];
17 const ASSET_SYMBOLS = {
18 [USDC]: 'USDC',
19 [WETH]: 'WETH',
20 [CBBTC]: 'cbBTC',
21 };
22 const ASSET_DECIMALS = {
23 [USDC]: 6,
24 [WETH]: 18,
25 [CBBTC]: 8,
26 };
27 const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
28
29 const PERFORMANCE_FEE = 0.1; // 10% on earned yield
30
31 // V4 constants — same addresses on all chains
32 const V4_FACTORY = '0x8fa50DeA8DB10987D7d22ac092001c3613C18779';
33 const V4_REGISTRY = '0x98A0DeF9C959Ec934Df02141291303819369f271';
34 const V4_FROM_BLOCKS = {
35 base: 43800000,
36 ethereum: 22200000,
37 arbitrum: 445000000,
38 polygon: 71000000,
39 };
40 const V4_CHAINS = ['ethereum', 'arbitrum', 'polygon', 'base'];
41
42 async function fetchV4ChainPools(chain) {
43 const fromBlock = V4_FROM_BLOCKS[chain];
44
45 // Get current and 24h-ago blocks for APY computation
46 const now = Math.floor(Date.now() / 1000);
47 const dayAgo = now - 86400;
48 const [blockNow, blockPast] = await utils.getBlocksByTime([now, dayAgo], chain);
49
50 // Enumerate user vaults
51 const vaultLogs = await sdk.getEventLogs({
52 target: V4_FACTORY,
53 fromBlock,
54 toBlock: blockNow,
55 chain,
56 eventAbi:
57 'event VaultDeployed(address indexed vaultAddress, address indexed owner, bytes32 salt)',
58 });
59 const userVaults = vaultLogs.map((l) => l.args.vaultAddress);
60 if (userVaults.length === 0) return [];
61
62 // Get allowed assets from registry
63 const { output: assets } = await sdk.api.abi.call({
64 target: V4_REGISTRY,
65 abi: 'function getAllowedAssets() view returns (address[])',
66 chain,
67 });
68 if (!assets || assets.length === 0) return [];
69
70 // Fetch prices for all assets
71 const priceKeys = assets.map((a) => `${chain}:${a}`).join(',');
72 const priceResp = await axios.get(
73 `https://coins.llama.fi/prices/current/${priceKeys}`
74 );
75 const coinData = priceResp.data?.coins || {};
76
77 const pools = [];
78
79 for (const asset of assets) {
80 const priceKey = `${chain}:${asset}`;
81 const price = coinData[priceKey]?.price || 0;
82 const decimals = coinData[priceKey]?.decimals || 18;
83 const symbol = coinData[priceKey]?.symbol || asset.slice(0, 6);
84
85 // Get active Morpho vault per user vault (for APY weighting)
86 const { output: morphoResults } = await sdk.api.abi.multiCall({
87 abi: 'function assetToVault(address) view returns (address)',
88 calls: userVaults.map((vault) => ({ target: vault, params: [asset] })),
89 chain,
90 });
91
92 const uniqueMorpho = [
93 ...new Set(
94 morphoResults
95 .map((r) => r.output)
96 .filter((v) => v && v !== ZERO_ADDR)
97 ),
98 ];
99
100 if (uniqueMorpho.length === 0) continue;
101
102 // Compute APY from Morpho vault share price changes (24h window)
103 const [nowAssets, nowSupply, pastAssets, pastSupply] = await Promise.all([
104 sdk.api.abi.multiCall({
105 abi: 'uint256:totalAssets',
106 calls: uniqueMorpho.map((t) => ({ target: t })),
107 chain,
108 block: blockNow,
109 }),
110 sdk.api.abi.multiCall({
111 abi: 'uint256:totalSupply',
112 calls: uniqueMorpho.map((t) => ({ target: t })),
113 chain,
114 block: blockNow,
115 }),
116 sdk.api.abi.multiCall({
117 abi: 'uint256:totalAssets',
118 calls: uniqueMorpho.map((t) => ({ target: t })),
119 chain,
120 block: blockPast,
121 }),
122 sdk.api.abi.multiCall({
123 abi: 'uint256:totalSupply',
124 calls: uniqueMorpho.map((t) => ({ target: t })),
125 chain,
126 block: blockPast,
127 }),
128 ]);
129
130 const morphoApyMap = {};
131 for (let i = 0; i < uniqueMorpho.length; i++) {
132 const aNow = Number(nowAssets.output[i].output || '0');
133 const sNow = Number(nowSupply.output[i].output || '0');
134 const aPast = Number(pastAssets.output[i].output || '0');
135 const sPast = Number(pastSupply.output[i].output || '0');
136 if (sNow <= 0 || sPast <= 0) continue;
137 const priceNow = aNow / sNow;
138 const pricePast = aPast / sPast;
139 const apy = pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0;
140 morphoApyMap[uniqueMorpho[i].toLowerCase()] = Math.max(apy, 0);
141 }
142
143 // Get underlying asset value per user vault (TVL)
144 const { output: tvlResults } = await sdk.api.abi.multiCall({
145 abi: 'function getAssetVaultAssets(address) view returns (uint256)',
146 calls: userVaults.map((vault) => ({ target: vault, params: [asset] })),
147 chain,
148 });
149
150 let totalTvlUsd = 0;
Script executed:
# Check what the `@defillama/sdk` multiCall returns
rg "multiCall" src/adaptors --type js -A 3 -B 1 | head -100Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/lodestar-v0/index.js- return (
src/adaptors/lodestar-v0/index.js: await sdk.api.abi.multiCall({
src/adaptors/lodestar-v0/index.js- chain: CHAIN,
src/adaptors/lodestar-v0/index.js- calls: markets.map((market) => ({
src/adaptors/lodestar-v0/index.js- target: COMPTROLLER_ADDRESS,
--
src/adaptors/lodestar-v0/index.js-
src/adaptors/lodestar-v0/index.js:const multiCallMarkets = async (markets, method, abi) => {
src/adaptors/lodestar-v0/index.js- return (
src/adaptors/lodestar-v0/index.js: await sdk.api.abi.multiCall({
src/adaptors/lodestar-v0/index.js- chain: CHAIN,
src/adaptors/lodestar-v0/index.js- calls: markets.map((market) => ({ target: market })),
src/adaptors/lodestar-v0/index.js- abi: abi.find(({ name }) => name === method),
--
src/adaptors/lodestar-v0/index.js- const markets = (
src/adaptors/lodestar-v0/index.js: await sdk.api.abi.multiCall({
src/adaptors/lodestar-v0/index.js- chain: CHAIN,
src/adaptors/lodestar-v0/index.js- abi: comptrollerAbi.find((n) => n.name === 'markets'),
src/adaptors/lodestar-v0/index.js- calls: allMarkets.map((m) => ({
--
src/adaptors/lodestar-v0/index.js-
src/adaptors/lodestar-v0/index.js: const supplyRewards = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- allMarkets,
src/adaptors/lodestar-v0/index.js- SUPPLY_RATE,
src/adaptors/lodestar-v0/index.js- ercDelegator
--
src/adaptors/lodestar-v0/index.js-
src/adaptors/lodestar-v0/index.js: const borrowRewards = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- allMarkets,
src/adaptors/lodestar-v0/index.js- BORROW_RATE,
src/adaptors/lodestar-v0/index.js- ercDelegator
--
src/adaptors/lodestar-v0/index.js-
src/adaptors/lodestar-v0/index.js: const marketsCash = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- allMarkets,
src/adaptors/lodestar-v0/index.js- GET_CHASH,
src/adaptors/lodestar-v0/index.js- ercDelegator
src/adaptors/lodestar-v0/index.js- );
src/adaptors/lodestar-v0/index.js: const totalBorrows = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- allMarkets,
src/adaptors/lodestar-v0/index.js- TOTAL_BORROWS,
src/adaptors/lodestar-v0/index.js- ercDelegator
src/adaptors/lodestar-v0/index.js- );
src/adaptors/lodestar-v0/index.js: const totalReserves = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- allMarkets,
src/adaptors/lodestar-v0/index.js- TOTAL_RESERVES,
src/adaptors/lodestar-v0/index.js- ercDelegator
--
src/adaptors/lodestar-v0/index.js-
src/adaptors/lodestar-v0/index.js: const underlyingTokens = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- allMarkets,
src/adaptors/lodestar-v0/index.js- UNDERLYING,
src/adaptors/lodestar-v0/index.js- ercDelegator
--
src/adaptors/lodestar-v0/index.js-
src/adaptors/lodestar-v0/index.js: const underlyingSymbols = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- underlyingTokens,
src/adaptors/lodestar-v0/index.js- 'symbol',
src/adaptors/lodestar-v0/index.js- ercDelegator
src/adaptors/lodestar-v0/index.js- );
src/adaptors/lodestar-v0/index.js: const underlyingDecimals = await multiCallMarkets(
src/adaptors/lodestar-v0/index.js- underlyingTokens,
src/adaptors/lodestar-v0/index.js- 'decimals',
src/adaptors/lodestar-v0/index.js- ercDelegator
--
src/adaptors/zerobase-cedefi/index.js- await Promise.all([
src/adaptors/zerobase-cedefi/index.js: sdk.api.abi.multiCall({
src/adaptors/zerobase-cedefi/index.js- chain,
src/adaptors/zerobase-cedefi/index.js- abi: vaultAbi.getTVL,
src/adaptors/zerobase-cedefi/index.js- calls: tokenAddrs.map((token) => ({ target: vault, params: [token] })),
--
src/adaptors/zerobase-cedefi/index.js- }),
src/adaptors/zerobase-cedefi/index.js: sdk.api.abi.multiCall({
src/adaptors/zerobase-cedefi/index.js- chain,
src/adaptors/zerobase-cedefi/index.js- abi: vaultAbi.getCurrentRewardRate,
src/adaptors/zerobase-cedefi/index.js- calls: tokenAddrs.map((token) => ({ target: vault, params: [token] })),
--
src/adaptors/zerobase-cedefi/index.js- }),
src/adaptors/zerobase-cedefi/index.js: sdk.api.abi.multiCall({
src/adaptors/zerobase-cedefi/index.js- chain,
src/adaptors/zerobase-cedefi/index.js- abi: 'erc20:decimals',
src/adaptors/zerobase-cedefi/index.js- calls: tokenAddrs.map((t) => ({ target: t })),
--
src/adaptors/zyberswap-amm/index.js- const poolInfo = (
src/adaptors/zyberswap-amm/index.js: await sdk.api.abi.multiCall({
src/adaptors/zyberswap-amm/index.js- calls: [...Array(Number(poolLength)).keys()].map((i) => ({
src/adaptors/zyberswap-amm/index.js- target: masterchef,
src/adaptors/zyberswap-amm/index.js- params: [i],
--
src/adaptors/zklend/index.js:const { multiCall, call } = require('../../helper/starknet');
src/adaptors/zklend/index.js-const { marketAbi, erc20Abi, irmAbi, zTokenAbi } = require('./abi');
src/adaptors/zklend/index.js-const axios = require('axios');
src/adaptors/zklend/index.js-const BN = require('bn.js');
--
src/adaptors/zircuit-finance/index.js- [
src/adaptors/zircuit-finance/index.js: sdk.api.abi.multiCall({ abi: 'erc20:symbol', calls, chain: CHAIN }),
src/adaptors/zircuit-finance/index.js: sdk.api.abi.multiCall({ abi: 'erc20:decimals', calls, chain: CHAIN }),
src/adaptors/zircuit-finance/index.js: sdk.api.abi.multiCall({
src/adaptors/zircuit-finance/index.js- abi: 'uint:totalAssets',
src/adaptors/zircuit-finance/index.js- calls,
Script executed:
# Look for bigint usage in the codebase
rg "BigInt|bigint" src/adaptors --type js | head -50Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/midas-rwa/chainlinkHelpers.js: answer: BigInt(answer),
src/adaptors/midas-rwa/chainlinkHelpers.js: answer: BigInt(answer),
src/adaptors/midas-rwa/chainlinkHelpers.js: answer: BigInt(answer),
src/adaptors/zeebu/index.js: const amount = Number(BigInt(amountHex).toString()) / 10 ** decimals;
src/adaptors/yuzu-money/index.js: params: [BigInt(token.unit)],
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:x = BigInt(x);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:startUtilization = BigInt(startUtilization);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:startRateAtTarget = BigInt(startRateAtTarget);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:elapsed = BigInt(elapsed);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:borrowRate = BigInt(borrowRate);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:rateAtTarget = BigInt(rateAtTarget);
src/adaptors/yieldoor/sharesMath.js: BigInt(totalAssets) + VIRTUAL_ASSETS,
src/adaptors/yieldoor/sharesMath.js: BigInt(totalShares) + VIRTUAL_SHARES,
src/adaptors/yieldoor/sharesMath.js: BigInt(totalShares) + VIRTUAL_SHARES,
src/adaptors/yieldoor/sharesMath.js: BigInt(totalAssets) + VIRTUAL_ASSETS,
src/adaptors/yieldoor/morphoSdk.js: totalSupplyAssets = BigInt(totalSupplyAssets);
src/adaptors/yieldoor/morphoSdk.js: totalBorrowAssets = BigInt(totalBorrowAssets);
src/adaptors/yieldoor/morphoSdk.js: borrowShares = BigInt(borrowShares);
src/adaptors/yieldoor/morphoSdk.js: market.totalBorrowShares = BigInt(market.totalBorrowShares);
src/adaptors/yieldoor/morphoSdk.js: timestamp = BigInt(timestamp);
src/adaptors/yieldoor/morphoSdk.js: const elapsed = timestamp - BigInt(market.lastUpdate);
src/adaptors/yieldoor/morphoSdk.js: timestamp = BigInt(timestamp);
src/adaptors/yieldoor/morphoSdk.js: totalSupplyAssets: BigInt(totalSupplyAssets) - feeAmount,
src/adaptors/yieldoor/mathLib.js: return BigInt(`0x${"f".repeat(nBits / 4)}`);
src/adaptors/yieldoor/mathLib.js:a = BigInt(a);
src/adaptors/yieldoor/mathLib.js:return xs.map(BigInt).reduce((x, y) => (x <= y ? x : y));
src/adaptors/yieldoor/mathLib.js:return xs.map(BigInt).reduce((x, y) => (x <= y ? y : x));
src/adaptors/yieldoor/mathLib.js:x = BigInt(x);
src/adaptors/yieldoor/mathLib.js:y = BigInt(y);
src/adaptors/yieldoor/mathLib.js:x = BigInt(x);
src/adaptors/yieldoor/mathLib.js:y = BigInt(y);
src/adaptors/yieldoor/mathLib.js:denominator = BigInt(denominator);
src/adaptors/yieldoor/mathLib.js:x = BigInt(x);
src/adaptors/yieldoor/mathLib.js:y = BigInt(y);
src/adaptors/yieldoor/mathLib.js:denominator = BigInt(denominator);
src/adaptors/yieldoor/mathLib.js:const firstTerm = BigInt(x) * BigInt(n);
src/adaptors/yieldoor/mathLib.js: return BigInt(x) * 1_000000000n;
src/adaptors/yieldoor/index.js: timestamp = BigInt(timestamp);
src/adaptors/yieldoor/index.js: // position.accrueInterest(BigInt(Math.floor(Date.now() / 1000)));
src/adaptors/yieldoor/index.js: accrueInterest(position, BigInt(Math.floor(Date.now() / 1000)));
src/adaptors/yieldoor/index.js: const ltv = +formatEther(_ltv ?? BigInt(0));
src/adaptors/yieldoor/index.js: const borrowApy = +formatEther(getBorrowApy(market, BigInt(Math.floor(Date.now() / 1000))));
src/adaptors/yieldoor/index.js: const borrowSize = +formatUnits(borrowAssets ?? BigInt(0), tokenData[loanToken].decimals);
src/adaptors/vesu/starknet-helper.js: params[0] = '0x' + BigInt(poolId).toString(16)
src/adaptors/vesu/starknet-helper.js: params[0] = '0x' + BigInt(poolId).toString(16)
src/adaptors/vesu/starknet-helper.js: const utilizationU256 = BigInt(params[2])
src/adaptors/vesu/starknet-helper.js: const rateU256 = BigInt(params[4])
src/adaptors/vesu/starknet-helper.js: '0x' + BigInt(params[3]).toString(16),
src/adaptors/vesu/starknet-helper.js: return BigInt(low) + (BigInt(high) << 128n)
src/adaptors/vesu/starknet-helper.js: last_updated: BigInt(result[13]),
Script executed:
# Check for similar share price / APY calculations
rg "totalAssets|totalSupply" src/adaptors --type js -B 2 -A 2 | head -80Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/stusdt/index.js- await sdk.api.abi.call({
src/adaptors/stusdt/index.js- chain: 'tron',
src/adaptors/stusdt/index.js: abi: 'erc20:totalSupply',
src/adaptors/stusdt/index.js- target: stUSDT,
src/adaptors/stusdt/index.js- })
--
src/adaptors/stusdt/index.js- await sdk.api.abi.call({
src/adaptors/stusdt/index.js- chain: 'ethereum',
src/adaptors/stusdt/index.js: abi: 'erc20:totalSupply',
src/adaptors/stusdt/index.js- target: stUSDTEthereum,
src/adaptors/stusdt/index.js- })
--
src/adaptors/yuzu-money/index.js- const { output } = await sdk.api.abi.call({
src/adaptors/yuzu-money/index.js- target: token.address,
src/adaptors/yuzu-money/index.js: abi: 'erc20:totalSupply',
src/adaptors/yuzu-money/index.js- chain,
src/adaptors/yuzu-money/index.js- });
--
src/adaptors/yield-yak-aggregator/index.js- sdk.api.abi.call({
src/adaptors/yield-yak-aggregator/index.js- target: vaultConfig.address,
src/adaptors/yield-yak-aggregator/index.js: abi: 'erc20:totalSupply',
src/adaptors/yield-yak-aggregator/index.js- chain: chain
src/adaptors/yield-yak-aggregator/index.js- })
src/adaptors/yield-yak-aggregator/index.js- ]);
src/adaptors/yield-yak-aggregator/index.js-
src/adaptors/yield-yak-aggregator/index.js: const totalSupply = currentSupplyWei.output / 1e18;
src/adaptors/yield-yak-aggregator/index.js-
src/adaptors/yield-yak-aggregator/index.js- // Get underlying token price
--
src/adaptors/yield-yak-aggregator/index.js- const apr7d = weeklyRate * 365 * 100;
src/adaptors/yield-yak-aggregator/index.js- const apy7d = (Math.pow(1 + weeklyRate, 365) - 1) * 100;
src/adaptors/yield-yak-aggregator/index.js: const tvlUsd = totalSupply * currentRateNormalized * underlyingPrice;
src/adaptors/yield-yak-aggregator/index.js-
src/adaptors/yield-yak-aggregator/index.js- const chainName = CHAIN_CONFIG[vaultConfig.chainId].chainName;
--
src/adaptors/wefi/index.js- if (price === undefined)
src/adaptors/wefi/index.js- price = symbol.toLowerCase().includes('usd') ? 1 : 0;
src/adaptors/wefi/index.js: const totalSupplyUsd =
src/adaptors/wefi/index.js- ((Number(marketsCash[i]) + Number(totalBorrows[i])) / 10 ** decimals) *
src/adaptors/wefi/index.js- price;
--
src/adaptors/wefi/index.js- };
src/adaptors/wefi/index.js-
src/adaptors/wefi/index.js: const apyReward = calcRewardApy(rewardSpeed, totalSupplyUsd, pool);
src/adaptors/wefi/index.js- const apyRewardBorrow = calcRewardApy(
src/adaptors/wefi/index.js- rewardSpeedBorrow,
--
src/adaptors/wefi/index.js- poolReturned = {
src/adaptors/wefi/index.js- ...poolReturned,
src/adaptors/wefi/index.js: totalSupplyUsd,
src/adaptors/wefi/index.js- totalBorrowUsd,
src/adaptors/wefi/index.js- apyBaseBorrow,
--
src/adaptors/zircuit-finance/index.js- }));
src/adaptors/zircuit-finance/index.js-
src/adaptors/zircuit-finance/index.js: const [symbols, decimals, totalAssets, rateNow, ratePrev] = await Promise.all(
src/adaptors/zircuit-finance/index.js- [
src/adaptors/zircuit-finance/index.js- sdk.api.abi.multiCall({ abi: 'erc20:symbol', calls, chain: CHAIN }),
src/adaptors/zircuit-finance/index.js- sdk.api.abi.multiCall({ abi: 'erc20:decimals', calls, chain: CHAIN }),
src/adaptors/zircuit-finance/index.js- sdk.api.abi.multiCall({
src/adaptors/zircuit-finance/index.js: abi: 'uint:totalAssets',
src/adaptors/zircuit-finance/index.js- calls,
src/adaptors/zircuit-finance/index.js- chain: CHAIN,
--
src/adaptors/zircuit-finance/index.js- const dec = Number(decimals.output[i].output);
src/adaptors/zircuit-finance/index.js- const symbol = symbols.output[i].output;
src/adaptors/zircuit-finance/index.js: const total = Number(totalAssets.output[i].output);
src/adaptors/zircuit-finance/index.js- const rNow = Number(rateNow.output[i].output);
src/adaptors/zircuit-finance/index.js- const rPrev = Number(ratePrev.output[i].output);
--
src/adaptors/zklend/index.js- apyBaseBorrow: borrowApy,
src/adaptors/zklend/index.js- underlyingTokens: [address],
src/adaptors/zklend/index.js: totalSupplyUsd: marketTokenBalanceUsd.plus(totalDebtUsd).toNumber(),
src/adaptors/zklend/index.js- totalBorrowUsd: totalDebtUsd.toNumber(),
src/adaptors/zklend/index.js- url: `https://app.zklend.com/asset/${name}`,
--
src/adaptors/zircon-gamma/index.js- .balanceOf(pool.id)
src/adaptors/zircon-gamma/index.js- .call();
src/adaptors/zircon-gamma/index.js: const totalSupply = await ptStakedContract.methods.totalSupply().call();
src/adaptors/zircon-gamma/index.js- const stakedRatio = new BigNumber(totalStaked.toString()).dividedBy(
Script executed:
# Look for Morpho-specific handling patterns
rg "Morpho|morpho" src/adaptors --type js | grep -i "totalassets\|totalsupply" -A 2 -B 2Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 1
Script executed:
# Check zircuit-finance more closely to see if same pattern exists there
cat -n src/adaptors/zircuit-finance/index.js | sed -n '1,150p'Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
1 const sdk = require('@defillama/sdk');
2 const utils = require('../utils');
3
4 const VAULTS = [
5 {
6 address: '0x03067bbD0d41E3Fe4A0bb6ca67c99e7352Da4CAE',
7 underlying: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
8 },
9 {
10 address: '0x25d90ABd6c1E8DCCD40932D2fdD2Cd381bfc832D',
11 underlying: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', // USDT
12 },
13 ];
14
15 const CHAIN = 'base';
16
17 const convertToAssetsAbi =
18 'function convertToAssets(uint256 shares) external view returns (uint256)';
19
20 const SHARE_UNIT = (10n ** 18n).toString();
21 const DAY = 24 * 3600;
22
23 async function apy() {
24 const timestamp = Math.floor(Date.now() / 1000);
25
26 const [{ height: blockNow }, { height: blockPrev }] = await Promise.all([
27 utils.getData(`https://coins.llama.fi/block/${CHAIN}/${timestamp}`),
28 utils.getData(`https://coins.llama.fi/block/${CHAIN}/${timestamp - DAY}`),
29 ]);
30
31 const addresses = VAULTS.map((v) => v.address);
32 const calls = addresses.map((target) => ({ target }));
33 const shareCalls = addresses.map((target) => ({
34 target,
35 params: [SHARE_UNIT],
36 }));
37
38 const [symbols, decimals, totalAssets, rateNow, ratePrev] = await Promise.all(
39 [
40 sdk.api.abi.multiCall({ abi: 'erc20:symbol', calls, chain: CHAIN }),
41 sdk.api.abi.multiCall({ abi: 'erc20:decimals', calls, chain: CHAIN }),
42 sdk.api.abi.multiCall({
43 abi: 'uint:totalAssets',
44 calls,
45 chain: CHAIN,
46 block: blockNow,
47 }),
48 sdk.api.abi.multiCall({
49 abi: convertToAssetsAbi,
50 calls: shareCalls,
51 chain: CHAIN,
52 block: blockNow,
53 }),
54 sdk.api.abi.multiCall({
55 abi: convertToAssetsAbi,
56 calls: shareCalls,
57 chain: CHAIN,
58 block: blockPrev,
59 }),
60 ]
61 );
62
63 const underlyings = VAULTS.map((v) => v.underlying);
64 const { pricesByAddress } = await utils.getPrices(underlyings, CHAIN);
65
66 const pools = VAULTS.map((vault, i) => {
67 const dec = Number(decimals.output[i].output);
68 const symbol = symbols.output[i].output;
69 const total = Number(totalAssets.output[i].output);
70 const rNow = Number(rateNow.output[i].output);
71 const rPrev = Number(ratePrev.output[i].output);
72 const price = pricesByAddress[vault.underlying.toLowerCase()] || 0;
73
74 const tvlUsd = (total / 10 ** dec) * price;
75
76 let apyBase = 0;
77 if (rPrev > 0 && rNow > 0) {
78 apyBase = (rNow / rPrev) ** 365 * 100 - 100;
79 }
80
81 return {
82 pool: `${vault.address}-${CHAIN}`.toLowerCase(),
83 chain: utils.formatChain(CHAIN),
84 project: 'zircuit-finance',
85 symbol: utils.formatSymbol(symbol),
86 tvlUsd,
87 apyBase,
88 underlyingTokens: [vault.underlying],
89 url: 'https://finance.zircuit.com',
90 };
91 });
92
93 return pools.filter(utils.keepFinite);
94 }
95
96 module.exports = {
97 timetravel: false,
98 apy,
99 url: 'https://finance.zircuit.com',
100 };
Script executed:
# Search for any string-to-bigint conversions for uint256 values
rg "BigInt.*output\|BigInt.*\\.output" src/adaptors --type js -B 1 -A 1 | head -60Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
Script executed:
# Check how large Morpho vaults typically get by looking at actual calls or documentation
rg "Morpho" src/adaptors/surf-liquid --type js -B 5 -A 5 | head -100Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/surf-liquid/index.js- const priceKey = `${chain}:${asset}`;
src/adaptors/surf-liquid/index.js- const price = coinData[priceKey]?.price || 0;
src/adaptors/surf-liquid/index.js- const decimals = coinData[priceKey]?.decimals || 18;
src/adaptors/surf-liquid/index.js- const symbol = coinData[priceKey]?.symbol || asset.slice(0, 6);
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js: // Get active Morpho vault per user vault (for APY weighting)
src/adaptors/surf-liquid/index.js- const { output: morphoResults } = await sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'function assetToVault(address) view returns (address)',
src/adaptors/surf-liquid/index.js- calls: userVaults.map((vault) => ({ target: vault, params: [asset] })),
src/adaptors/surf-liquid/index.js- chain,
src/adaptors/surf-liquid/index.js- });
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js: const uniqueMorpho = [
src/adaptors/surf-liquid/index.js- ...new Set(
src/adaptors/surf-liquid/index.js- morphoResults
src/adaptors/surf-liquid/index.js- .map((r) => r.output)
src/adaptors/surf-liquid/index.js- .filter((v) => v && v !== ZERO_ADDR)
src/adaptors/surf-liquid/index.js- ),
src/adaptors/surf-liquid/index.js- ];
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js: if (uniqueMorpho.length === 0) continue;
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js: // Compute APY from Morpho vault share price changes (24h window)
src/adaptors/surf-liquid/index.js- const [nowAssets, nowSupply, pastAssets, pastSupply] = await Promise.all([
src/adaptors/surf-liquid/index.js- sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'uint256:totalAssets',
src/adaptors/surf-liquid/index.js: calls: uniqueMorpho.map((t) => ({ target: t })),
src/adaptors/surf-liquid/index.js- chain,
src/adaptors/surf-liquid/index.js- block: blockNow,
src/adaptors/surf-liquid/index.js- }),
src/adaptors/surf-liquid/index.js- sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'uint256:totalSupply',
src/adaptors/surf-liquid/index.js: calls: uniqueMorpho.map((t) => ({ target: t })),
src/adaptors/surf-liquid/index.js- chain,
src/adaptors/surf-liquid/index.js- block: blockNow,
src/adaptors/surf-liquid/index.js- }),
src/adaptors/surf-liquid/index.js- sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'uint256:totalAssets',
src/adaptors/surf-liquid/index.js: calls: uniqueMorpho.map((t) => ({ target: t })),
src/adaptors/surf-liquid/index.js- chain,
src/adaptors/surf-liquid/index.js- block: blockPast,
src/adaptors/surf-liquid/index.js- }),
src/adaptors/surf-liquid/index.js- sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'uint256:totalSupply',
src/adaptors/surf-liquid/index.js: calls: uniqueMorpho.map((t) => ({ target: t })),
src/adaptors/surf-liquid/index.js- chain,
src/adaptors/surf-liquid/index.js- block: blockPast,
src/adaptors/surf-liquid/index.js- }),
src/adaptors/surf-liquid/index.js- ]);
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js- const morphoApyMap = {};
src/adaptors/surf-liquid/index.js: for (let i = 0; i < uniqueMorpho.length; i++) {
src/adaptors/surf-liquid/index.js- const aNow = Number(nowAssets.output[i].output || '0');
src/adaptors/surf-liquid/index.js- const sNow = Number(nowSupply.output[i].output || '0');
src/adaptors/surf-liquid/index.js- const aPast = Number(pastAssets.output[i].output || '0');
src/adaptors/surf-liquid/index.js- const sPast = Number(pastSupply.output[i].output || '0');
src/adaptors/surf-liquid/index.js- if (sNow <= 0 || sPast <= 0) continue;
src/adaptors/surf-liquid/index.js- const priceNow = aNow / sNow;
src/adaptors/surf-liquid/index.js- const pricePast = aPast / sPast;
src/adaptors/surf-liquid/index.js- const apy = pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0;
src/adaptors/surf-liquid/index.js: morphoApyMap[uniqueMorpho[i].toLowerCase()] = Math.max(apy, 0);
src/adaptors/surf-liquid/index.js- }
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js- // Get underlying asset value per user vault (TVL)
src/adaptors/surf-liquid/index.js- const { output: tvlResults } = await sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'function getAssetVaultAssets(address) view returns (uint256)',
--
src/adaptors/surf-liquid/index.js- eventAbi:
src/adaptors/surf-liquid/index.js- 'event VaultDeployed(address indexed vaultAddress, address indexed owner, address indexed pool, bytes32 marketId, uint256 chainId)',
src/adaptors/surf-liquid/index.js- });
src/adaptors/surf-liquid/index.js- const v3Vaults = v3Logs.map((l) => l.args.vaultAddress);
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js: // --- Step 2: Get current Morpho vaults per asset ---
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js: const { output: v2MorphoResults } = await sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'address:currentVault',
src/adaptors/surf-liquid/index.js- calls: v2Vaults.map((target) => ({ target })),
src/adaptors/surf-liquid/index.js- chain: CHAIN,
src/adaptors/surf-liquid/index.js- });
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js- const morphoToSurfVaults = {};
src/adaptors/surf-liquid/index.js: const assetToMorphoVaults = {};
src/adaptors/surf-liquid/index.js: for (const asset of ASSETS) assetToMorphoVaults[asset] = new Set();
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js- for (let i = 0; i < v2Vaults.length; i++) {
src/adaptors/surf-liquid/index.js: const morpho = v2MorphoResults[i].output;
src/adaptors/surf-liquid/index.js- if (morpho && morpho !== ZERO_ADDR) {
src/adaptors/surf-liquid/index.js- const key = morpho.toLowerCase();
src/adaptors/surf-liquid/index.js: assetToMorphoVaults[USDC].add(key);
src/adaptors/surf-liquid/index.js- if (!morphoToSurfVaults[key]) morphoToSurfVaults[key] = new Set();
src/adaptors/surf-liquid/index.js- morphoToSurfVaults[key].add(v2Vaults[i]);
src/adaptors/surf-liquid/index.js- }
src/adaptors/surf-liquid/index.js- }
src/adaptors/surf-liquid/index.js-
--
src/adaptors/surf-liquid/index.js-
src/adaptors/surf-liquid/index.js- for (let i = 0; i < v3Vaults.length; i++) {
src/adaptors/surf-liquid/index.js- const morpho = morphoResults[i].output;
src/adaptors/surf-liquid/index.js- if (morpho && morpho !== ZERO_ADDR) {
src/adaptors/surf-liquid/index.js- const key = morpho.toLowerCase();
Script executed:
# Verify that SDK multiCall returns string outputs by checking a few concrete examples
rg "\.output\[.*\]\.output" src/adaptors --type js -A 1 -B 1 | head -40Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/zircuit-finance/index.js- const pools = VAULTS.map((vault, i) => {
src/adaptors/zircuit-finance/index.js: const dec = Number(decimals.output[i].output);
src/adaptors/zircuit-finance/index.js: const symbol = symbols.output[i].output;
src/adaptors/zircuit-finance/index.js: const total = Number(totalAssets.output[i].output);
src/adaptors/zircuit-finance/index.js: const rNow = Number(rateNow.output[i].output);
src/adaptors/zircuit-finance/index.js: const rPrev = Number(ratePrev.output[i].output);
src/adaptors/zircuit-finance/index.js- const price = pricesByAddress[vault.underlying.toLowerCase()] || 0;
--
src/adaptors/zentra-finance/index.js- const price = prices[`${CHAIN}:${reserve.tokenAddress}`]?.price;
src/adaptors/zentra-finance/index.js: const dec = Number(decimals.output[i].output);
src/adaptors/zentra-finance/index.js: const p = reserveData.output[i].output;
src/adaptors/zentra-finance/index.js: const cfg = reserveConfig.output[i].output;
src/adaptors/zentra-finance/index.js-
src/adaptors/zentra-finance/index.js- const totalSupplyUsd =
src/adaptors/zentra-finance/index.js: (totalSupply.output[i].output / 10 ** dec) * price;
src/adaptors/zentra-finance/index.js- const tvlUsd =
src/adaptors/zentra-finance/index.js: (underlyingBalances.output[i].output / 10 ** dec) * price;
src/adaptors/zentra-finance/index.js-
--
src/adaptors/yieldoor/index.js- const { token0, token1 } = vaultsData[vault];
src/adaptors/yieldoor/index.js: const [b0, b1] = balances.output[i].output;
src/adaptors/yieldoor/index.js- const token0Usd = new BigNumber(b0).dividedBy(TEN.pow(vaultsData[vault].token0Decimals)).multipliedBy(prices.pricesByAddress[token0.toLowerCase()]);
--
src/adaptors/yieldoor/index.js- vaultsData[vault].totalBalanceUsd = totalBalance;
src/adaptors/yieldoor/index.js: vaultsData[vault].strategy = strategies.output[i].output;
src/adaptors/yieldoor/index.js- });
--
src/adaptors/yieldoor/index.js- Object.keys(vaultsData).forEach((vault, i) => {
src/adaptors/yieldoor/index.js: vaultsData[vault].protocolFee = protocolFees.output[i].output;
src/adaptors/yieldoor/index.js: vaultsData[vault].pool = pools.output[i].output;
src/adaptors/yieldoor/index.js- });
--
src/adaptors/yieldoor/index.js- vaults.forEach((vault, i) => {
src/adaptors/yieldoor/index.js: const [vestingAmount0, vestingAmount1] = vestingAmounts.output[i].output;
src/adaptors/yieldoor/index.js- const token0Usd = new BigNumber(vestingAmount0).dividedBy(TEN.pow(vaultsData[vault].token0Decimals)).times(prices.pricesByAddress[vaultsData[vault].token0.toLowerCase()]);
--
src/adaptors/yieldoor/index.js- vaultsData[vault] = {
src/adaptors/yieldoor/index.js: token0: token0s.output[i].output,
src/adaptors/yieldoor/index.js: token1: token1s.output[i].output
src/adaptors/yieldoor/index.js- }
Script executed:
# Check if there are any explicit conversions or handling of large numbers in similar calculations
rg "Number\(.*\.output.*\)" src/adaptors --type js | head -30Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/bracket-vaults/index.js: return getTokenPrice(priceKey, Number(tvl.output), vaultDecimals);
src/adaptors/orbitalswap/index.js: calls: [...Array(Number(poolLength.output)).keys()].map((i) => ({
src/adaptors/orbitalswap/index.js: calls: [...Array(Number(poolLength.output)).keys()].map((i) => ({
src/adaptors/zircuit-finance/index.js: const dec = Number(decimals.output[i].output);
src/adaptors/zircuit-finance/index.js: const total = Number(totalAssets.output[i].output);
src/adaptors/zircuit-finance/index.js: const rNow = Number(rateNow.output[i].output);
src/adaptors/zircuit-finance/index.js: const rPrev = Number(ratePrev.output[i].output);
src/adaptors/zkswap-v2/index.js: const zfDAOPerSecond = Number(zfDAOPerSecondRes.output) / 1e18;
src/adaptors/zkswap-v2/index.js: const pendingZf = Number(pendingZfRes.output) / 1e18;
src/adaptors/zkswap-v2/index.js: const currentGovTvl = Number(currentGovTvlRes.output) / 1e18;
src/adaptors/zentra-finance/index.js: const dec = Number(decimals.output[i].output);
src/adaptors/yodeswap/index.js: calls: [...Array(Number(poolLength.output)).keys()].map((i) => ({
src/adaptors/yieldoor/index.js: decimals: Number(decimalsResults[index].output ?? 18),
src/adaptors/venus-core-pool/index.js: ).output.map((decimal) => Math.pow(10, Number(decimal.output)));
src/adaptors/yel-finance/index.js: const adjustedBaseAssetOnPotion = formatNumber(baseAssetOnPotion.output, baseAssetDecimals.output)
src/adaptors/yel-finance/index.js: const adjustedTotalSupplyPotion = formatNumber(totalSupplyPotion.output, decimalsPotion.output)
src/adaptors/veda/index.js: return Number(result.output);
src/adaptors/veda/index.js: return Number(result.output) / Math.pow(10, vault.decimals);
src/adaptors/veda/index.js: return Number(result.output) / Math.pow(10, vault.decimals);
src/adaptors/vaneck-treasury-fund/index.js: const decimals = Number(decimalsResults[i].output);
src/adaptors/vaneck-treasury-fund/index.js: const supply = Number(supplyResults[i].output) / 10 ** decimals;
src/adaptors/teller/index.js: decimals: Number(tokenInfo.output.decimals) || 18,
src/adaptors/uniswap-v3/onchain.js: ).output.map((o) => [o.input.target, Number(o.output)])
src/adaptors/uniswap-v3/onchain.js: pools[i].balance0 = BigNumber(token0Balances.output[i].output);
src/adaptors/uniswap-v3/onchain.js: pools[i].balance1 = BigNumber(token1Balances.output[i].output);
src/adaptors/steakhouse-financial/index.js: const total = Number(totalAssets.output[i]?.output || 0);
src/adaptors/steakhouse-financial/index.js: decMap[a.toLowerCase()] = Number(decResults.output[i]?.output || 18);
src/adaptors/steakhouse-financial/index.js: const current = Number(currentRates.output[i]?.output || 0);
src/adaptors/steakhouse-financial/index.js: const past = Number(pastRates.output[i]?.output || 0);
src/adaptors/travessia-credit/index.js: return loans.output.map(loan => Number(loan.output[8]));
Script executed:
# Check Morpho v4 documentation/contracts to understand typical vault sizes
rg "Morpho" src/adaptors/surf-liquid --type js -C 10 | grep -i "deploy\|vault\|tvl" | head -30Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/surf-liquid/index.js: // Get active Morpho vault per user vault (for APY weighting)
src/adaptors/surf-liquid/index.js- abi: 'function assetToVault(address) view returns (address)',
src/adaptors/surf-liquid/index.js- calls: userVaults.map((vault) => ({ target: vault, params: [asset] })),
src/adaptors/surf-liquid/index.js: // Compute APY from Morpho vault share price changes (24h window)
src/adaptors/surf-liquid/index.js- // Get underlying asset value per user vault (TVL)
src/adaptors/surf-liquid/index.js- const { output: tvlResults } = await sdk.api.abi.multiCall({
src/adaptors/surf-liquid/index.js- abi: 'function getAssetVaultAssets(address) view returns (uint256)',
src/adaptors/surf-liquid/index.js- calls: userVaults.map((vault) => ({ target: vault, params: [asset] })),
src/adaptors/surf-liquid/index.js- let totalTvlUsd = 0;
src/adaptors/surf-liquid/index.js- fromBlock: V3_DEPLOY_FROM_BLOCK,
src/adaptors/surf-liquid/index.js- 'event VaultDeployed(address indexed vaultAddress, address indexed owner, address indexed pool, bytes32 marketId, uint256 chainId)',
src/adaptors/surf-liquid/index.js- const v3Vaults = v3Logs.map((l) => l.args.vaultAddress);
src/adaptors/surf-liquid/index.js: // --- Step 2: Get current Morpho vaults per asset ---
src/adaptors/surf-liquid/index.js- abi: 'address:currentVault',
src/adaptors/surf-liquid/index.js- calls: v2Vaults.map((target) => ({ target })),
src/adaptors/surf-liquid/index.js- const morphoToSurfVaults = {};
src/adaptors/surf-liquid/index.js: const assetToMorphoVaults = {};
src/adaptors/surf-liquid/index.js: for (const asset of ASSETS) assetToMorphoVaults[asset] = new Set();
src/adaptors/surf-liquid/index.js- for (let i = 0; i < v2Vaults.length; i++) {
src/adaptors/surf-liquid/index.js: assetToMorphoVaults[USDC].add(key);
src/adaptors/surf-liquid/index.js- if (!morphoToSurfVaults[key]) morphoToSurfVaults[key] = new Set();
src/adaptors/surf-liquid/index.js- morphoToSurfVaults[key].add(v2Vaults[i]);
src/adaptors/surf-liquid/index.js- if (v3Vaults.length === 0) continue;
src/adaptors/surf-liquid/index.js- abi: 'function assetToVault(address) view returns (address)',
src/adaptors/surf-liquid/index.js- calls: v3Vaults.map((vault) => ({ target: vault, params: [asset] })),
src/adaptors/surf-liquid/index.js- for (let i = 0; i < v3Vaults.length; i++) {
src/adaptors/surf-liquid/index.js: assetToMorphoVaults[asset].add(key);
src/adaptors/surf-liquid/index.js- if (!morphoToSurfVaults[key]) morphoToSurfVaults[key] = new Set();
src/adaptors/surf-liquid/index.js- morphoToSurfVaults[key].add(v3Vaults[i]);
src/adaptors/surf-liquid/index.js: // --- Step 3: Get balances (shares held in Morpho vaults) ---
Script executed:
# Calculate the boundary where precision loss becomes significant
python3 << 'EOF'
import math
# JS Number safe integer limit
safe_int = 2**53 - 1
print(f"JS safe integer limit: {safe_int:,} ({safe_int:.2e})")
# Typical vault sizes with different token decimals
vault_sizes = {
"USDC (6 decimals)": 1e6 * 1e6, # $1M
"USDC (6 decimals)": 1e9 * 1e6, # $1B
"WETH (18 decimals)": 1e6 * 1e18, # $1M worth
"WETH (18 decimals)": 1e9 * 1e18, # $1B worth
}
for desc, val in vault_sizes.items():
if val > safe_int:
# Calculate precision loss
precision_loss = val - (val - (val % (2**32))) # rough estimate
print(f"{desc}: {val:.2e} - EXCEEDS SAFE INT by {val - safe_int:,}")
else:
print(f"{desc}: {val:.2e} - within safe int")
# For APY calculation: what precision loss means
print("\n--- APY Impact ---")
# Share price of 1.5 (50% gain)
price_now = 1.5
price_past = 1.0
ratio = price_now / price_past
apy = (ratio ** 365 - 1) * 100
print(f"Simple ratio: {ratio}")
print(f"365-day APY: {apy:.4f}%")
# If precision is lost at 1e15 level
ratio_precision_lost = (1.5000000000000001) / 1.0 # microscopic difference
apy_precision_lost = (ratio_precision_lost ** 365 - 1) * 100
print(f"With precision loss: {apy_precision_lost:.4f}%")
print(f"Difference: {abs(apy - apy_precision_lost):.8f}%")
EOFRepository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
JS safe integer limit: 9,007,199,254,740,991 (9.01e+15)
USDC (6 decimals): 1.00e+15 - within safe int
WETH (18 decimals): 1.00e+27 - EXCEEDS SAFE INT by 9.999999999909928e+26
--- APY Impact ---
Simple ratio: 1.5
365-day APY: 1876331438326366337974321168394264304254480485746188385389020971008.0000%
With precision loss: 1876331438326366337974321168394264304254480485746188385389020971008.0000%
Difference: 0.00000000%
Script executed:
# Look at the actual Morpho protocol - are vaults typically in billions?
rg "morpho\|Morpho" src/adaptors/surf-liquid/index.js -i | head -20Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
Script executed:
# Fix the calculation - compute ratio precision properly
python3 << 'EOF'
# JS Number safe integer limit
safe_int = 2**53 - 1
print(f"JS safe integer limit: {safe_int:,}\n")
# Test actual vault size scenarios
test_cases = [
("$1B USDC (6 decimals)", 1e9 * 1e6),
("$100M USDC (6 decimals)", 1e8 * 1e6),
("$1M WETH (18 decimals)", 1e6 * 1e18),
("$100K WETH (18 decimals)", 1e5 * 1e18),
]
print("Vault Size Analysis:")
for desc, raw_value in test_cases:
exceeds = raw_value > safe_int
status = "⚠️ EXCEEDS SAFE INT" if exceeds else "✓ Safe"
print(f"{desc:.<40} {raw_value:.2e} {status}")
print("\n--- Share Price Ratio Impact ---")
# For Morpho: typical share price changes over 24h are small (1-5% APY range)
# So ratio would be like 1.00001 to 1.0001 at most
# Scenario: vault with 1M WETH (~$40B at current prices)
large_vault = 1e6 * 1e18
print(f"Large vault value: {large_vault:.2e}")
# If precision is lost at this scale, what's the impact on ratio?
# The issue: when dividing two large numbers, precision in the division result
ratio_now = 1.000001 # 0.0001% change in 24h (very small)
ratio_past = 1.0
# Annualized: (1.000001)^365 - 1
import math
apy_exact = (ratio_now ** 365 - 1)
print(f"24h ratio: {ratio_now}, Annualized APY: {apy_exact*100:.6f}%")
# But if vault size causes precision loss during division...
# The real issue is: does Number(stringValue) with large uint256 lose bits?
print("\n--- Number Conversion Precision ---")
# In JavaScript, very large numbers lose precision
test_nums = [
(1e15, "1e15"),
(1e16, "1e16"),
(1e24, "1e24"),
]
for num, label in test_nums:
# Can we represent it exactly?
if num == int(num):
print(f"{label}: exact int representation")
else:
print(f"{label}: NOT exact - loses precision")
EOFRepository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
JS safe integer limit: 9,007,199,254,740,991
Vault Size Analysis:
$1B USDC (6 decimals)................... 1.00e+15 ✓ Safe
$100M USDC (6 decimals)................. 1.00e+14 ✓ Safe
$1M WETH (18 decimals).................. 1.00e+24 ⚠️ EXCEEDS SAFE INT
$100K WETH (18 decimals)................ 1.00e+23 ⚠️ EXCEEDS SAFE INT
--- Share Price Ratio Impact ---
Large vault value: 1.00e+24
24h ratio: 1.000001, Annualized APY: 0.036507%
--- Number Conversion Precision ---
1e15: exact int representation
1e16: exact int representation
1e24: exact int representation
Script executed:
# Check if the SDK returns strings or numbers from multiCall
grep -r "multiCall.*totalAssets\|multiCall.*totalSupply" src/adaptors -A 10 | grep -E "output.*output|Number\(.*output" | head -20Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/gauntlet/index.js- const calc = feeCalc.output[i]?.output;
src/adaptors/gauntlet/index.js- const supply = totalSupply.output[i]?.output;
src/adaptors/steakhouse-financial/index.js- const total = Number(totalAssets.output[i]?.output || 0);
src/adaptors/steakhouse-financial/index.js- const asset = assets.output[i]?.output;
src/adaptors/steakhouse-financial/index.js- const name = names.output[i]?.output;
src/adaptors/travessia-credit/index.js- calls: vaults.map((vault, i) => ({ target: vault, params: [supplies.output[i].output || 0] })),
src/adaptors/upshift/index.js- : [{ output: [] }, { output: [] }, { output: [] }, { output: [] }];
src/adaptors/upshift/index.js- symbol: symbolsRes.output[i].output,
Script executed:
# Search for any existing large number handling patterns in yield calculations
rg "convertToAssets|convertToShares|toFixed|toPrecision|BigInt" src/adaptors --type js | grep -i "apy\|yield\|rate" | head -20Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
src/adaptors/yieldfi/index.js: return parseFloat(latestAPY.toFixed(2));
src/adaptors/yieldfi/index.js: return parseFloat(tvlUsd.toFixed(2));
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:x = BigInt(x);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:startUtilization = BigInt(startUtilization);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:startRateAtTarget = BigInt(startRateAtTarget);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:elapsed = BigInt(elapsed);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:borrowRate = BigInt(borrowRate);
src/adaptors/yieldoor/adaptiveCurveIrmLib.js:rateAtTarget = BigInt(rateAtTarget);
src/adaptors/yieldoor/mathLib.js: return BigInt(`0x${"f".repeat(nBits / 4)}`);
src/adaptors/yieldoor/mathLib.js:a = BigInt(a);
src/adaptors/yieldoor/mathLib.js:return xs.map(BigInt).reduce((x, y) => (x <= y ? x : y));
src/adaptors/yieldoor/mathLib.js:return xs.map(BigInt).reduce((x, y) => (x <= y ? y : x));
src/adaptors/yieldoor/mathLib.js:x = BigInt(x);
src/adaptors/yieldoor/mathLib.js:y = BigInt(y);
src/adaptors/yieldoor/mathLib.js:x = BigInt(x);
src/adaptors/yieldoor/mathLib.js:y = BigInt(y);
src/adaptors/yieldoor/mathLib.js:denominator = BigInt(denominator);
src/adaptors/yieldoor/mathLib.js:x = BigInt(x);
src/adaptors/yieldoor/mathLib.js:y = BigInt(y);
src/adaptors/yieldoor/mathLib.js:denominator = BigInt(denominator);
Script executed:
# Check if Morpho v4 vaults are expected to be large enough to hit precision issues
# Look at actual test data or vault initialization
rg "1e18\|1e24\|BigInt\|Safe" src/adaptors/surf-liquid --type js | head -15Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
Script executed:
# Verify the SDK output type by looking at how it's used in other places
rg "\.output\[.*\]\.output.*Number" src/adaptors --type js -B 2 -A 2 | head -50Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
Script executed:
# Create a concrete test to show precision loss
python3 << 'EOF'
# Simulate what happens with Number conversion on large uint256 values
import json
# Example: 1M WETH as uint256
large_weth = 1000000000000000000000000 # 1M WETH in wei
# Simulate SDK returning it as string, then Number() converting it
sdk_string = str(large_weth)
print(f"SDK returns (as string): {sdk_string}")
# When JavaScript converts this with Number(), it becomes a float
# which loses precision beyond 2^53-1
import math
safe_int = 2**53 - 1
print(f"Safe integer limit: {safe_int}")
print(f"Large value > safe int: {large_weth > safe_int}")
# The precision loss happens in the conversion and subsequent division
# Let's show the actual impact on APY calculation
aNow_large = 1000000000000000000000000
sNow_large = 1000000000000000000001000 # slightly different
# With Number() - JS can't distinguish these precisely
ratio_js_float = aNow_large / sNow_large
print(f"\nShare price ratio (imprecise): {ratio_js_float}")
# What we really want (using arbitrary precision)
from decimal import Decimal
aNow_decimal = Decimal(aNow_large)
sNow_decimal = Decimal(sNow_large)
ratio_precise = aNow_decimal / sNow_decimal
print(f"Share price ratio (precise): {ratio_precise}")
# The difference in APY
apy_js = (ratio_js_float ** 365 - 1) * 100
apy_precise = float(ratio_precise ** 365 - 1) * 100
print(f"\nAPY (JS float): {apy_js:.10f}%")
print(f"APY (precise): {apy_precise:.10f}%")
print(f"Difference: {abs(apy_js - apy_precise):.10f}%")
EOFRepository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
SDK returns (as string): 1000000000000000000000000
Safe integer limit: 9007199254740991
Large value > safe int: True
Share price ratio (imprecise): 1.0
Share price ratio (precise): 0.9999999999999999999990000000
APY (JS float): 0.0000000000%
APY (precise): -0.0000000000%
Difference: 0.0000000000%
Script executed:
# Final check: Look at yieldoor's approach to Morpho math since it already handles this
cat -n src/adaptors/yieldoor/sharesMath.js | head -80Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
1 const MathLib = require('./mathLib');
2
3 /**
4 * JS implementation of {`@link` https://github.com/morpho-org/morpho-blue/blob/main/src/libraries/SharesMathLib.sol SharesMathLib} used by Morpho Blue
5 * & MetaMorpho (via {`@link` https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol ERC4626}).
6 */
7
8 const VIRTUAL_SHARES = 1000000n;
9 const VIRTUAL_ASSETS = 1n;
10
11 const toAssets = (
12 shares,
13 totalAssets,
14 totalShares,
15 rounding,
16 ) => {
17 return MathLib.mulDiv(
18 shares,
19 BigInt(totalAssets) + VIRTUAL_ASSETS,
20 BigInt(totalShares) + VIRTUAL_SHARES,
21 rounding,
22 );
23 }
24
25 const toShares = (
26 assets,
27 totalAssets,
28 totalShares,
29 rounding,
30 ) => {
31 return MathLib.mulDiv(
32 assets,
33 BigInt(totalShares) + VIRTUAL_SHARES,
34 BigInt(totalAssets) + VIRTUAL_ASSETS,
35 rounding,
36 );
37 }
38
39 module.exports = {
40 toAssets,
41 toShares,
42 };
Script executed:
# Check if there's any documentation or comments about precision in Morpho calculations
rg "precision\|BigInt\|uint256" src/adaptors/yieldoor --type js -B 2 -A 2 | head -60Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
Script executed:
# Confirm SDK multiCall output format by checking a direct usage pattern
cat -n src/adaptors/yieldoor/index.js | grep -A 5 "multiCall" | head -40Repository: DefiLlama/yield-server
Repository: DefiLlama/yield-server
Exit code: 0
stdout:
183 sdk.api.abi.multiCall({
184 calls: vaultsAddresses.map(addr => ({ target: addr })),
185 abi: abis.balances,
186 chain
187 }),
188 sdk.api.abi.multiCall({
189 calls: vaultsAddresses.map(addr => ({ target: addr })),
190 abi: 'address:strategy',
191 chain
192 })
193 ])
--
207 sdk.api.abi.multiCall({
208 calls: strategyAddresses.map(addr => ({ target: addr })),
209 abi: 'uint256:protocolFee',
210 chain
211 }),
212 sdk.api.abi.multiCall({
213 calls: strategyAddresses.map(addr => ({ target: addr })),
214 abi: 'address:pool',
215 chain
216 })
217 ]);
--
228 sdk.api.abi.multiCall({
229 calls: vaults.map(vault => ({ target: vaultsData[vault].strategy })),
230 abi: abis.getVestingAmounts,
231 chain
232 })
233 ]);
--
257 sdk.api.abi.multiCall({
258 calls: vaults.map(addr => ({ target: addr })),
259 abi: 'address:token0',
260 chain
261 }),
262 sdk.api.abi.multiCall({
263 calls: vaults.map(addr => ({ target: addr })),
264 abi: 'address:token1',
265 chain
Use BigInt for share-price math, not Number.
totalAssets and totalSupply are uint256 values that can exceed JavaScript's safe integer limit (2^53-1). Converting them to Number before dividing loses precision, which compounds through the annualization step. The yieldoor adaptor already demonstrates the correct pattern: use BigInt for the division, then convert only the final normalized rate to Number. Apply the same approach here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adaptors/surf-liquid/index.js` around lines 130 - 140, The loop building
morphoApyMap currently converts uint256 outputs to Number (aNow, sNow, aPast,
sPast) which loses precision; change to parse outputs as BigInt for nowAssets,
nowSupply, pastAssets, pastSupply, compute share prices using integer
fixed-point math (e.g. multiply numerator by a constant SCALE BigInt before
dividing to get a scaled priceNow and pricePast), then compute the growth ratio
and annualize using floating math only after converting the normalized ratio to
Number; update references to aNow/sNow/aPast/sPast, priceNow/pricePast and apy
so that BigInt math is used until the final Number conversion when assigning
morphoApyMap[uniqueMorpho[i].toLowerCase()] = Math.max(apy, 0).
Summary
Extends surf-liquid yield adapter with V4 pools across 4 chains.
V4 contracts (same addresses on all chains):
0x8fa50DeA8DB10987D7d22ac092001c3613C187790x98A0DeF9C959Ec934Df02141291303819369f271V4 yield logic per chain:
VaultDeployedevents on UserVaultFactoryVaultRegistry.getAllowedAssets()(dynamic — no hardcoded token list)getAssetVaultAssets(asset)— exact underlying regardless of which Morpho vault holds fundsPool IDs:
surf-v4-{asset_address}-{chain}(distinct from V2/V3surf-{asset}-base)Test output:
Ethereum/Arbitrum V4 pools below $100 TVL threshold (just launched, no real user deposits yet).
Summary by CodeRabbit