Skip to content

updating subgraph endpoints (satsuma is deprecated) #2352

Open
ethereumdegen wants to merge 2 commits intoDefiLlama:masterfrom
teller-protocol:teller-update-endpoints
Open

updating subgraph endpoints (satsuma is deprecated) #2352
ethereumdegen wants to merge 2 commits intoDefiLlama:masterfrom
teller-protocol:teller-update-endpoints

Conversation

@ethereumdegen
Copy link
Contributor

@ethereumdegen ethereumdegen commented Feb 11, 2026

updating subgraph endpoints (satsuma is deprecated) and using updated status keywords for more accurate accounting

Summary by CodeRabbit

  • Refactor

    • Switched Teller data sources to new endpoints and reworked data pipeline for more reliable pool data.
    • Improved pool metrics calculations for TVL, APY, LTV, supply, and borrow values.
    • Enhanced data validation and filtering to remove invalid or very low-value pools.
    • Added token info normalization (symbols/decimals) with fallbacks for unknown tokens.
  • New Features

    • Added support for direct (non-pool) lending/collateral entries.

… status keywords for more accurate accounting
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

The Teller adaptor was rewritten to use Ormi Labs subgraph endpoints and The Graph non-pool endpoints, adding token metadata fetching, price lookups, enrichment calculations (tvlUsd, apy, ltv, etc.), filtering guardrails, and outputting separate Lending and Collateral pool objects per source pool.

Changes

Cohort / File(s) Summary
Teller Adaptor Data Pipeline Overhaul
src/adaptors/teller/index.js
Replaced Satsuma endpoints with Ormi Labs v1/v2 and direct The Graph endpoints. Added axios-based GraphQL querying, fetchTokenInfo (per-token timeout and UNKNOWN fallback), topLvl orchestration, price fetching, enrichment of metrics (tvlUsd, totalSupplyUsd, totalBorrowUsd, ltv, apyBase, borrowApy), non-pool loan handling, filtering guardrails (UNKNOWN tokens, invalid LTV, low TVL), and transformation into dual Lending/Collateral pool objects. Preserved export shape (timetravel: false, apy: main).

Sequence Diagram(s)

sequenceDiagram
    participant Main as Main Process
    participant Subgraph as Ormi/TheGraph Subgraphs
    participant TokenInfo as Token Info Resolver
    participant PriceSvc as Price Lookup Service
    participant Enricher as Enrichment & Transform
    participant Output as Filtered Output

    Main->>Subgraph: Query pools (v2 & v1) and non-pool bids
    Subgraph-->>Main: Pool and bid data (token addresses, amounts)

    Main->>TokenInfo: Resolve symbols & decimals (per-token, timeout)
    TokenInfo-->>Main: Token metadata (or UNKNOWN)

    Main->>PriceSvc: Request prices for principal & collateral tokens
    PriceSvc-->>Main: Prices in USD

    Main->>Enricher: Enrich pools (calculate tvlUsd, totalSupplyUsd, totalBorrowUsd, ltv, apyBase, borrowApy)
    Enricher->>Enricher: Apply guardrails (skip UNKNOWN, invalid LTV, low TVL)
    Enricher->>Enricher: Produce Lending & Collateral objects per pool

    Enricher-->>Output: Emit filtered pool objects
    Output-->>Main: Aggregated result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hopping through subgraphs, I fetch each token's name,

Prices hum like clover, numbers never the same.
Pools split in two — lending and collateral cheer,
Unknowns weeded out, the ledger grows clear.
A rabbit's wink: enrichment springs near! 🎋

🚥 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 accurately captures the main objective: updating subgraph endpoints due to Satsuma deprecation, which aligns with the primary change of replacing Satsuma-based endpoints with Ormi Labs and Graph endpoints.
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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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

🤖 Fix all issues with AI agents
In `@src/adaptors/teller/index.js`:
- Around line 220-230: The lending pool object is setting tvlUsd to
p.totalSupplyUsd which double-counts borrowed assets; replace that assignment
with the previously computed tvlUsd (the value computed as totalSupplyUsd −
totalBorrowUsd) so lendingPool.tvlUsd uses the net TVL, or if the intent was to
report gross supply, rename the property or add a clear comment; update the
tvlUsd field in the lendingPool construction (const lendingPool = { ... tvlUsd:
... }) to reference the computed tvlUsd variable instead of p.totalSupplyUsd.
- Line 217: The variable `chain` is computed but never used; either remove the
unused declaration `const chain = chainString === 'ethereum' ? 'mainnet' :
chainString;` or, if the intent was to normalize `ethereum` to `mainnet` for
downstream URL/resolve logic, replace the downstream uses of `chainString` with
`chain` (e.g., in any URL construction or resolution code that expects
"mainnet") so the normalized value is actually consumed and the unused-variable
lint error is resolved.
- Around line 280-316: The main function collects pool entries from
pools_v2_endpoints and pools_v1_endpoints causing duplicate pool IDs when the
same group_pool_address exists in both; add deduplication before returning
filteredData by iterating filteredData and using a Set of seen
group_pool_address values (or the composite pool id) to keep only the first
occurrence from v2 (ensure v2 is processed before v1 as in the current loops)
and skip duplicates from v1, then return the deduplicated array; reference main,
pools_v2_endpoints, pools_v1_endpoints, topLvl, filteredData, and
group_pool_address to locate where to apply this change.
🧹 Nitpick comments (4)
src/adaptors/teller/index.js (4)

21-45: Pagination: first: 1000 silently truncates results.

If any chain ever has more than 1000 pools, data will be silently lost. Consider either paginating (using skip) or at least logging a warning when the result count equals 1000 so you know you've hit the cap.


48-79: Sequential token info fetching is a performance bottleneck; also remove commented-out code.

Each token address is fetched one-by-one with a 1.5s timeout. With many unique tokens across pools, this serialization adds significant latency. Consider batching with Promise.all (or Promise.allSettled to handle individual failures gracefully).

Also, line 64 has commented-out dead code that should be removed.

♻️ Parallel token info fetching
 const fetchTokenInfo = async (tokenAddresses, chainString) => {
   const tokens = {};
-  for (const address of tokenAddresses) {
-    try {
-      const timeoutPromise = new Promise((_, reject) =>
-        setTimeout(() => reject(new Error('Token fetch timeout')), 1500)
-      );
-      const tokenInfoPromise = sdk.api.erc20.info(address, chainString);
-      const tokenInfo = await Promise.race([tokenInfoPromise, timeoutPromise]);
-
-    //  const tokenInfo = await sdk.api.erc20.info(address, chainString);
-      tokens[address.toLowerCase()] = {
-        symbol: tokenInfo.output.symbol,
-        decimals: Number(tokenInfo.output.decimals) || 18,
-      };
-      console.log(`Successfully fetched ${tokenInfo.output.symbol} for ${address}`);
-    } catch (error) {
-      console.warn(`failure to fetch token info ${address}, ${chainString}`);
-      tokens[address.toLowerCase()] = { symbol: 'UNKNOWN', decimals: 18 };
-    }
-  }
+  const results = await Promise.allSettled(
+    tokenAddresses.map(async (address) => {
+      const timeoutPromise = new Promise((_, reject) =>
+        setTimeout(() => reject(new Error('Token fetch timeout')), 3000)
+      );
+      const tokenInfo = await Promise.race([
+        sdk.api.erc20.info(address, chainString),
+        timeoutPromise,
+      ]);
+      return { address, tokenInfo };
+    })
+  );
+  for (const result of results) {
+    if (result.status === 'fulfilled') {
+      const { address, tokenInfo } = result.value;
+      tokens[address.toLowerCase()] = {
+        symbol: tokenInfo.output.symbol,
+        decimals: Number(tokenInfo.output.decimals) || 18,
+      };
+    } else {
+      // Extract address from the rejected promise context isn't straightforward,
+      // so consider wrapping to always resolve with {address, error/data}.
+    }
+  }
+  // Fill in any missing addresses as UNKNOWN
+  for (const address of tokenAddresses) {
+    if (!tokens[address.toLowerCase()]) {
+      tokens[address.toLowerCase()] = { symbol: 'UNKNOWN', decimals: 18 };
+    }
+  }
   return tokens;
 };

107-122: Per-pool price fetching inside Promise.all may cause excessive API calls.

utils.getPrices is called once per pool, but many pools likely share the same principal/collateral tokens. Consider collecting all unique token addresses upfront and making a single batched price call, then distributing the results. This would reduce API load and improve performance.


252-278: Yield functions are correct but defined after their call sites.

calculateActiveLenderYield and calculateActiveBorrowerYield are referenced on lines 169 and 171 but defined on lines 253 and 267. This works at runtime because topLvl is only called from main() (after module initialization), but it harms readability. Consider moving these function definitions above topLvl for clarity.

}
}
const underlyingTokens = [p.principal_token_address, p.collateral_token_address];
const chain = chainString === 'ethereum' ? 'mainnet' : chainString;
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

Unused variable chain.

chain is computed but never referenced — chainString is used everywhere downstream (lines 218, 222, 234). Either this is dead code to remove, or there was an intent to use chain somewhere (e.g., in the URL for mainnet resolution). If the URL is intentionally using chainString as-is, just remove the unused line.

Proposed fix
-    const chain = chainString === 'ethereum' ? 'mainnet' : chainString;
     const url = `https://app.teller.org/${chainString}/lend/pool/${p.group_pool_address}`;
📝 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
const chain = chainString === 'ethereum' ? 'mainnet' : chainString;
const url = `https://app.teller.org/${chainString}/lend/pool/${p.group_pool_address}`;
🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` at line 217, The variable `chain` is computed
but never used; either remove the unused declaration `const chain = chainString
=== 'ethereum' ? 'mainnet' : chainString;` or, if the intent was to normalize
`ethereum` to `mainnet` for downstream URL/resolve logic, replace the downstream
uses of `chainString` with `chain` (e.g., in any URL construction or resolution
code that expects "mainnet") so the normalized value is actually consumed and
the unused-variable lint error is resolved.

Comment on lines +220 to +230
const lendingPool = {
pool: p.group_pool_address + "-lending",
chain: utils.formatChain(chainString),
project: 'teller',
symbol: p.principalSymbol,
poolMeta: p.collateralSymbol,
tvlUsd: Number(p.totalSupplyUsd.toFixed(4)),
apyBase: p.apyBase,
underlyingTokens,
url,
};
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

Lending pool tvlUsd uses totalSupplyUsd instead of the computed tvlUsd (supply − borrows).

Line 163 correctly computes tvlUsd = totalSupplyUsd - totalBorrowUsd, but line 226 sets the lending pool's tvlUsd to totalSupplyUsd. This means the lending pool's TVL double-counts tokens that are currently borrowed. If DefiLlama expects TVL to exclude outstanding borrows for lending pools, this would overstate TVL.

Was this intentional? If so, consider renaming or adding a comment. If not:

Proposed fix if TVL should exclude borrows
     const lendingPool = {
       pool: p.group_pool_address + "-lending",
       chain: utils.formatChain(chainString),
       project: 'teller',
       symbol: p.principalSymbol,
       poolMeta: p.collateralSymbol,
-      tvlUsd: Number(p.totalSupplyUsd.toFixed(4)),
+      tvlUsd: Number(p.tvlUsd.toFixed(4)),
       apyBase: p.apyBase,
       underlyingTokens,
       url,
     };
📝 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
const lendingPool = {
pool: p.group_pool_address + "-lending",
chain: utils.formatChain(chainString),
project: 'teller',
symbol: p.principalSymbol,
poolMeta: p.collateralSymbol,
tvlUsd: Number(p.totalSupplyUsd.toFixed(4)),
apyBase: p.apyBase,
underlyingTokens,
url,
};
const lendingPool = {
pool: p.group_pool_address + "-lending",
chain: utils.formatChain(chainString),
project: 'teller',
symbol: p.principalSymbol,
poolMeta: p.collateralSymbol,
tvlUsd: Number(p.tvlUsd.toFixed(4)),
apyBase: p.apyBase,
underlyingTokens,
url,
};
🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` around lines 220 - 230, The lending pool object
is setting tvlUsd to p.totalSupplyUsd which double-counts borrowed assets;
replace that assignment with the previously computed tvlUsd (the value computed
as totalSupplyUsd − totalBorrowUsd) so lendingPool.tvlUsd uses the net TVL, or
if the intent was to report gross supply, rename the property or add a clear
comment; update the tvlUsd field in the lendingPool construction (const
lendingPool = { ... tvlUsd: ... }) to reference the computed tvlUsd variable
instead of p.totalSupplyUsd.

Comment on lines +280 to +316
const main = async (timestamp = null) => {
let data = [];


for (const [chain, url] of Object.entries(pools_v2_endpoints)) {
if (!url) {
console.log(`Skipping v2 data for ${chain} - no URL configured`);
continue;
}
try {
console.log(`Fetching v2 data for ${chain}...`);
const chainData = await topLvl(chain, url, query, timestamp);
data.push(...chainData);
} catch (err) {
console.log(chain, err);
}
}

for (const [chain, url] of Object.entries(pools_v1_endpoints)) {
if (!url) {
console.log(`Skipping v1 data for ${chain} - no URL configured`);
continue;
}
try {
console.log(`Fetching v1 data for ${chain}...`);
const chainData = await topLvl(chain, url, query, timestamp);
data.push(...chainData);
} catch (err) {
console.log(chain, err);
}
for (const [chain, url] of Object.entries(pools_v1_endpoints)) {
if (!url) {
console.log(`Skipping v1 data for ${chain} - no URL configured`);
continue;
}
try {
console.log(`Fetching v1 data for ${chain}...`);
const chainData = await topLvl(chain, url, query, timestamp);
data.push(...chainData);
} catch (err) {
console.log(chain, err);
}
}

console.log(`build filteredData ...`);
console.log(`build filteredData ...`);

const filteredData = data.filter((p) => utils.keepFinite(p));
return filteredData;
};
const filteredData = data.filter((p) => utils.keepFinite(p));
return filteredData;
};
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

🧩 Analysis chain

🏁 Script executed:

# Check if DefiLlama's utils or framework enforces unique pool IDs
rg -n 'pool.*unique|duplicate.*pool|pool.*already' --type=js -C 3 -g '!node_modules/**'

Repository: DefiLlama/yield-server

Length of output: 8722


🏁 Script executed:

# Read the specific file and lines mentioned in the review
sed -n '1,20p' src/adaptors/teller/index.js && echo "---" && sed -n '280,316p' src/adaptors/teller/index.js

Repository: DefiLlama/yield-server

Length of output: 2314


🏁 Script executed:

# Find the topLvl function to understand what data it returns
rg -n 'const topLvl|function topLvl' --type=js -A 30 -g '!node_modules/**'

Repository: DefiLlama/yield-server

Length of output: 50378


🏁 Script executed:

# Search for group_pool_address usage to understand pool ID generation
rg -n 'group_pool_address' --type=js -C 2 -g '!node_modules/**'

Repository: DefiLlama/yield-server

Length of output: 3348


🏁 Script executed:

# Find utils.keepFinite to understand what it filters
rg -n 'keepFinite' --type=js -C 5 -g '!node_modules/**'

Repository: DefiLlama/yield-server

Length of output: 50378


Duplicate pool IDs if same group_pool_address appears in both v1 and v2 subgraphs.

If a pool exists in both v1 and v2 endpoints, the code will emit duplicate pool IDs (e.g., "0xabc-lending" and "0xabc-collateral" appear twice). This violates the database UNIQUE constraint on the pool field and will cause insertion errors or test failures. Add deduplication logic (e.g., using a Set to track seen group_pool_address values, preferring v2 over v1) before returning filtered data.

🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` around lines 280 - 316, The main function
collects pool entries from pools_v2_endpoints and pools_v1_endpoints causing
duplicate pool IDs when the same group_pool_address exists in both; add
deduplication before returning filteredData by iterating filteredData and using
a Set of seen group_pool_address values (or the composite pool id) to keep only
the first occurrence from v2 (ensure v2 is processed before v1 as in the current
loops) and skip duplicates from v1, then return the deduplicated array;
reference main, pools_v2_endpoints, pools_v1_endpoints, topLvl, filteredData,
and group_pool_address to locate where to apply this change.

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: 4

🤖 Fix all issues with AI agents
In `@src/adaptors/teller/index.js`:
- Around line 487-500: The non-pool loop can call malformed endpoints when
GRAPH_API_KEY is undefined; add a guard to validate the API key and/or
constructed endpoints before calling topLvlNonPool. Specifically, in the main
flow where nonpool_endpoints are built (or at the start of main), check
process.env.GRAPH_API_KEY (or validate each endpoint string) and skip/log any
entry that would contain "undefined" or is missing the API key; update the loop
that iterates nonpool_endpoints and the endpoint construction logic to ensure
endpoints are valid before calling topLvlNonPool and to emit a clear warning
when GRAPH_API_KEY is not set.
- Around line 434-448: The pushed non-pool collateral object is using the
lending token symbol (`symbol: info.symbol`) which is incorrect; change the
`symbol` field in the object created in the `results.push` call to use the
collateral token's symbol (e.g., lookup the symbol for `addr` such as
`collateral.symbol` or from your token info map for the `addr`), falling back to
`info.symbol` if no collateral symbol exists; keep `underlyingTokens: [addr]`
and optionally update `poolMeta` to clarify it's collateral if you want an
additional hint.
- Around line 262-288: The two arrow-function constants
calculateActiveLenderYield and calculateActiveBorrowerYield are used inside
topLvl before their const declarations (causing a TDZ ReferenceError); fix by
relocating their definitions above topLvl (move both functions to before
topLvl’s declaration) or convert them into hoisted function declarations
(function calculateActiveLenderYield(...) { ... } and function
calculateActiveBorrowerYield(...) { ... }) and remove the duplicate definitions
later in the file so each function is defined once.
- Around line 21-27: Replace the hardcoded GRAPH_API_KEY value with a lookup
from an env var (e.g., process.env.THEGRAPH_API_KEY) and construct
nonpool_endpoints dynamically (use the GRAPH_API_KEY variable for template
strings) instead of committing secrets; ensure you remove the literal key from
the file and rotate the exposed key immediately. In addition, add a guard in the
main flow so that if process.env.THEGRAPH_API_KEY is undefined or empty the code
skips non-pool fetching (the nonpool_endpoints creation and any fetch logic that
uses nonpool_endpoints) to avoid runtime errors and accidental calls when the
key is absent; reference GRAPH_API_KEY, nonpool_endpoints, and main to locate
where to implement these changes.
🧹 Nitpick comments (3)
src/adaptors/teller/index.js (3)

58-89: Token info fetched sequentially — consider batching.

Each token is fetched one at a time with its own timeout. For chains with many unique tokens this becomes a serial bottleneck. Promise.allSettled with the same per-token timeout would parallelize the work. Not blocking, but worth noting for performance.


117-132: Per-pool price fetching inside Promise.all may cause rate-limit issues.

Every pool fires its own utils.getPrices call concurrently. With many pools this could hit rate limits. Consider deduplicating token addresses across all pools and making a single batch price call before the map, then looking up prices from the result.


314-324: No request timeout on axios.post — could hang indefinitely.

Add a timeout to prevent the adaptor from stalling if The Graph endpoint is unresponsive:

     const response = await axios.post(endpoint, {
       query: nonPoolQueryString,
       variables: { lastId },
-    });
+    }, { timeout: 30000 });

Comment on lines +21 to +27
const GRAPH_API_KEY = 'c2d64965ccdfcfed572cdb30b0369ab0';
const nonpool_endpoints = {
ethereum: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/4JruhWH1ZdwvUuMg2xCmtnZQYYHvmEq6cmTcZkpM6pW`,
base: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/8jSq7mzq9HEiJEcAZfvrTT4wYk59oMxm82xUpcVBzryF`,
arbitrum: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/F2Cgx4q4ATiopuZ13nr1EMKmZXwfAdevF3EujqfayK7a`,
polygon: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/BBp2ZJTG8j4sx9gLoFYN6iLCpWQsNpoiYjXNwRcE3DQr`,
};
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 | 🔴 Critical

Hardcoded API key committed to a public repository.

The Graph gateway API key is embedded directly in source code. This key is now exposed in git history and can be abused by anyone. Move it to an environment variable (e.g., process.env.THEGRAPH_API_KEY) and rotate the current key immediately.

Proposed fix
-const GRAPH_API_KEY = 'c2d64965ccdfcfed572cdb30b0369ab0';
+const GRAPH_API_KEY = process.env.THEGRAPH_API_KEY;
 const nonpool_endpoints = {
-  ethereum: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/4JruhWH1ZdwvUuMg2xCmtnZQYYHvmEq6cmTcZkpM6pW`,
-  base: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/8jSq7mzq9HEiJEcAZfvrTT4wYk59oMxm82xUpcVBzryF`,
-  arbitrum: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/F2Cgx4q4ATiopuZ13nr1EMKmZXwfAdevF3EujqfayK7a`,
-  polygon: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/BBp2ZJTG8j4sx9gLoFYN6iLCpWQsNpoiYjXNwRcE3DQr`,
+  ethereum: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/4JruhWH1ZdwvUuMg2xCmtnZQYYHvmEq6cmTcZkpM6pW`,
+  base: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/8jSq7mzq9HEiJEcAZfvrTT4wYk59oMxm82xUpcVBzryF`,
+  arbitrum: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/F2Cgx4q4ATiopuZ13nr1EMKmZXwfAdevF3EujqfayK7a`,
+  polygon: `https://gateway.thegraph.com/api/${GRAPH_API_KEY}/subgraphs/id/BBp2ZJTG8j4sx9gLoFYN6iLCpWQsNpoiYjXNwRcE3DQr`,
 };

Also guard against undefined in main so non-pool fetching is skipped gracefully when the env var isn't set.

🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 21-21: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` around lines 21 - 27, Replace the hardcoded
GRAPH_API_KEY value with a lookup from an env var (e.g.,
process.env.THEGRAPH_API_KEY) and construct nonpool_endpoints dynamically (use
the GRAPH_API_KEY variable for template strings) instead of committing secrets;
ensure you remove the literal key from the file and rotate the exposed key
immediately. In addition, add a guard in the main flow so that if
process.env.THEGRAPH_API_KEY is undefined or empty the code skips non-pool
fetching (the nonpool_endpoints creation and any fetch logic that uses
nonpool_endpoints) to avoid runtime errors and accidental calls when the key is
absent; reference GRAPH_API_KEY, nonpool_endpoints, and main to locate where to
implement these changes.

Comment on lines +262 to +288
// Calculate active yield for lenders
const calculateActiveLenderYield = (poolBorrowedPercent, interestRateLowerBound, interestRateUpperBound) => {
let poolYieldRaw;
if (poolBorrowedPercent === 0) {
poolYieldRaw = interestRateLowerBound;
} else if (poolBorrowedPercent === 1) {
poolYieldRaw = interestRateUpperBound;
} else {
const range = interestRateUpperBound - interestRateLowerBound;
poolYieldRaw = interestRateLowerBound + (poolBorrowedPercent * range);
}
return (poolYieldRaw / 100) * poolBorrowedPercent;
};

// Calculate active yield for borrowers
const calculateActiveBorrowerYield = (poolBorrowedPercent, interestRateLowerBound, interestRateUpperBound) => {
let poolYieldRaw;
if (poolBorrowedPercent === 0) {
poolYieldRaw = interestRateLowerBound;
} else if (poolBorrowedPercent === 1) {
poolYieldRaw = interestRateUpperBound;
} else {
const range = interestRateUpperBound - interestRateLowerBound;
poolYieldRaw = interestRateLowerBound + (poolBorrowedPercent * range);
}
return poolYieldRaw / 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 | 🔴 Critical

calculateActiveLenderYield and calculateActiveBorrowerYield are called before they are defined — ReferenceError at runtime.

These are const arrow-function expressions (lines 263, 277), which are not hoisted. They are first called on lines 179 and 181 inside topLvl, which executes before reaching their declarations. This will throw a ReferenceError due to the temporal dead zone (TDZ).

Move both functions above topLvl, or convert them to hoisted function declarations.

Proposed fix — move declarations before topLvl (before line 91)
+// Calculate active yield for lenders
+const calculateActiveLenderYield = (poolBorrowedPercent, interestRateLowerBound, interestRateUpperBound) => {
+  let poolYieldRaw;
+  if (poolBorrowedPercent === 0) {
+    poolYieldRaw = interestRateLowerBound;
+  } else if (poolBorrowedPercent === 1) {
+    poolYieldRaw = interestRateUpperBound;
+  } else {
+    const range = interestRateUpperBound - interestRateLowerBound;
+    poolYieldRaw = interestRateLowerBound + (poolBorrowedPercent * range);
+  }
+  return (poolYieldRaw / 100) * poolBorrowedPercent;
+};
+
+// Calculate active yield for borrowers
+const calculateActiveBorrowerYield = (poolBorrowedPercent, interestRateLowerBound, interestRateUpperBound) => {
+  let poolYieldRaw;
+  if (poolBorrowedPercent === 0) {
+    poolYieldRaw = interestRateLowerBound;
+  } else if (poolBorrowedPercent === 1) {
+    poolYieldRaw = interestRateUpperBound;
+  } else {
+    const range = interestRateUpperBound - interestRateLowerBound;
+    poolYieldRaw = interestRateLowerBound + (poolBorrowedPercent * range);
+  }
+  return poolYieldRaw / 100;
+};
+
 const topLvl = async (chainString, url, query, timestamp) => {

And remove the duplicate definitions at lines 262–288.

🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` around lines 262 - 288, The two arrow-function
constants calculateActiveLenderYield and calculateActiveBorrowerYield are used
inside topLvl before their const declarations (causing a TDZ ReferenceError);
fix by relocating their definitions above topLvl (move both functions to before
topLvl’s declaration) or convert them into hoisted function declarations
(function calculateActiveLenderYield(...) { ... } and function
calculateActiveBorrowerYield(...) { ... }) and remove the duplicate definitions
later in the file so each function is defined once.

Comment on lines +434 to +448
results.push({
pool: poolId + '-collateral',
chain: utils.formatChain(chainString),
project: 'teller',
symbol: p.collateralSymbol,
mintedCoin: p.principalSymbol,
tvlUsd: Number(p.totalCollateralUsd.toFixed(4)),
totalSupplyUsd: Number(p.totalCollateralUsd.toFixed(4)),
totalBorrowUsd: Number(p.totalBorrowUsd.toFixed(4)),
ltv: p.ltv,
apyBaseBorrow: p.borrowApy,
symbol: info.symbol,
poolMeta: 'Non-Pool Collateral',
tvlUsd: Number(totalCollateralUsd.toFixed(4)),
totalSupplyUsd: Number(totalCollateralUsd.toFixed(4)),
totalBorrowUsd: Number(totalBorrowUsd.toFixed(4)),
apyBaseBorrow: borrowApy,
apyBase: 0,
underlyingTokens,
url,
};
underlyingTokens: [addr],
url: appUrl,
});
}
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

Non-pool collateral entry uses the lending token symbol, not the collateral token symbol.

Line 438 sets symbol: info.symbol which is the lending token (e.g., USDC), but this entry represents locked collateral (e.g., ETH). This is misleading for users viewing the pool. Consider using the collateral token symbol(s) instead, or at minimum clarify via poolMeta.

🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` around lines 434 - 448, The pushed non-pool
collateral object is using the lending token symbol (`symbol: info.symbol`)
which is incorrect; change the `symbol` field in the object created in the
`results.push` call to use the collateral token's symbol (e.g., lookup the
symbol for `addr` such as `collateral.symbol` or from your token info map for
the `addr`), falling back to `info.symbol` if no collateral symbol exists; keep
`underlyingTokens: [addr]` and optionally update `poolMeta` to clarify it's
collateral if you want an additional hint.

Comment on lines +487 to +500
// Fetch non-pool (direct P2P) loans from The Graph Protocol
for (const [chain, endpoint] of Object.entries(nonpool_endpoints)) {
if (!endpoint) {
console.log(`Skipping non-pool data for ${chain} - no URL configured`);
continue;
}
try {
console.log(`Fetching non-pool data for ${chain}...`);
const chainData = await topLvlNonPool(chain, endpoint);
data.push(...chainData);
} catch (err) {
console.log(`Non-pool ${chain}:`, err.message || err);
}
}
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

Non-pool endpoints will silently fail if GRAPH_API_KEY env var is undefined.

If you move the API key to an environment variable (per the earlier comment), GRAPH_API_KEY being undefined will produce malformed URLs like https://gateway.thegraph.com/api/undefined/subgraphs/.... The !endpoint guard on line 489 won't catch this. Add a check for the API key at the top of main or when constructing endpoints.

🤖 Prompt for AI Agents
In `@src/adaptors/teller/index.js` around lines 487 - 500, The non-pool loop can
call malformed endpoints when GRAPH_API_KEY is undefined; add a guard to
validate the API key and/or constructed endpoints before calling topLvlNonPool.
Specifically, in the main flow where nonpool_endpoints are built (or at the
start of main), check process.env.GRAPH_API_KEY (or validate each endpoint
string) and skip/log any entry that would contain "undefined" or is missing the
API key; update the loop that iterates nonpool_endpoints and the endpoint
construction logic to ensure endpoints are valid before calling topLvlNonPool
and to emit a clear warning when GRAPH_API_KEY is not set.

@0xkr3p
Copy link
Contributor

0xkr3p commented Mar 6, 2026

hi @ethereumdegen pls ensure the test passes before i can review the pr - thanks!

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