Skip to content

Add surf-liquid yield adapter#2475

Open
rohansingh4 wants to merge 3 commits intoDefiLlama:masterfrom
rohansingh4:add-surf-liquid-yield
Open

Add surf-liquid yield adapter#2475
rohansingh4 wants to merge 3 commits intoDefiLlama:masterfrom
rohansingh4:add-surf-liquid-yield

Conversation

@rohansingh4
Copy link

@rohansingh4 rohansingh4 commented Mar 12, 2026

Summary

  • Add yield adapter for Surf Liquid protocol on Base
  • 4 pools: USDC, WETH, cbBTC vault yields + SURF staking rewards

Pools

Pool APY Source Details
USDC Vaults Morpho lending APY × 90% 10% performance fee on earned yield
WETH Vaults Morpho lending APY × 90% 10% performance fee on earned yield
cbBTC Vaults Morpho lending APY × 90% 10% performance fee on earned yield
SURF Staking 14% / 18% fixed 14% for 6M lock, 18% for 12M lock

How it works

  • Surf Liquid deposits user funds into Morpho ERC-4626 vaults on Base
  • Every 12 hours, vaults rebalance to the highest-APY Morpho market
  • 10% performance fee is taken on earned yield during rebalance
  • APY data is fetched from Morpho's GraphQL API for the current underlying vaults
  • TVL is computed from on-chain Morpho vault share balances

Test results

PASS src/adaptors/test.js
Test Suites: 1 passed, 1 total
Tests:       29 passed, 29 total

Data sources

  • On-chain: V2/V3 factory contracts, Morpho ERC-4626 vault balances, staking contract
  • Morpho GraphQL API: vault APY data
  • DefiLlama coins API: SURF token price

Summary by CodeRabbit

  • New Features
    • Surf Liquid integration on Base chain: discover and aggregate Morpho-backed vaults across assets, map to Surf pools, and expose detailed pool objects.
    • Real-time on-chain APY and TVL calculations (USD), including a 10% performance-fee adjustment for user APY.
    • Dedicated SURF staking pool with staking TVL and reward APY.

Pools:
- USDC/WETH/cbBTC vault pools with Morpho lending APY (after 10% fee)
- SURF staking pool (14% for 6M, 18% for 12M lock)

APY is sourced from Morpho GraphQL API for the underlying vaults
that Surf Liquid deposits into, with 10% performance fee deducted.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Adds a Surf Liquid adaptor for Base that discovers Morpho V2/V3 vaults, maps Morpho-to-Surf vaults, aggregates balances, computes on-chain APY and USD TVL (via CoinGecko/llama), builds per-asset yield pools with a 10% performance fee, and adds a SURF staking pool.

Changes

Cohort / File(s) Summary
Surf Liquid Adaptor
src/adaptors/surf-liquid/index.js
New adaptor implementing vault discovery (V2/V3 factories and events), Morpho↔Surf mapping, balance aggregation, on-chain APY calculation (totalAssets/totalSupply over blocks), price fetching, TVL and pool construction (per-asset yield pools + SURF staking pool), and exported timetravel, apy, and url.

Sequence Diagram

sequenceDiagram
    participant Adaptor as Adaptor
    participant V2Factory as Morpho V2<br/>Factory
    participant V3Factory as Morpho V3<br/>Factory
    participant MorphoAPI as Morpho API
    participant StakingContract as SURF Staking<br/>Contract
    participant PriceAPI as CoinGecko/llama
    participant User as User/Consumer

    Adaptor->>V2Factory: Discover vaults (total, infos, events)
    Adaptor->>V3Factory: Discover vaults (total, infos, events)
    Adaptor->>V2Factory: Map vaults (currentVault)
    Adaptor->>V3Factory: Map vaults (assetToVault)
    Adaptor->>Adaptor: Aggregate balances and mappings
    Adaptor->>MorphoAPI: Query per-vault metrics (totalAssets, totalSupply, decimals)
    MorphoAPI-->>Adaptor: Return vault metrics
    Adaptor->>PriceAPI: Fetch asset prices
    PriceAPI-->>Adaptor: Return prices
    Adaptor->>StakingContract: Query totalStaked & subscription TVL
    Adaptor->>Adaptor: Compute per-vault APY, convert shares, calculate TVL/USD, apply 10% fee, build pools
    Adaptor->>User: Return array of pool objects (pools + poolMeta)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I sniffed through V2 and V3,
Mapped vaults beneath the sea,
SURF pools shimmer, APYs gleam,
TVL grows like a dream,
A rabbit's hop through code and stream. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add surf-liquid yield adapter' directly matches the main change: a new yield adaptor for Surf Liquid on Base is introduced in src/adaptors/surf-liquid/index.js.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

@llamatester
Copy link

The surf-liquid adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 29 passed, 29 total
Snapshots: 0 total
Time: 0.246 s
Ran all test suites.

Nb of pools: 4
 

Sample pools:
┌─────────┬────────────────────────────┬────────┬───────────────┬─────────┬────────────────────┬────────────────────┬──────────────────────────────────────────────────┬───────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────┐
│ (index) │ pool                       │ chain  │ project       │ symbol  │ tvlUsd             │ apyBase            │ underlyingTokens                                 │ apyReward │ rewardTokens                                     │ poolMeta                                 │
├─────────┼────────────────────────────┼────────┼───────────────┼─────────┼────────────────────┼────────────────────┼──────────────────────────────────────────────────┼───────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ 0       │ 'surf-liquid-usdc-base'    │ 'Base' │ 'surf-liquid' │ 'USDC'  │ 239208.65411740588 │ 6.001787620055975  │ [ '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ] │           │                                                  │                                          │
│ 1       │ 'surf-liquid-staking-base' │ 'Base' │ 'surf-liquid' │ 'SURF'  │ 31784.08498683567  │ 0                  │ [ '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf' ] │ 14        │ [ '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf' ] │ '14% APY (6M lock) / 18% APY (12M lock)' │
│ 2       │ 'surf-liquid-cbbtc-base'   │ 'Base' │ 'surf-liquid' │ 'cbBTC' │ 7824.892745443627  │ 0.2569473422422813 │ [ '0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf' ] │           │                                                  │                                          │
│ 3       │ 'surf-liquid-weth-base'    │ 'Base' │ 'surf-liquid' │ 'WETH'  │ 6477.04982699591   │ 1.876240749824577  │ [ '0x4200000000000000000000000000000000000006' ] │           │                                                  │                                          │
└─────────┴────────────────────────────┴────────┴───────────────┴─────────┴────────────────────┴────────────────────┴──────────────────────────────────────────────────┴───────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@0xkr3p
Copy link
Contributor

0xkr3p commented Mar 17, 2026

Hi @rohansingh4 thanks for the PR. A tvl adapter is required before we can proceed with a yield adapter. I've added a couple of comments

const ZERO_ADDR = '0x0000000000000000000000000000000000000000';

const PERFORMANCE_FEE = 0.1; // 10% on earned yield
const STAKING_APY = 14; // conservative: 14% (6M), up to 18% (12M)
Copy link
Contributor

Choose a reason for hiding this comment

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

apy should be derived from onchain sources using defillama sdk where possible or a subgraph

Copy link
Author

Choose a reason for hiding this comment

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

tvl adapter- DefiLlama/DefiLlama-Adapters#18401 (comment)

also i see our surfliquid page deprecetated tag is removed, which is great but please update our numbers meanwhile whichver is verified from your end, those with issues you can tell me I will try to resolved

const userApy = avgMorphoApy * (1 - PERFORMANCE_FEE);

pools.push({
pool: `surf-liquid-${ASSET_SYMBOLS[asset].toLowerCase()}-${CHAIN}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

pool should be made up of the receipt token address and chain


if (stakingTvl > 100) {
pools.push({
pool: `surf-liquid-staking-${CHAIN}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

same applies here


let morphoData = {};
if (allMorphoAddresses.length > 0) {
const { vaults: morphoVaultsResp } = await request(
Copy link
Contributor

Choose a reason for hiding this comment

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

can we get morpho apy from onchain or subgraph

…endency

- Replace Morpho GraphQL API with on-chain share price calculation
  (totalAssets/totalSupply at current vs 1-day-ago blocks)
- Derive staking APR from on-chain contract (apr6Months, apr12Months,
  BASIS_POINTS) instead of hardcoded value
- Change pool IDs to use contract addresses (factory/staking address + chain)
- Remove graphql-request dependency
- Get asset prices from coins.llama.fi instead of Morpho API
@llamatester
Copy link

The surf-liquid adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 29 passed, 29 total
Snapshots: 0 total
Time: 0.293 s
Ran all test suites.

Nb of pools: 4
 

Sample pools:
┌─────────┬─────────────────────────────────────────────────────────┬────────┬───────────────┬─────────┬────────────────────┬──────────────────────┬──────────────────────────────────────────────────┬───────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────┐
│ (index) │ pool                                                    │ chain  │ project       │ symbol  │ tvlUsd             │ apyBase              │ underlyingTokens                                 │ apyReward │ rewardTokens                                     │ poolMeta                                 │
├─────────┼─────────────────────────────────────────────────────────┼────────┼───────────────┼─────────┼────────────────────┼──────────────────────┼──────────────────────────────────────────────────┼───────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ 0       │ '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa-usdc-base'  │ 'Base' │ 'surf-liquid' │ 'USDC'  │ 211849.34725221578 │ 4.398388112749307    │ [ '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ] │           │                                                  │                                          │
│ 1       │ '0xb0fdfc081310a5914c2d2c97e7582f4de12fa9d6-base'       │ 'Base' │ 'surf-liquid' │ 'SURF'  │ 42125.640507197684 │ 0                    │ [ '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf' ] │ 12        │ [ '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf' ] │ '12% APR (6M lock) / 18% APR (12M lock)' │
│ 2       │ '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa-cbbtc-base' │ 'Base' │ 'surf-liquid' │ 'cbBTC' │ 8362.145902014516  │ 0.002613331376060124 │ [ '0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf' ] │           │                                                  │                                          │
│ 3       │ '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa-weth-base'  │ 'Base' │ 'surf-liquid' │ 'WETH'  │ 4121.649398812255  │ 1.5168904025579644   │ [ '0x4200000000000000000000000000000000000006' ] │           │                                                  │                                          │
└─────────┴─────────────────────────────────────────────────────────┴────────┴───────────────┴─────────┴────────────────────┴──────────────────────┴──────────────────────────────────────────────────┴───────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/adaptors/surf-liquid/index.js (1)

239-239: ⚠️ Potential issue | 🟠 Major

Use a stable address-based pool id, not symbol-derived suffixes.

Line 239 ties identity to ASSET_SYMBOLS[...]; symbol changes/casing changes can create a new config key. Since pool is the dedupe key downstream, this risks pool history fragmentation. Prefer a canonical address-based id (asset or receipt token address + chain).

Suggested fix
-      pool: `${V3_FACTORY.toLowerCase()}-${ASSET_SYMBOLS[asset].toLowerCase()}-${CHAIN}`,
+      pool: `${asset.toLowerCase()}-${CHAIN}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/surf-liquid/index.js` at line 239, The pool id currently uses
ASSET_SYMBOLS[asset] which is unstable; change it to use a canonical token
address (e.g., the asset or receipt token address) instead. Replace
`${V3_FACTORY.toLowerCase()}-${ASSET_SYMBOLS[asset].toLowerCase()}-${CHAIN}`
with a stable id built from the token address like
`${V3_FACTORY.toLowerCase()}-${assetAddress.toLowerCase()}-${CHAIN}`, where
assetAddress is resolved from your existing mapping or accessor (e.g.,
ASSET_ADDRESSES[asset] or getAssetAddress(asset)) and normalized (lowercase or
checksum) before interpolation so pool dedupe uses the immutable address rather
than a symbol.
🧹 Nitpick comments (1)
src/adaptors/surf-liquid/index.js (1)

85-91: Parallelize per-asset assetToVault multicalls to reduce RPC latency.

The current loop awaits each asset call serially. This is safe but slower; a Promise.all over ASSETS keeps behavior and reduces runtime variance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/surf-liquid/index.js` around lines 85 - 91, The loop over ASSETS
currently awaits sdk.api.abi.multiCall for each asset sequentially; change it to
run the per-asset multicalls in parallel by mapping ASSETS to an array of
promises that call sdk.api.abi.multiCall (with the same abi 'function
assetToVault(address) view returns (address)', calls built from
v3Vaults.map(vault => ({ target: vault, params: [asset] })), and CHAIN) and then
await Promise.all on that array; ensure you preserve the skip when
v3Vaults.length === 0 (either check once before creating promises or keep a
per-asset guard that returns a resolved value) and then process the resulting
array of { output: morphoResults } responses the same way as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/adaptors/surf-liquid/index.js`:
- Around line 279-280: The APR calculation for stakingApr6M and stakingApr12M
divides by basisPoints without checking for zero/invalid, causing Infinity/NaN;
update the code around stakingApr6M/stakingApr12M to parse Number(basisPoints)
once (e.g., const bp = Number(basisPoints)) and if bp <= 0 or isNaN(bp) avoid
the division by setting stakingApr6M and stakingApr12M to a safe fallback (null
or 0) or skip the calculation and optionally log a warning; ensure you reference
and update the expressions that use apr6M, apr12M and basisPoints so they use
the validated bp variable.
- Around line 298-305: The if gate currently checks only stakingTvl but tvlUsd
uses stakingTvl + subscriptionTvl, so update the condition to apply the
visibility threshold to the combined TVL; compute a combinedTvl (stakingTvl +
subscriptionTvl) or directly change the guard to if (stakingTvl +
subscriptionTvl > 100) before pushing the pool object (the pools.push block that
creates the SURF_STAKING-CHAIN entry and sets tvlUsd), ensuring the same summed
value is used in both the check and the reported tvlUsd.
- Around line 174-188: The code is currently defaulting supply to '1'
(sNow/sPast) which can fabricate prices/apy; change the logic in the block that
computes aNow, sNow, aPast, sPast, priceNow, pricePast, apyVal and the
assignment to morphoData[addr] to treat missing or zero supply as invalid: do
not substitute '1' for supply—parse supply to a numeric (or BigInt) and if sNow
=== 0 or sPast === 0 or any supply field is missing/NaN then set apy to 0 (or
skip setting apy) and set totalSupply to BigInt(0) (and totalAssets to BigInt(0)
if assets missing) instead of using '1'; ensure the priceNow/pricePast and
Math.pow calculation are only executed when both supplies are >0 to avoid
synthetic APY and use the existing symbols (aNow, sNow, aPast, sPast, priceNow,
pricePast, apyVal, morphoData[addr], totalAssets, totalSupply) to localize the
changes.

---

Duplicate comments:
In `@src/adaptors/surf-liquid/index.js`:
- Line 239: The pool id currently uses ASSET_SYMBOLS[asset] which is unstable;
change it to use a canonical token address (e.g., the asset or receipt token
address) instead. Replace
`${V3_FACTORY.toLowerCase()}-${ASSET_SYMBOLS[asset].toLowerCase()}-${CHAIN}`
with a stable id built from the token address like
`${V3_FACTORY.toLowerCase()}-${assetAddress.toLowerCase()}-${CHAIN}`, where
assetAddress is resolved from your existing mapping or accessor (e.g.,
ASSET_ADDRESSES[asset] or getAssetAddress(asset)) and normalized (lowercase or
checksum) before interpolation so pool dedupe uses the immutable address rather
than a symbol.

---

Nitpick comments:
In `@src/adaptors/surf-liquid/index.js`:
- Around line 85-91: The loop over ASSETS currently awaits sdk.api.abi.multiCall
for each asset sequentially; change it to run the per-asset multicalls in
parallel by mapping ASSETS to an array of promises that call
sdk.api.abi.multiCall (with the same abi 'function assetToVault(address) view
returns (address)', calls built from v3Vaults.map(vault => ({ target: vault,
params: [asset] })), and CHAIN) and then await Promise.all on that array; ensure
you preserve the skip when v3Vaults.length === 0 (either check once before
creating promises or keep a per-asset guard that returns a resolved value) and
then process the resulting array of { output: morphoResults } responses the same
way as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09fe5cc3-80a1-4c38-b71a-3bd610c6b70a

📥 Commits

Reviewing files that changed from the base of the PR and between 920907c and af8c19c.

📒 Files selected for processing (1)
  • src/adaptors/surf-liquid/index.js

Comment on lines +174 to +188
const aNow = Number(assetsNowRes.output[i].output || '0');
const sNow = Number(supplyNowRes.output[i].output || '1');
const aPast = Number(assetsPastRes.output[i].output || '0');
const sPast = Number(supplyPastRes.output[i].output || '1');

const priceNow = sNow > 0 ? aNow / sNow : 1;
const pricePast = sPast > 0 ? aPast / sPast : 1;
const apyVal =
pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0;

morphoData[addr] = {
apy: Math.max(apyVal, 0),
totalAssets: BigInt(assetsNowRes.output[i].output || '0'),
totalSupply: BigInt(supplyNowRes.output[i].output || '1'),
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid synthetic APY when supply data is missing or zero.

On Line 175 and Line 177, defaulting totalSupply to '1' can fabricate share prices/APY if the call fails or returns zero. That can silently skew apyBase and downstream rankings.

Suggested fix
-    const aNow = Number(assetsNowRes.output[i].output || '0');
-    const sNow = Number(supplyNowRes.output[i].output || '1');
-    const aPast = Number(assetsPastRes.output[i].output || '0');
-    const sPast = Number(supplyPastRes.output[i].output || '1');
+    const aNowRaw = assetsNowRes.output[i]?.output ?? '0';
+    const sNowRaw = supplyNowRes.output[i]?.output ?? '0';
+    const aPastRaw = assetsPastRes.output[i]?.output ?? '0';
+    const sPastRaw = supplyPastRes.output[i]?.output ?? '0';
+
+    const aNow = Number(aNowRaw);
+    const sNow = Number(sNowRaw);
+    const aPast = Number(aPastRaw);
+    const sPast = Number(sPastRaw);

-    const priceNow = sNow > 0 ? aNow / sNow : 1;
-    const pricePast = sPast > 0 ? aPast / sPast : 1;
-    const apyVal =
-      pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0;
+    const hasValidSupply = sNow > 0 && sPast > 0;
+    const priceNow = hasValidSupply ? aNow / sNow : 0;
+    const pricePast = hasValidSupply ? aPast / sPast : 0;
+    const apyVal =
+      hasValidSupply && pricePast > 0 ? Math.pow(priceNow / pricePast, 365) - 1 : 0;

     morphoData[addr] = {
       apy: Math.max(apyVal, 0),
-      totalAssets: BigInt(assetsNowRes.output[i].output || '0'),
-      totalSupply: BigInt(supplyNowRes.output[i].output || '1'),
+      totalAssets: BigInt(aNowRaw),
+      totalSupply: BigInt(sNowRaw),
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/surf-liquid/index.js` around lines 174 - 188, The code is
currently defaulting supply to '1' (sNow/sPast) which can fabricate prices/apy;
change the logic in the block that computes aNow, sNow, aPast, sPast, priceNow,
pricePast, apyVal and the assignment to morphoData[addr] to treat missing or
zero supply as invalid: do not substitute '1' for supply—parse supply to a
numeric (or BigInt) and if sNow === 0 or sPast === 0 or any supply field is
missing/NaN then set apy to 0 (or skip setting apy) and set totalSupply to
BigInt(0) (and totalAssets to BigInt(0) if assets missing) instead of using '1';
ensure the priceNow/pricePast and Math.pow calculation are only executed when
both supplies are >0 to avoid synthetic APY and use the existing symbols (aNow,
sNow, aPast, sPast, priceNow, pricePast, apyVal, morphoData[addr], totalAssets,
totalSupply) to localize the changes.

Comment on lines +279 to +280
const stakingApr6M = (Number(apr6M) / Number(basisPoints)) * 100;
const stakingApr12M = (Number(apr12M) / Number(basisPoints)) * 100;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against zero BASIS_POINTS before APR division.

If BASIS_POINTS is zero (or invalid), Line 279/280 yields Infinity/NaN APY values.

Suggested fix
-  const stakingApr6M = (Number(apr6M) / Number(basisPoints)) * 100;
-  const stakingApr12M = (Number(apr12M) / Number(basisPoints)) * 100;
+  const bp = Number(basisPoints);
+  if (!bp) throw new Error('Invalid BASIS_POINTS from staking contract');
+  const stakingApr6M = (Number(apr6M) / bp) * 100;
+  const stakingApr12M = (Number(apr12M) / bp) * 100;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/surf-liquid/index.js` around lines 279 - 280, The APR
calculation for stakingApr6M and stakingApr12M divides by basisPoints without
checking for zero/invalid, causing Infinity/NaN; update the code around
stakingApr6M/stakingApr12M to parse Number(basisPoints) once (e.g., const bp =
Number(basisPoints)) and if bp <= 0 or isNaN(bp) avoid the division by setting
stakingApr6M and stakingApr12M to a safe fallback (null or 0) or skip the
calculation and optionally log a warning; ensure you reference and update the
expressions that use apr6M, apr12M and basisPoints so they use the validated bp
variable.

Comment on lines +298 to +305
if (stakingTvl > 100) {
pools.push({
pool: `${SURF_STAKING.toLowerCase()}-${CHAIN}`,
chain: utils.formatChain(CHAIN),
project: 'surf-liquid',
symbol: 'SURF',
tvlUsd: stakingTvl + subscriptionTvl,
apyBase: 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Apply the visibility threshold to the same TVL you report.

Line 298 gates on stakingTvl only, but Line 304 reports stakingTvl + subscriptionTvl. This can drop a valid pool even when reported TVL would pass the threshold.

Suggested fix
-  if (stakingTvl > 100) {
+  const totalSurfTvl = stakingTvl + subscriptionTvl;
+  if (totalSurfTvl > 100) {
     pools.push({
       pool: `${SURF_STAKING.toLowerCase()}-${CHAIN}`,
       chain: utils.formatChain(CHAIN),
       project: 'surf-liquid',
       symbol: 'SURF',
-      tvlUsd: stakingTvl + subscriptionTvl,
+      tvlUsd: totalSurfTvl,
       apyBase: 0,
       apyReward: stakingApr6M,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (stakingTvl > 100) {
pools.push({
pool: `${SURF_STAKING.toLowerCase()}-${CHAIN}`,
chain: utils.formatChain(CHAIN),
project: 'surf-liquid',
symbol: 'SURF',
tvlUsd: stakingTvl + subscriptionTvl,
apyBase: 0,
const totalSurfTvl = stakingTvl + subscriptionTvl;
if (totalSurfTvl > 100) {
pools.push({
pool: `${SURF_STAKING.toLowerCase()}-${CHAIN}`,
chain: utils.formatChain(CHAIN),
project: 'surf-liquid',
symbol: 'SURF',
tvlUsd: totalSurfTvl,
apyBase: 0,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/surf-liquid/index.js` around lines 298 - 305, The if gate
currently checks only stakingTvl but tvlUsd uses stakingTvl + subscriptionTvl,
so update the condition to apply the visibility threshold to the combined TVL;
compute a combinedTvl (stakingTvl + subscriptionTvl) or directly change the
guard to if (stakingTvl + subscriptionTvl > 100) before pushing the pool object
(the pools.push block that creates the SURF_STAKING-CHAIN entry and sets
tvlUsd), ensuring the same summed value is used in both the check and the
reported tvlUsd.

@0xkr3p
Copy link
Contributor

0xkr3p commented Mar 19, 2026

hi @rohansingh4, thanks for the changes! pls dbl check the code rabbit issues, remove hardcoded fallbacks for reward apy, better to be null than inaccurate values. Reward tokens should only be added to the pools when reward apy > 0

- Remove totalSupply fallback to '1'; skip vaults with zero supply instead
  of fabricating APY values
- Guard BASIS_POINTS against zero before APR division
- Use total TVL (staking + subscriptions) for visibility threshold
- Only add rewardTokens/apyReward when reward APY > 0
- Clean up BigInt comparisons to use 0n literals
@llamatester
Copy link

The surf-liquid adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 29 passed, 29 total
Snapshots: 0 total
Time: 0.287 s
Ran all test suites.

Nb of pools: 4
 

Sample pools:
┌─────────┬─────────────────────────────────────────────────────────┬────────┬───────────────┬─────────┬────────────────────┬───────────────────────┬──────────────────────────────────────────────────┬───────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────┐
│ (index) │ pool                                                    │ chain  │ project       │ symbol  │ tvlUsd             │ apyBase               │ underlyingTokens                                 │ apyReward │ rewardTokens                                     │ poolMeta                                 │
├─────────┼─────────────────────────────────────────────────────────┼────────┼───────────────┼─────────┼────────────────────┼───────────────────────┼──────────────────────────────────────────────────┼───────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────┤
│ 0       │ '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa-usdc-base'  │ 'Base' │ 'surf-liquid' │ 'USDC'  │ 195864.8976000432  │ 3.2602201940971605    │ [ '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ] │           │                                                  │                                          │
│ 1       │ '0xb0fdfc081310a5914c2d2c97e7582f4de12fa9d6-base'       │ 'Base' │ 'surf-liquid' │ 'SURF'  │ 37102.123160716896 │ 0                     │ [ '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf' ] │ 12        │ [ '0xcdca2eaae4a8a6b83d7a3589946c2301040dafbf' ] │ '12% APR (6M lock) / 18% APR (12M lock)' │
│ 2       │ '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa-cbbtc-base' │ 'Base' │ 'surf-liquid' │ 'cbBTC' │ 7993.408916730189  │ 0.0025750811108249394 │ [ '0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf' ] │           │                                                  │                                          │
│ 3       │ '0xf1d64dee9f8e109362309a4bfbb523c8e54fa1aa-weth-base'  │ 'Base' │ 'surf-liquid' │ 'WETH'  │ 3401.4744511167987 │ 1.5354570706516912    │ [ '0x4200000000000000000000000000000000000006' ] │           │                                                  │                                          │
└─────────┴─────────────────────────────────────────────────────────┴────────┴───────────────┴─────────┴────────────────────┴───────────────────────┴──────────────────────────────────────────────────┴───────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants