Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 210 additions & 54 deletions src/adaptors/surf-liquid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ const sdk = require('@defillama/sdk');
const axios = require('axios');
const utils = require('../utils');

// V2/V3 Base constants
const CHAIN = 'base';

const V2_FACTORY = '0x1D283b668F947E03E8ac8ce8DA5505020434ea0E';
const V3_FACTORY = '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa';
const V3_DEPLOY_FROM_BLOCK = 38856207;
const SURF_STAKING = '0xB0fDFc081310A5914c2d2c97e7582F4De12FA9d6';
const SURF_STAKING_V2 = '0xeBa3B16E175fD36c8b01953D9e3962AB3c575718';
const SURF_TOKEN = '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf';
const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
const WETH = '0x4200000000000000000000000000000000000006';
Expand All @@ -27,10 +28,168 @@ const ZERO_ADDR = '0x0000000000000000000000000000000000000000';

const PERFORMANCE_FEE = 0.1; // 10% on earned yield

// V4 constants — same addresses on all chains
const V4_FACTORY = '0x8fa50DeA8DB10987D7d22ac092001c3613C18779';
const V4_REGISTRY = '0x98A0DeF9C959Ec934Df02141291303819369f271';
const V4_FROM_BLOCKS = {
base: 43800000,
ethereum: 22200000,
arbitrum: 445000000,
polygon: 71000000,
};
const V4_CHAINS = ['ethereum', 'arbitrum', 'polygon', 'base'];

async function fetchV4ChainPools(chain) {
const fromBlock = V4_FROM_BLOCKS[chain];

// Get current and 24h-ago blocks for APY computation
const now = Math.floor(Date.now() / 1000);
const dayAgo = now - 86400;
const [blockNow, blockPast] = await utils.getBlocksByTime([now, dayAgo], chain);

// Enumerate user vaults
const vaultLogs = await sdk.getEventLogs({
target: V4_FACTORY,
fromBlock,
toBlock: blockNow,
chain,
eventAbi:
'event VaultDeployed(address indexed vaultAddress, address indexed owner, bytes32 salt)',
});
const userVaults = vaultLogs.map((l) => l.args.vaultAddress);
if (userVaults.length === 0) return [];

// Get allowed assets from registry
const { output: assets } = await sdk.api.abi.call({
target: V4_REGISTRY,
abi: 'function getAllowedAssets() view returns (address[])',
chain,
});
if (!assets || assets.length === 0) return [];

// Fetch prices for all assets
const priceKeys = assets.map((a) => `${chain}:${a}`).join(',');
const priceResp = await axios.get(
`https://coins.llama.fi/prices/current/${priceKeys}`
);
const coinData = priceResp.data?.coins || {};

const pools = [];

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);
Comment on lines +130 to +140
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

❓ Verification inconclusive

Script executed:

cat -n src/adaptors/surf-liquid/index.js | head -150

Repository: 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 -100

Repository: 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 -50

Repository: 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 -80

Repository: 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 2

Repository: 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 -60

Repository: 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 -100

Repository: 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 -40

Repository: 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 -30

Repository: 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 -30

Repository: 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}%")
EOF

Repository: 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 -20

Repository: 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")
EOF

Repository: 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 -20

Repository: 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 -20

Repository: 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 -15

Repository: 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 -50

Repository: 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}%")
EOF

Repository: 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 -80

Repository: 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 -60

Repository: 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 -40

Repository: 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).

}

// 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],
});
}
Comment on lines +79 to +181
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.


return pools;
}

const apy = async () => {
// =====================================================================
// V2/V3 Base pools
// =====================================================================

// --- Step 1: Discover Surf Liquid vault addresses ---

// V2 vaults from factory
const { output: totalV2 } = await sdk.api.abi.call({
target: V2_FACTORY,
abi: 'uint256:getTotalVaults',
Expand All @@ -47,7 +206,6 @@ const apy = async () => {
});
const v2Vaults = v2Infos.map((info) => info.output[0]);

// V3 vaults from factory deploy events
const currentBlock = (await sdk.api.util.getLatestBlock(CHAIN)).number;
const v3Logs = await sdk.getEventLogs({
target: V3_FACTORY,
Expand All @@ -61,7 +219,6 @@ const apy = async () => {

// --- Step 2: Get current Morpho vaults per asset ---

// V2 vaults -> currentVault() (USDC only)
const { output: v2MorphoResults } = await sdk.api.abi.multiCall({
abi: 'address:currentVault',
calls: v2Vaults.map((target) => ({ target })),
Expand Down Expand Up @@ -202,7 +359,7 @@ const apy = async () => {
prices[asset] = priceResp.data?.coins?.[key]?.price || 0;
}

// --- Step 6: Build yield pools per asset ---
// --- Step 6: Build V2/V3 yield pools per asset ---

const pools = [];

Expand Down Expand Up @@ -248,65 +405,64 @@ const apy = async () => {
});
}

// --- Step 7: SURF Staking pool (APR from on-chain) ---

const [
{ output: totalStaked },
{ output: apr6M },
{ output: apr12M },
{ output: basisPoints },
] = await Promise.all([
sdk.api.abi.call({
target: SURF_STAKING,
abi: 'uint256:totalStaked',
chain: CHAIN,
}),
sdk.api.abi.call({
target: SURF_STAKING,
abi: 'uint256:apr6Months',
chain: CHAIN,
}),
sdk.api.abi.call({
target: SURF_STAKING,
abi: 'uint256:apr12Months',
chain: CHAIN,
}),
sdk.api.abi.call({
target: SURF_STAKING,
abi: 'uint256:BASIS_POINTS',
chain: CHAIN,
}),
]);

const bp = Number(basisPoints);
const stakingApr6M = bp > 0 ? (Number(apr6M) / bp) * 100 : null;
const stakingApr12M = bp > 0 ? (Number(apr12M) / bp) * 100 : null;
// --- Step 7: SURF Staking pools (v1 + v2) ---

const surfPriceKey = `${CHAIN}:${SURF_TOKEN}`;
const surfPriceResp = await axios.get(
`https://coins.llama.fi/prices/current/${surfPriceKey}?searchWidth=24h`
);
const surfPrice = surfPriceResp.data?.coins?.[surfPriceKey]?.price || 0;
const stakingTvl = (Number(totalStaked) / 1e18) * surfPrice;

if (stakingTvl > 100) {
const stakingPool = {
pool: `${SURF_STAKING.toLowerCase()}-${CHAIN}`,
chain: utils.formatChain(CHAIN),
project: 'surf-liquid',
symbol: 'SURF',
tvlUsd: stakingTvl,
apyBase: 0,
underlyingTokens: [SURF_TOKEN],
};
for (const stakingAddr of [SURF_STAKING, SURF_STAKING_V2]) {
const [
{ output: totalStaked },
{ output: apr6M },
{ output: apr12M },
{ output: basisPoints },
] = await Promise.all([
sdk.api.abi.call({ target: stakingAddr, abi: 'uint256:totalStaked', chain: CHAIN }),
sdk.api.abi.call({ target: stakingAddr, abi: 'uint256:apr6Months', chain: CHAIN }),
sdk.api.abi.call({ target: stakingAddr, abi: 'uint256:apr12Months', chain: CHAIN }),
sdk.api.abi.call({ target: stakingAddr, abi: 'uint256:BASIS_POINTS', chain: CHAIN }),
]);

const bp = Number(basisPoints);
const stakingApr6M = bp > 0 ? (Number(apr6M) / bp) * 100 : null;
const stakingTvl = (Number(totalStaked) / 1e18) * surfPrice;

if (stakingTvl > 100) {
const stakingPool = {
pool: `${stakingAddr.toLowerCase()}-${CHAIN}`,
chain: utils.formatChain(CHAIN),
project: 'surf-liquid',
symbol: 'SURF',
tvlUsd: stakingTvl,
apyBase: 0,
underlyingTokens: [SURF_TOKEN],
};

if (stakingApr6M != null && stakingApr6M > 0) {
stakingPool.apyReward = stakingApr6M;
stakingPool.rewardTokens = [SURF_TOKEN];
stakingPool.poolMeta = '6M / 12M lock';
}

if (stakingApr6M != null && stakingApr6M > 0) {
stakingPool.apyReward = stakingApr6M;
stakingPool.rewardTokens = [SURF_TOKEN];
stakingPool.poolMeta = '6M / 12M lock';
pools.push(stakingPool);
}
}

pools.push(stakingPool);
// =====================================================================
// V4 pools — all chains (Base, Ethereum, Arbitrum, Polygon)
// =====================================================================

const v4Results = await Promise.allSettled(
V4_CHAINS.map((chain) => fetchV4ChainPools(chain))
);

for (const result of v4Results) {
if (result.status === 'fulfilled') {
pools.push(...result.value);
}
}

return pools;
Expand Down
Loading