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
70 changes: 55 additions & 15 deletions projects/surf-liquid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,17 @@ const ASSETS = [USDC, WETH, CBBTC];
const ZERO_ADDR = "0x0000000000000000000000000000000000000000";

async function tvl(api) {
const tokensAndOwners = [];
// --- Discover vault addresses ---

// V2 Vaults: enumerate via factory, read Morpho ERC-4626 positions
// V2 vaults from factory
const totalV2 = await api.call({ abi: "uint256:getTotalVaults", target: V2_FACTORY });
const v2Infos = await api.multiCall({
abi: "function getVaultInfo(uint256) view returns (address, address, address, uint256, bytes32, uint256)",
calls: [...Array(Number(totalV2)).keys()].map((i) => ({ target: V2_FACTORY, params: [i] })),
});
const v2Owners = v2Infos.map((info) => info[0]);
const v2MorphoVaults = await api.multiCall({
abi: "address:currentVault",
calls: v2Owners.map((target) => ({ target })),
});
for (let i = 0; i < v2Owners.length; i++) {
if (v2Owners[i] && v2Owners[i] !== ZERO_ADDR && v2MorphoVaults[i] && v2MorphoVaults[i] !== ZERO_ADDR) {
tokensAndOwners.push([v2MorphoVaults[i], v2Owners[i]]);
}
}
const v2Vaults = v2Infos.map((info) => info[0]).filter((a) => a && a !== ZERO_ADDR);

// V3 Vaults: discover via event logs, read Morpho ERC-4626 positions per asset
// V3 vaults from factory events
const currentBlock = api.block || await api.getBlock();
const v3Logs = await api.getLogs({
target: V3_FACTORY,
Expand All @@ -39,19 +30,68 @@ async function tvl(api) {
});
const v3Vaults = v3Logs.map((l) => l.vaultAddress);

// --- Build (surfVault, morphoVault, asset) allocations ---

const allocations = []; // { surfVault, morphoVault, asset }

// V2: currentVault() → USDC only
const v2MorphoVaults = await api.multiCall({
abi: "address:currentVault",
calls: v2Vaults.map((target) => ({ target })),
});
for (let i = 0; i < v2Vaults.length; i++) {
if (v2MorphoVaults[i] && v2MorphoVaults[i] !== ZERO_ADDR) {
allocations.push({ surfVault: v2Vaults[i], morphoVault: v2MorphoVaults[i], asset: USDC });
}
}

// V3: assetToVault(asset) for each asset
for (const asset of ASSETS) {
if (v3Vaults.length === 0) continue;
const morphoVaults = await api.multiCall({
abi: "function assetToVault(address) view returns (address)",
calls: v3Vaults.map((vault) => ({ target: vault, params: [asset] })),
});
for (let i = 0; i < v3Vaults.length; i++) {
if (morphoVaults[i] && morphoVaults[i] !== ZERO_ADDR) {
tokensAndOwners.push([morphoVaults[i], v3Vaults[i]]);
allocations.push({ surfVault: v3Vaults[i], morphoVault: morphoVaults[i], asset });
}
}
}
Comment on lines +33 to 60
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 | 🔴 Critical

Missing direct underlying token balance tracking as stated in PR objectives.

The PR objectives explicitly state:

  • V2 vaults: add [USDC, v2Vault] to track direct USDC balances
  • V3 vaults: add [asset, v3Vault] for each asset to track direct token balances during idle periods

However, the current implementation only builds Morpho vault allocations. To capture idle tokens sitting directly in Surf vault contracts (when funds aren't deployed to Morpho), you need to also call api.sumTokens with the Surf vault addresses as owners of the underlying assets.

🐛 Proposed fix to add idle token balance tracking

After the allocations loop (around line 60), add:

+  // --- Track idle underlying tokens held directly by Surf vaults ---
+  const tokensAndOwners = [];
+  
+  // V2 vaults hold USDC when idle
+  for (const v2Vault of v2Vaults) {
+    tokensAndOwners.push([USDC, v2Vault]);
+  }
+  
+  // V3 vaults can hold any asset when idle
+  for (const v3Vault of v3Vaults) {
+    for (const asset of ASSETS) {
+      tokensAndOwners.push([asset, v3Vault]);
+    }
+  }
+  
+  await api.sumTokens({ tokensAndOwners });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/surf-liquid/index.js` around lines 33 - 60, The code currently only
records Morph o vault allocations in the allocations array (built by
v2MorphoVaults loop and v3 morphoVaults loop) but does not track idle underlying
token balances sitting directly in Surf vaults; after the existing
allocation-building loops (after the V3 loop), add logic to register direct
token-owner pairs so api.sumTokens can pick them up: for V2, add USDC with each
v2Vault as owner (use v2Vaults array and USDC constant), and for V3, for each
asset in ASSETS add the pair (asset, v3Vault) using v3Vaults; ensure you still
exclude ZERO_ADDR entries and reuse the same arrays/variables (v2Vaults,
v3Vaults, ASSETS, USDC, ZERO_ADDR) so sumTokens can be called with those
owner/token pairs to capture idle balances.


await api.sumTokens({ tokensAndOwners });
// --- Unwrap Morpho ERC-4626 shares to underlying tokens ---
// Manually compute underlying value so DefiLlama prices USDC/WETH/cbBTC directly,
// avoiding reliance on pricing for custom Morpho vault tokens.

const uniqueMorphoVaults = [...new Set(allocations.map((a) => a.morphoVault))];

const [allTotalAssets, allTotalSupply] = await Promise.all([
api.multiCall({ abi: "uint256:totalAssets", calls: uniqueMorphoVaults.map((t) => ({ target: t })) }),
api.multiCall({ abi: "uint256:totalSupply", calls: uniqueMorphoVaults.map((t) => ({ target: t })) }),
]);

const morphoData = {};
for (let i = 0; i < uniqueMorphoVaults.length; i++) {
morphoData[uniqueMorphoVaults[i]] = {
totalAssets: BigInt(allTotalAssets[i] || 0),
totalSupply: BigInt(allTotalSupply[i] || 0),
};
}

const allShares = await api.multiCall({
abi: "function balanceOf(address) view returns (uint256)",
calls: allocations.map((a) => ({ target: a.morphoVault, params: [a.surfVault] })),
});

for (let i = 0; i < allocations.length; i++) {
const { morphoVault, asset } = allocations[i];
const shares = BigInt(allShares[i] || 0);
if (shares === 0n) continue;
const { totalAssets, totalSupply } = morphoData[morphoVault];
if (totalSupply === 0n) continue;
const underlying = (shares * totalAssets) / totalSupply;
api.add(asset, underlying);
}
}

async function staking(api) {
Expand Down
Loading