Skip to content

Add ForgeYields yield adaptor #2534

Open
0xSacha wants to merge 3 commits intoDefiLlama:masterfrom
ForgeYields:master
Open

Add ForgeYields yield adaptor #2534
0xSacha wants to merge 3 commits intoDefiLlama:masterfrom
ForgeYields:master

Conversation

@0xSacha
Copy link
Copy Markdown
Contributor

@0xSacha 0xSacha commented Mar 31, 2026

Add ForgeYields yield adaptor

ForgeYields is a cross-chain yield
allocator that deploys and rotates liquidity into optimized DeFi
strategies (Curve, Convex, lending markets, LST/LRT issuers).

Strategies

Strategy Underlying Ethereum Starknet
fyETH WETH (Ethereum) / ETH (Starknet) WETH ETH
fyUSDC USDC USDC USDC
fyWBTC WBTC WBTC WBTC

Architecture

  • 3 strategies, each with 2 token gateways (one per chain = 6 total)
  • Token gateways are ERC-4626 contracts that handle deposits and
    redemptions
  • Users deposit on any chain, assets are netted cross-chain and
    allocated to yield strategies on Ethereum L1

Adaptor details

  • Data source: https://api.forgeyields.com/strategies
  • Chains: Ethereum, Starknet
  • Pools: 6 (1 per token gateway)
  • apyBase: projected APY (integrationInfo.overallApy)
  • token: token gateway contract address (receipt token / deposit
    entry point)
  • underlyingTokens: per-chain underlying asset addresses
  • searchTokenOverride: token gateway address for search matching

TVL

Already listed on DefiLlama:
forgeyields (~$2M TVL)

Summary by CodeRabbit

  • New Features
    • Added ForgeYields integration to display yield opportunities across supported protocols.
    • Supports Ethereum and Starknet chains with ETH, USDC, and WBTC token strategies.
    • Displays pool information including TVL and APY rates for each strategy.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

A new ForgeYields APY adaptor module has been added that fetches strategy data from an external API, processes token gateway information across supported chains, and returns standardized pool records with APY and TVL data.

Changes

Cohort / File(s) Summary
ForgeYields APY Adaptor
src/adaptors/forgeyields/index.js
New adaptor module implementing APY data fetching from ForgeYields API, chain/symbol mappings, data transformation, and pool record construction with filtering.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A forge of yields so bright and true,
New pools discovered, shiny and new!
From Ethereum chains to Starknet's gleam,
APY data flows like a rabbit's dream ✨
Strategies gathered, transformed with care,
ForgeYields adaptor beyond compare!

🚥 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 ForgeYields yield adaptor' directly and accurately summarizes the main change: adding a new ForgeYields yield adaptor module.
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

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.

@llamatester
Copy link
Copy Markdown

The forgeyields adapter exports pools:

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

Nb of pools: 6
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────────────────────────────┬────────────┬───────────────┬────────┬────────────┬─────────┬──────────┬────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────┐
│ (index) │ pool                                                                          │ chain      │ project       │ symbol │ tvlUsd     │ apyBase │ poolMeta │ url                                                │ token                                                                │ underlyingTokens                                                         │ searchTokenOverride                                                  │
├─────────┼───────────────────────────────────────────────────────────────────────────────┼────────────┼───────────────┼────────┼────────────┼─────────┼──────────┼────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────┤
│ 0       │ '0x07fdcec0cef01294c9c3d52415215949805c77bae8003702a7928fd6d2c36bc1-starknet' │ 'Starknet' │ 'forgeyields' │ 'USDC' │ 1204858.14 │ 15.74   │ 'fyUSDC' │ 'https://app.forgeyields.com/opportunities/fyUSDC' │ '0x07fDcec0ceF01294C9C3D52415215949805C77bAe8003702A7928fd6D2c36BC1' │ [ '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8' ] │ '0x07fDcec0ceF01294C9C3D52415215949805C77bAe8003702A7928fd6D2c36BC1' │
│ 1       │ '0x943109dc7c950da4592d85ebd4cfed007af64670-ethereum'                         │ 'Ethereum' │ 'forgeyields' │ 'USDC' │ 1204858.14 │ 15.74   │ 'fyUSDC' │ 'https://app.forgeyields.com/opportunities/fyUSDC' │ '0x943109DC7C950da4592d85ebd4Cfed007Af64670'                         │ [ '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' ]                         │ '0x943109DC7C950da4592d85ebd4Cfed007Af64670'                         │
│ 2       │ '0x050707bc3b8730022f10530c2c6f6b9467644129c50c2868ad0036c5e4e9e616-starknet' │ 'Starknet' │ 'forgeyields' │ 'ETH'  │ 556918.76  │ 8.88    │ 'fyETH'  │ 'https://app.forgeyields.com/opportunities/fyETH'  │ '0x050707bc3b8730022f10530c2c6f6b9467644129c50c2868ad0036c5e4e9e616' │ [ '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7' ] │ '0x050707bc3b8730022f10530c2c6f6b9467644129c50c2868ad0036c5e4e9e616' │
│ 3       │ '0x98cd770b4e9905b1263f0c9ae6cde34e1923508e-ethereum'                         │ 'Ethereum' │ 'forgeyields' │ 'ETH'  │ 556918.76  │ 8.88    │ 'fyETH'  │ 'https://app.forgeyields.com/opportunities/fyETH'  │ '0x98CD770b4e9905B1263f0c9ae6cdE34E1923508E'                         │ [ '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' ]                         │ '0x98CD770b4e9905B1263f0c9ae6cdE34E1923508E'                         │
│ 4       │ '0x04dd39de0a588f5e1c7a8377e1bef2c49caaee49a11433429d2c48f587b3a492-starknet' │ 'Starknet' │ 'forgeyields' │ 'WBTC' │ 230135.31  │ 8.45    │ 'fyWBTC' │ 'https://app.forgeyields.com/opportunities/fyWBTC' │ '0x04DD39DE0a588F5e1c7A8377e1BEf2C49CAAeE49a11433429d2c48F587b3A492' │ [ '0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac' ] │ '0x04DD39DE0a588F5e1c7A8377e1BEf2C49CAAeE49a11433429d2c48F587b3A492' │
│ 5       │ '0xedca8230366b9eaff06becdd1d261577836aa507-ethereum'                         │ 'Ethereum' │ 'forgeyields' │ 'WBTC' │ 230135.31  │ 8.45    │ 'fyWBTC' │ 'https://app.forgeyields.com/opportunities/fyWBTC' │ '0xeDca8230366B9eaFf06becdD1D261577836AA507'                         │ [ '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' ]                         │ '0xeDca8230366B9eaFf06becdD1D261577836AA507'                         │
└─────────┴───────────────────────────────────────────────────────────────────────────────┴────────────┴───────────────┴────────┴────────────┴─────────┴──────────┴────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────┘

Copy link
Copy Markdown
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: 2

🧹 Nitpick comments (1)
src/adaptors/forgeyields/index.js (1)

24-26: Consider case-insensitive chain matching.

If the API returns chain names with different casing (e.g., "Ethereum" vs "ethereum"), the lookup will silently fail and skip the gateway. This is defensive but worth noting.

Optional: normalize chain to lowercase
       const chain = gw.domain;
-      const tokens = underlyingTokens[chain]?.[s.underlyingSymbol];
+      const tokens = underlyingTokens[chain.toLowerCase()]?.[s.underlyingSymbol];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/forgeyields/index.js` around lines 24 - 26, The lookup for
tokens uses gw.domain as-is so differing case (e.g., "Ethereum" vs "ethereum")
causes misses; normalize the chain string before lookup (e.g., const chain =
gw.domain.toLowerCase()) and ensure the keys in underlyingTokens are normalized
to the same case (or perform a case-insensitive lookup) so the tokens =
underlyingTokens[chain]?.[s.underlyingSymbol] check succeeds; update the code
around gw.domain, underlyingTokens, and the tokens lookup to use the normalized
key.
🤖 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/forgeyields/index.js`:
- Line 34: The assignment apyBase: Number(s.integrationInfo.overallApy) can
throw when s.integrationInfo is null/undefined; update the apyBase calculation
in the adaptor (where apyBase is set) to use optional chaining on
s.integrationInfo (e.g., s.integrationInfo?.overallApy) so Number() receives
undefined instead of causing a TypeError, letting keepFinite handle the
resulting NaN as intended.
- Line 33: The pool creation currently sets tvlUsd to the strategy-level
s.tvlUSD (causing double-counting across gateways) and accesses
s.integrationInfo.overallApy without a null guard; update the pool TVL
assignment to use per-gateway gw.tvlUSD if the API supplies it, otherwise divide
s.tvlUSD by s.gateways.length (e.g., tvlUsd = gw.tvlUSD ?? s.tvlUSD /
s.gateways.length), and wrap reads of s.integrationInfo.overallApy with a
null-safe check (e.g., compute overallApy = s.integrationInfo ?
s.integrationInfo.overallApy : undefined) before using it in the pool object so
no TypeError occurs.

---

Nitpick comments:
In `@src/adaptors/forgeyields/index.js`:
- Around line 24-26: The lookup for tokens uses gw.domain as-is so differing
case (e.g., "Ethereum" vs "ethereum") causes misses; normalize the chain string
before lookup (e.g., const chain = gw.domain.toLowerCase()) and ensure the keys
in underlyingTokens are normalized to the same case (or perform a
case-insensitive lookup) so the tokens =
underlyingTokens[chain]?.[s.underlyingSymbol] check succeeds; update the code
around gw.domain, underlyingTokens, and the tokens lookup to use the normalized
key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0f2686e-00c0-44fd-85db-0021f9c5cfe7

📥 Commits

Reviewing files that changed from the base of the PR and between c9684dc and b6a6b95.

📒 Files selected for processing (1)
  • src/adaptors/forgeyields/index.js

chain: utils.formatChain(chain),
project: 'forgeyields',
symbol: s.underlyingSymbol,
tvlUsd: Number(s.tvlUSD),
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

🧩 Analysis chain

🏁 Script executed:

cat -n src/adaptors/forgeyields/index.js

Repository: DefiLlama/yield-server

Length of output: 1967


🏁 Script executed:

# Search for forgeyields-related files
fd -i "forgeyields" -type f

# Search for documentation or examples
fd -i "test" -type f | grep -i forgeyields || echo "No test file found with forgeyields in name"

# Look for API documentation or comments about forgeyields
rg -i "forgeyields" --type=json --type=md -A 5 -B 5 | head -100

Repository: DefiLlama/yield-server

Length of output: 468


🏁 Script executed:

# Find all forgeyields-related files
fd -i "forgeyields"

# Search for example API responses or documentation
rg "tvlUSD\|token_gateway_per_domain" -A 3 -B 3

# Check if there are any comments or docs about the API structure
rg "forgeyields" -i -A 5 -B 5 | head -150

Repository: DefiLlama/yield-server

Length of output: 2029


🏁 Script executed:

# Search for other adaptors with similar nested loop patterns (multi-chain/gateway)
rg "for.*strategies\|for.*domains\|for.*chains" -A 10 src/adaptors/ | head -200

# Check git history for this file
git log --oneline src/adaptors/forgeyields/index.js 2>/dev/null | head -20

# Look for similar patterns in other adaptors where TVL comes from outer loop
rg "tvlUsd.*Number\(s\." src/adaptors/ | head -20

Repository: DefiLlama/yield-server

Length of output: 206


🏁 Script executed:

# Check the recent commit to understand the changes
git show b6a6b95d --stat 2>/dev/null || echo "Cannot show commit"

# Search for adaptors with nested loops and nested pool creation
rg "for.*of.*strategies\|for.*of.*gateways\|for.*of.*domains" -A 15 src/adaptors/ | grep -A 15 "tvlUsd\|tvl" | head -150

# Look for PR or issue documentation
fd README -type f src/adaptors/forgeyields/

# Check if there are any similar multi-gateway or chain-specific TVL patterns
rg "tvlUsd.*Number" -B 5 -A 5 src/adaptors/ | head -200

Repository: DefiLlama/yield-server

Length of output: 50567


🏁 Script executed:

# Look for other adaptors with nested loops where TVL might be duplicated
rg "for.*const.*of" -A 20 src/adaptors/ --max-count=5 | grep -A 20 "pools.push\|tvlUsd" | head -100

# Search for similar multi-chain/multi-gateway patterns
rg "\.length.*||.*1" src/adaptors/ | grep -i "gateway\|domain\|chain" | head -20

Repository: DefiLlama/yield-server

Length of output: 8091


🏁 Script executed:

# Search for any documentation about ForgeYields API or data structure
rg "integrationInfo\|token_gateway_per_domain" -B 5 -A 5 src/adaptors/ | head -50

# Look for any tests or examples that might show API response structure
fd -i "test\|spec\|example" -type f src/adaptors/forgeyields/

Repository: DefiLlama/yield-server

Length of output: 236


🏁 Script executed:

# Search for integrationInfo and token_gateway_per_domain without type filter
rg "integrationInfo\|token_gateway_per_domain" -B 3 -A 3

# List all files in forgeyields directory
ls -la src/adaptors/forgeyields/

Repository: DefiLlama/yield-server

Length of output: 243


🏁 Script executed:

# Search for adaptors with similar nested loops (multiple pools from single data point)
rg "for.*of.*\[" -A 15 src/adaptors/ | grep -B 5 -A 10 "pools.push" | head -150

# Check if there are any comments or documentation about multi-gateway TVL handling
rg "gateway\|domain\|chain.*loop\|tvl.*strategy\|tvl.*per" -i src/adaptors/ | grep -v node_modules | head -30

Repository: DefiLlama/yield-server

Length of output: 1337


🏁 Script executed:

# Search for any README or documentation that might have API examples
find . -name "README*" -o -name "*.md" | xargs grep -l "forgeyields\|tvlUSD\|token_gateway" 2>/dev/null || echo "No docs found"

# Look for any API response examples or constants that might show data structure
rg "API_URL\|https://api.forgeyields" -A 10 -B 5 src/adaptors/

# Check if integrationInfo is checked elsewhere in the codebase
rg "integrationInfo" src/adaptors/

Repository: DefiLlama/yield-server

Length of output: 168


Divide TVL across gateways and add null check for integrationInfo.

The strategy-level s.tvlUSD is assigned to every pool created per gateway, causing TVL to be counted multiple times. Additionally, s.integrationInfo.overallApy is accessed without a null guard, which will throw a TypeError if integrationInfo is undefined.

Proposed fixes
+      const gatewayCount = s.token_gateway_per_domain.length || 1;
       pools.push({
         pool: `${gw.token_gateway}-${chain}`.toLowerCase(),
         chain: utils.formatChain(chain),
         project: 'forgeyields',
         symbol: s.underlyingSymbol,
-        tvlUsd: Number(s.tvlUSD),
+        tvlUsd: Number(s.tvlUSD) / gatewayCount,
-        apyBase: Number(s.integrationInfo.overallApy),
+        apyBase: s.integrationInfo ? Number(s.integrationInfo.overallApy) : undefined,

Consider whether the API provides per-gateway TVL; if so, use gw.tvlUSD instead of dividing.

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

In `@src/adaptors/forgeyields/index.js` at line 33, The pool creation currently
sets tvlUsd to the strategy-level s.tvlUSD (causing double-counting across
gateways) and accesses s.integrationInfo.overallApy without a null guard; update
the pool TVL assignment to use per-gateway gw.tvlUSD if the API supplies it,
otherwise divide s.tvlUSD by s.gateways.length (e.g., tvlUsd = gw.tvlUSD ??
s.tvlUSD / s.gateways.length), and wrap reads of s.integrationInfo.overallApy
with a null-safe check (e.g., compute overallApy = s.integrationInfo ?
s.integrationInfo.overallApy : undefined) before using it in the pool object so
no TypeError occurs.

project: 'forgeyields',
symbol: s.underlyingSymbol,
tvlUsd: Number(s.tvlUSD),
apyBase: Number(s.integrationInfo.overallApy),
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

Missing null check on integrationInfo will crash the adaptor.

If s.integrationInfo is null or undefined for any strategy, accessing .overallApy throws a TypeError before keepFinite can filter it out.

Proposed fix using optional chaining
-        apyBase: Number(s.integrationInfo.overallApy),
+        apyBase: Number(s.integrationInfo?.overallApy),

This will produce NaN for missing data, which keepFinite will then filter out.

📝 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
apyBase: Number(s.integrationInfo.overallApy),
apyBase: Number(s.integrationInfo?.overallApy),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/adaptors/forgeyields/index.js` at line 34, The assignment apyBase:
Number(s.integrationInfo.overallApy) can throw when s.integrationInfo is
null/undefined; update the apyBase calculation in the adaptor (where apyBase is
set) to use optional chaining on s.integrationInfo (e.g.,
s.integrationInfo?.overallApy) so Number() receives undefined instead of causing
a TypeError, letting keepFinite handle the resulting NaN as intended.

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.

2 participants