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
51 changes: 51 additions & 0 deletions src/adaptors/forgeyields/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const utils = require('../utils');

const API_URL = 'https://api.forgeyields.com/strategies';

const underlyingTokens = {
ethereum: {
ETH: ['0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'],
USDC: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'],
WBTC: ['0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599'],
},
starknet: {
ETH: ['0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'],
USDC: ['0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8'],
WBTC: ['0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac'],
},
};

const apy = async () => {
const strategies = await utils.getData(API_URL);

const pools = [];
for (const s of strategies) {
for (const gw of s.token_gateway_per_domain) {
const chain = gw.domain;
const tokens = underlyingTokens[chain]?.[s.underlyingSymbol];
if (!tokens) continue;

pools.push({
pool: `${gw.token_gateway}-${chain}`.toLowerCase(),
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.

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.

poolMeta: s.symbol,
url: `https://app.forgeyields.com/opportunities/${s.symbol}`,
token: gw.token_gateway,
underlyingTokens: tokens,
searchTokenOverride: gw.token_gateway,
});
}
}

return pools.filter((p) => utils.keepFinite(p));
};

module.exports = {
timetravel: false,
apy,
url: 'https://app.forgeyields.com',
};
Loading