Update APY calculation for Kintsu#2655
Conversation
📝 WalkthroughWalkthroughThe Kintsu adaptor refactors from single-chain hardcoded constants to a modular multi-chain configuration with new utility functions for block resolution, price retrieval, and inception-based APY computation using share value calculations. ChangesKintsu Adaptor Refactoring
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
|
The kintsu adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/adaptors/kintsu/index.js (2)
92-92: 💤 Low valueConsider
Promise.allSettledonce more chains are added.With a single chain today this is fine, but
Promise.allrejects the entire adaptor if any one chain's RPC/price call fails. As soon as a second entry is added tochains, a transient failure on one chain will hide healthy data from the other.Promise.allSettled+ filtering fulfilled results would isolate per-chain failures.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adaptors/kintsu/index.js` at line 92, The apy function currently uses Promise.all which will reject the whole adaptor if any chainApy promise fails; change apy to use Promise.allSettled on Object.keys(chains).map(chainApy), then filter the settled results for status === "fulfilled" and extract their values (ignoring or logging rejected ones) so per-chain failures don't break successful chain results; reference functions/idents: apy, chainApy, chains.
61-69: 💤 Low valueRedundant on-chain reads for
totalPooled/totalSupplyatblockNow.
getShareValue(chain, vault, blockNow)(line 65) already fetchestotalPooledandtotalSupplyatblockNow, but you also issue a standalonetotalPooledcall on line 63. That doubles RPC traffic for the current-block reads. Consider returningtotalPooledfromgetShareValue(or refactoring to fetch the raw values once and derive bothsvNowandtvlUsdfrom them), so each block is queried only once.♻️ Sketch
-const getShareValue = async (chain, vault, block) => { - const [totalPooled, totalSupply] = await Promise.all([ - call(chain, vault, 'function totalPooled() view returns (uint96)', block), - call(chain, vault, 'erc20:totalSupply', block), - ]); - if (BigInt(totalSupply) === 0n) { - throw new Error(`RPC issue: zero totalSupply at block ${block}`); - } - return (BigInt(totalPooled) * SCALE) / BigInt(totalSupply); -}; +const getVaultState = async (chain, vault, block) => { + const [totalPooled, totalSupply] = await Promise.all([ + call(chain, vault, 'function totalPooled() view returns (uint96)', block), + call(chain, vault, 'erc20:totalSupply', block), + ]); + if (BigInt(totalSupply) === 0n) { + throw new Error(`RPC issue: zero totalSupply at block ${block}`); + } + const shareValue = (BigInt(totalPooled) * SCALE) / BigInt(totalSupply); + return { totalPooled, totalSupply, shareValue }; +};Then reuse
totalPooledfrom theblockNowresult instead of calling it again on line 63.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/adaptors/kintsu/index.js` around lines 61 - 69, The code is making a redundant on-chain read: you call call(..., 'function totalPooled()') to populate totalPooledNow while getShareValue(chain, vault, blockNow) already returns totalPooled/totalSupply; update getShareValue to include/return the raw totalPooled (and totalSupply) for the requested block and then change the Promise.all usage to remove the separate call(...) for totalPooledNow and instead extract totalPooledNow from the svNow result (or from the new getShareValue return shape), ensuring all places that used totalPooledNow/totalSupply now read those values from the svNow object and eliminating the duplicate RPC.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/adaptors/kintsu/index.js`:
- Around line 25-28: The getUsdPrice function currently assumes
r.data.coins[priceId] exists and will throw a vague TypeError when it's missing;
update getUsdPrice to validate the response after the axios.get call (inspect
r.data and r.data.coins[priceId]) and throw or reject with a clear, actionable
Error (including the requested priceId and whether coins or the specific entry
was missing) so callers can log/handle an explicit failure instead of a runtime
TypeError.
---
Nitpick comments:
In `@src/adaptors/kintsu/index.js`:
- Line 92: The apy function currently uses Promise.all which will reject the
whole adaptor if any chainApy promise fails; change apy to use
Promise.allSettled on Object.keys(chains).map(chainApy), then filter the settled
results for status === "fulfilled" and extract their values (ignoring or logging
rejected ones) so per-chain failures don't break successful chain results;
reference functions/idents: apy, chainApy, chains.
- Around line 61-69: The code is making a redundant on-chain read: you call
call(..., 'function totalPooled()') to populate totalPooledNow while
getShareValue(chain, vault, blockNow) already returns totalPooled/totalSupply;
update getShareValue to include/return the raw totalPooled (and totalSupply) for
the requested block and then change the Promise.all usage to remove the separate
call(...) for totalPooledNow and instead extract totalPooledNow from the svNow
result (or from the new getShareValue return shape), ensuring all places that
used totalPooledNow/totalSupply now read those values from the svNow object and
eliminating the duplicate RPC.
🪄 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: 370df34e-3e2a-43db-b15a-e04188745149
📒 Files selected for processing (1)
src/adaptors/kintsu/index.js
| const getUsdPrice = (priceId) => | ||
| axios | ||
| .get(`https://coins.llama.fi/prices/current/${priceId}`) | ||
| .then((r) => r.data.coins[priceId].price); |
There was a problem hiding this comment.
Guard against missing price in getUsdPrice response.
If the coin is unknown to coins.llama.fi (typo, new chain not yet indexed, transient lookup miss), r.data.coins[priceId] will be undefined and .price will throw a TypeError with a non-actionable message. Validate the response and surface a clear error so adaptor failures are easier to diagnose.
🛡️ Proposed fix
-const getUsdPrice = (priceId) =>
- axios
- .get(`https://coins.llama.fi/prices/current/${priceId}`)
- .then((r) => r.data.coins[priceId].price);
+const getUsdPrice = (priceId) =>
+ axios
+ .get(`https://coins.llama.fi/prices/current/${priceId}`)
+ .then((r) => {
+ const coin = r.data?.coins?.[priceId];
+ if (!coin || typeof coin.price !== 'number') {
+ throw new Error(`Price not available for ${priceId}`);
+ }
+ return coin.price;
+ });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/adaptors/kintsu/index.js` around lines 25 - 28, The getUsdPrice function
currently assumes r.data.coins[priceId] exists and will throw a vague TypeError
when it's missing; update getUsdPrice to validate the response after the
axios.get call (inspect r.data and r.data.coins[priceId]) and throw or reject
with a clear, actionable Error (including the requested priceId and whether
coins or the specific entry was missing) so callers can log/handle an explicit
failure instead of a runtime TypeError.
Summary by CodeRabbit
New Features
Refactor