feat: adapter for Spectra MetaVaults#2465
Conversation
📝 WalkthroughWalkthroughAdds a new Spectra MetaVaults adapter that concurrently fetches chain-specific APY data, constructs mvId and reward entries, filters visible vaults with finite TVL, normalizes results to APY objects, sorts by TVL descending, and exports Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(200,200,255,0.5)
participant Client
end
rect rgba(200,255,200,0.5)
participant Adapter
end
rect rgba(255,200,200,0.5)
participant SpectraAPI
end
rect rgba(255,255,200,0.5)
participant Processor
end
Client->>Adapter: call apy()
Adapter->>SpectraAPI: concurrent GET /chain-endpoint (x-client-id)
SpectraAPI-->>Adapter: per-chain responses
Adapter->>Processor: flatten responses, filter VISIBLE, compute mvId/rewards, normalize
Processor-->>Adapter: APY objects
Adapter-->>Client: sorted APY list (by TVL)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Error while running spectra-metavaults adapter: Test Suites: 1 failed, 1 total |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/adaptors/spectra-metavaults/index.js (1)
53-59: Add explicit timeout to axios requests to prevent indefinite waits.These requests inherit axios defaults without a timeout. Axios v1.7.2 defaults to
timeout: 0(no timeout), so a stalled chain endpoint will blockPromise.allindefinitely, preventing the adapter from returning data. Add a per-request timeout:Suggested fix
axios .get(api(chainId), { headers: { 'x-client-id': 'defillama', }, + timeout: 10_000, })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/adaptors/spectra-metavaults/index.js` around lines 53 - 59, The axios GET calls created inside the Object.keys(chains).map(...) (the axios.get(api(chainId), { headers: { 'x-client-id': 'defillama' } }) block) currently have no timeout and can hang; update the per-request config to include an explicit timeout (e.g., timeout: 5000) so each request fails after a reasonable period, or replace the direct axios.get calls with a pre-configured axios instance that sets a default timeout; ensure the timeout is added alongside the existing headers in the request config for the API calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/adaptors/spectra-metavaults/index.js`:
- Around line 53-59: The axios GET calls created inside the
Object.keys(chains).map(...) (the axios.get(api(chainId), { headers: {
'x-client-id': 'defillama' } }) block) currently have no timeout and can hang;
update the per-request config to include an explicit timeout (e.g., timeout:
5000) so each request fails after a reasonable period, or replace the direct
axios.get calls with a pre-configured axios instance that sets a default
timeout; ensure the timeout is added alongside the existing headers in the
request config for the API calls.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5050de61-08ee-46cb-ba62-ca917f47fa37
📒 Files selected for processing (1)
src/adaptors/spectra-metavaults/index.js
|
Error while running spectra-metavaults adapter: Test Suites: 1 failed, 1 total |
|
Error while running spectra-metavaults adapter: Test Suites: 1 failed, 1 total |
|
The spectra-metavaults adapter exports pools: Test Suites: 1 passed, 1 total |
|
The spectra-metavaults adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/adaptors/spectra-metavaults/index.js (2)
54-65: Consider handling individual chain API failures gracefully.If one chain's API request fails,
Promise.allwill reject entirely and no pools will be returned. UsingPromise.allSettledwould allow partial results when some chains are unavailable.♻️ Proposed resilient fetch pattern
const mvs = await Promise.all( Object.keys(chains).map((chainId) => axios .get(api(chainId), { headers: { 'x-client-id': 'defillama', }, timeout: 10_000, }) .then((res) => res.data.flat()) + .catch((err) => { + console.error(`Failed to fetch chain ${chainId}:`, err.message); + return []; + }) ) ).then((res) => res.flat());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/adaptors/spectra-metavaults/index.js` around lines 54 - 65, The current Promise.all call that builds mvs will reject if any chain request fails; replace it with Promise.allSettled over Object.keys(chains).map(...) so you can collect fulfilled responses only, flatten them and ignore or log rejected ones (include chainId and error). Update references around mvs and the axios/api call to process only entries with status === 'fulfilled', flatten their .value results, and optionally console.error or processLogger.warn rejected results to aid debugging while returning partial pool data.
67-72: Unnecessary.flat()call on line 70.
mvApyreturns a single object per MetaVault, not an array. The.flat()call after.map(mvApy)has no effect and can be removed for clarity.♻️ Proposed cleanup
const apys = mvs .filter((mv) => mv.status === 'VISIBLE') // only include live MetaVaults .map(mvApy) - .flat() .filter((i) => utils.keepFinite(i)) // skip MetaVaults with no TVL (e.g. missing price) .sort((a, b) => b.tvlUsd - a.tvlUsd);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/adaptors/spectra-metavaults/index.js` around lines 67 - 72, The chain building the apys array uses .map(mvApy).flat() even though mvApy returns a single object per MetaVault; remove the unnecessary .flat() to avoid a no-op and clarify intent. Update the expression that defines apys (which filters mvs by mv.status === 'VISIBLE', maps with mvApy, filters with utils.keepFinite, and sorts by tvlUsd) to drop the .flat() call so the pipeline becomes filter -> map(mvApy) -> filter(utils.keepFinite) -> sort.
🤖 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/spectra-metavaults/index.js`:
- Line 40: The call to mv.metadata.title.replace(' ', '-') only replaces the
first space so symbol generation can leave spaces; update the expression used
inside utils.formatSymbol to replace all spaces — e.g., use
mv.metadata.title.replaceAll(' ', '-') (if Node target supports it) or
mv.metadata.title.replace(/ /g, '-') (or mv.metadata.title.split(' ').join('-'))
so multi-word titles like "Meta Vault USDC" become "Meta-Vault-USDC"; update the
symbol: utils.formatSymbol(...) spot accordingly.
- Line 47: The code accesses mv.underlying.address without null-safety which can
throw if mv.underlying is undefined; update the construction of underlyingTokens
(the array assigned to underlyingTokens) to use optional chaining on
mv.underlying (e.g., mv.underlying?.address) and ensure downstream consumers can
handle undefined values (filter out falsy entries or provide a default) so
malformed API responses don't cause a TypeError.
---
Nitpick comments:
In `@src/adaptors/spectra-metavaults/index.js`:
- Around line 54-65: The current Promise.all call that builds mvs will reject if
any chain request fails; replace it with Promise.allSettled over
Object.keys(chains).map(...) so you can collect fulfilled responses only,
flatten them and ignore or log rejected ones (include chainId and error). Update
references around mvs and the axios/api call to process only entries with status
=== 'fulfilled', flatten their .value results, and optionally console.error or
processLogger.warn rejected results to aid debugging while returning partial
pool data.
- Around line 67-72: The chain building the apys array uses .map(mvApy).flat()
even though mvApy returns a single object per MetaVault; remove the unnecessary
.flat() to avoid a no-op and clarify intent. Update the expression that defines
apys (which filters mvs by mv.status === 'VISIBLE', maps with mvApy, filters
with utils.keepFinite, and sorts by tvlUsd) to drop the .flat() call so the
pipeline becomes filter -> map(mvApy) -> filter(utils.keepFinite) -> sort.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8b833141-61cb-4ee0-97f6-4c9ba04766ad
📒 Files selected for processing (1)
src/adaptors/spectra-metavaults/index.js
| pool: mvId(mv.address, mv.chainId), | ||
| chain: utils.formatChain(chain.name), | ||
| project: 'spectra-metavaults', | ||
| symbol: utils.formatSymbol(`${mv.metadata.title.replace(' ', '-')}`), |
There was a problem hiding this comment.
replace() only replaces the first space.
String.prototype.replace() with a string argument replaces only the first occurrence. If the title contains multiple spaces (e.g., "Meta Vault USDC"), the result would be "Meta-Vault USDC" instead of "Meta-Vault-USDC".
🐛 Proposed fix to replace all spaces
- symbol: utils.formatSymbol(`${mv.metadata.title.replace(' ', '-')}`),
+ symbol: utils.formatSymbol(`${mv.metadata.title.replace(/ /g, '-')}`),Alternatively, use replaceAll(' ', '-') if targeting Node.js 15+.
📝 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.
| symbol: utils.formatSymbol(`${mv.metadata.title.replace(' ', '-')}`), | |
| symbol: utils.formatSymbol(`${mv.metadata.title.replace(/ /g, '-')}`), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/adaptors/spectra-metavaults/index.js` at line 40, The call to
mv.metadata.title.replace(' ', '-') only replaces the first space so symbol
generation can leave spaces; update the expression used inside
utils.formatSymbol to replace all spaces — e.g., use
mv.metadata.title.replaceAll(' ', '-') (if Node target supports it) or
mv.metadata.title.replace(/ /g, '-') (or mv.metadata.title.split(' ').join('-'))
so multi-word titles like "Meta Vault USDC" become "Meta-Vault-USDC"; update the
symbol: utils.formatSymbol(...) spot accordingly.
| rewardTokens: Object.values(mv.liveApy?.details?.rewardTokens || {}).map( | ||
| (t) => t.address | ||
| ), | ||
| underlyingTokens: [mv.underlying.address], |
There was a problem hiding this comment.
Missing null safety on mv.underlying.
If mv.underlying is undefined (e.g., malformed API response), accessing .address will throw a TypeError. Add optional chaining for defensive handling.
🛡️ Proposed fix
- underlyingTokens: [mv.underlying.address],
+ underlyingTokens: mv.underlying?.address ? [mv.underlying.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.
| underlyingTokens: [mv.underlying.address], | |
| underlyingTokens: mv.underlying?.address ? [mv.underlying.address] : [], |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/adaptors/spectra-metavaults/index.js` at line 47, The code accesses
mv.underlying.address without null-safety which can throw if mv.underlying is
undefined; update the construction of underlyingTokens (the array assigned to
underlyingTokens) to use optional chaining on mv.underlying (e.g.,
mv.underlying?.address) and ensure downstream consumers can handle undefined
values (filter out falsy entries or provide a default) so malformed API
responses don't cause a TypeError.
This PR implements a basic yield adapter for Spectra MetaVaults, containing TVL, base & rewards APYs.
Summary by CodeRabbit