feat: add ScrubVault yield pools to scrub project (Kava USDt + Arbitrum USDC)#2564
feat: add ScrubVault yield pools to scrub project (Kava USDt + Arbitrum USDC)#2564gaspare100 wants to merge 3202 commits into
Conversation
* feat(MorphoBlue): adding morpho blue pools yields * feat(emtamorpho): adding mm + fixing Merlin comments * feat(emtamorpho): last change * feat(emtamorpho): typing * feat(metamorpho): using wadLibs * feat(metamorpho): tvl mm error fixed
* Sommelier: Add Turbo GHO * Edge case for apys * Sommelier: Add ETH Trend Growth vault * Add Turbo Steth * Add Turbo SOMM * Sommelier: Add Turbo EETH Vault * Sommelier: Add turbo steth 2 vault * Sommelier: Morpho Maxmimizer * Modify next minor version of vaults to handle new holding position logic * Add Turbo divETH & ETHx vaults * Sommelier Add Turbo eETH V2 * Stash changes for partial chain abstraction * Fix chain config issues * Holding postion chain abstraction * Finish abstracting away chains * Add real yield eth on arbitrum
* feat: add cream-lending stables yield adaptor * update cream-leanding adaptor
* Sommelier: Add Turbo GHO * Edge case for apys * Sommelier: Add ETH Trend Growth vault * Add Turbo Steth * Add Turbo SOMM * Sommelier: Add Turbo EETH Vault * Sommelier: Add turbo steth 2 vault * Sommelier: Morpho Maxmimizer * Modify next minor version of vaults to handle new holding position logic * Add Turbo divETH & ETHx vaults * Sommelier Add Turbo eETH V2 * Stash changes for partial chain abstraction * Fix chain config issues * Holding postion chain abstraction * Finish abstracting away chains * Add real yield eth on arbitrum * RYUSD-ARB --------- Co-authored-by: Phil <pbal@fordham.edu> Co-authored-by: Phil <30321052+philipjames44@users.noreply.github.com>
* IndigoProtocol/ Add Yields for Indigo Stability Pools - create appropriate functionality to get APY for each Stability Pool - fetch Prices/APR/AssetAnalytics/ADA price to USD - calculate TVL * IndigoProtocol/ Add Indigo Stability Pools - update response of TVL - update APY rewards * IndigoProtocol/ Add Indigo Stability Pools - update response of TVL - update APY rewards
…um USDC) Adds a yield adapter under src/adaptors/scrub/ tracking the two ScrubVault DepositVault pools, reusing the existing scrub protocol slug. ScrubVault is a delta-neutral managed vault. Users deposit USDt (Kava) or USDC (Arbitrum) and receive share tokens. Capital is deployed off-chain across CEXs and DEXs running a delta-neutral funding-rate and market-making strategy. Because funds are actively managed off-chain the vault contract holds only unprocessed pending deposits. The on-chain totalVaultValue variable is the authoritative AUM figure, updated via distributeRewards() each time PnL is settled back to the vault. TVL and APY here reflect assets under management, not tokens sitting in the contract. APY methodology: - Kava pool: rolling 30-day APR read from the live Kava subgraph (apr field in basis-points x100), falling back to on-chain RewardDistributed event computation if subgraph is unavailable. - Arbitrum pool: APR computed from RewardDistributed events via eth_getLogs, annualised over a 30-day window. Returns 0 until reward history accumulates. Pools: - 0x7bff6c730da681df03364c955b165576186370bc-kava (USDt, Kava) - 0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d-arbitrum (USDC, Arbitrum)
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 34 minutes and 8 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a new DefiLlama yield adapter for ScrubVault that exposes Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as External Caller
participant Adapter as Scrub Adapter
participant Vault as Vault Contract
participant Subgraph as Vault Subgraph
participant RPC as RPC Node
Caller->>Adapter: apy()
par Fetch TVL
Adapter->>Vault: totalVaultValue() (ABI)
Vault-->>Adapter: totalVaultValue
Adapter->>Adapter: convert to tvlUsd (stablecoin decimals)
and Fetch APY
Adapter->>Subgraph: query APR (if subgraphUrl)
alt Subgraph returns APR
Subgraph-->>Adapter: apr (scaled)
Adapter->>Adapter: convert to apyBase
else Subgraph missing/failed
Adapter->>RPC: eth_getLogs RewardDistributed (last ~30 days)
RPC-->>Adapter: logs
Adapter->>Adapter: decode rewards, filter positive, sum, annualize -> apyBase
end
end
Adapter->>Adapter: clamp & round apyBase
Adapter-->>Caller: return pools [{pool, chain, project, symbol, tvlUsd, apyBase, underlyingTokens, poolMeta, url}]
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/scrub/index.js`:
- Around line 91-107: In fetchAprFromSubgraph, guard against
malformed/non-finite apr values: after extracting aprRaw (from
data?.data?.vault?.apr) validate it using a finite-number check before returning
Number(aprRaw) / 100; if the converted value is NaN or not finite, return null
so fetchAprFromSubgraph always yields either a finite number or null (instead of
leaking NaN into apyBase).
🪄 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: 22c9b9e4-4884-4a16-96e4-5c7d47698542
📒 Files selected for processing (1)
src/adaptors/scrub/index.js
| const blocksWindow = Math.ceil(windowSec / avgBlockTime); | ||
|
|
||
| // Fetch latest block number | ||
| const latestResp = await axios.post(rpc, { |
There was a problem hiding this comment.
can you refactor to use defillama SDK?
maybe something similar to this:
Get latest block:
// Instead of:
const latestResp = await axios.post(rpc, {
jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [],
});
const latestBlock = parseInt(latestResp.data.result, 16);
// Use:
const currentBlock = await sdk.api.util.getLatestBlock(chain);
const latestBlock = currentBlock.number;
Fetch event logs:
// Instead of raw eth_getLogs via axios:
const logsResp = await axios.post(rpc, {
jsonrpc: '2.0', id: 2, method: 'eth_getLogs',
params: [{ fromBlock, toBlock, address, topics }],
});
// Use:
const logs = await sdk.getEventLogs({
target: vaultAddress,
eventAbi: 'event RewardDistributed(int256 rewardAmount, uint256 newTotalVaultValue, uint256 timestamp)',
fromBlock,
toBlock: latestBlock,
chain,
});
| const fromBlock = Math.max(0, latestBlock - blocksWindow); | ||
|
|
||
| // Fetch logs | ||
| const logsResp = await axios.post(rpc, { |
| apyBase: Math.round(apyBase * 100) / 100, // round to 2 dp | ||
| underlyingTokens: [vault.stablecoin], | ||
| poolMeta: vault.poolMeta, | ||
| url: 'https://invest.scrub.money/', |
There was a problem hiding this comment.
should be dynamic vault link e.g. https://invest.scrub.money/vault/chain/kava/0x7bff6c730da681df03364c955b165576186370bc
0xkr3p
left a comment
There was a problem hiding this comment.
hey @gaspare100 thanks for the PR, few changes needed and TVL adapter must be merged before approval
…R guard, dynamic URL
|
The aave-v3 adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/adaptors/scrub/index.js (2)
19-24: Keep APY methodology docs consistent with implemented denominator.Lines 20-24 say APR uses pre-reward TVL at window start, but Line 167 divides by current
tvlUsd. Please align comment text (or adjust calculation) to prevent semantic drift.Also applies to: 167-167
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/adaptors/scrub/index.js` around lines 19 - 24, Comment describes using the "pre-reward TVL at the start of that window" as the denominator for rolling 30-day APY but the implementation divides by the current tvlUsd; update the code or comment to match: either change the calculation to divide summed rewards by the stored preRewardTvl (or a captured tvlUsd at window start) before annualising, referencing the RewardDistributed handling and the variables rewardAmount and tvlUsd, or adjust the documentation text that mentions "pre-reward TVL" to state that the current tvlUsd is used as the denominator (and ensure the Kava `apr` note remains accurate).
83-90: PreferformatUnitswith finite checks for numeric conversions in reward/TVL calculations.Line 89 (
Number(output)) and Line 157 (rewardRaw.toNumber()) can lose precision on large values or throw exceptions. While Scrub's reward amounts are unlikely to exceed JavaScript's safe integer bounds (2^53) in practice, usingethers.utils.formatUnitswithNumber.isFinite()guards is more defensive and consistent with other adapters in the codebase. Note: Line 157 is already wrapped in try-catch, so exceptions silently return 0 APR; Line 89 has no such protection.Suggested refactor
async function fetchTvlUsd(chain, vaultAddress, decimals) { const { output } = await sdk.api.abi.call({ abi: TOTAL_VAULT_VALUE_ABI, target: vaultAddress, chain, }); - return Number(output) / 10 ** decimals; + const tvlUsd = Number(ethers.utils.formatUnits(output, decimals)); + return Number.isFinite(tvlUsd) ? tvlUsd : 0; } @@ - totalRewardUsd += rewardRaw.toNumber() / 10 ** decimals; + const rewardUsd = Number(ethers.utils.formatUnits(rewardRaw, decimals)); + if (!Number.isFinite(rewardUsd)) continue; + totalRewardUsd += rewardUsd;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/adaptors/scrub/index.js` around lines 83 - 90, Replace unsafe Number(...) and .toNumber() conversions with ethers.utils.formatUnits and finite checks: in fetchTvlUsd (function fetchTvlUsd) use formatUnits(output, decimals) then parseFloat and guard with Number.isFinite — return 0 if not finite; similarly replace rewardRaw.toNumber() usage (variable rewardRaw) with parseFloat(formatUnits(rewardRaw, rewardDecimals)) and check Number.isFinite before using the value to avoid precision loss or exceptions. Ensure ethers.utils.formatUnits is imported/available where these conversions occur.
🤖 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/scrub/index.js`:
- Around line 19-24: Comment describes using the "pre-reward TVL at the start of
that window" as the denominator for rolling 30-day APY but the implementation
divides by the current tvlUsd; update the code or comment to match: either
change the calculation to divide summed rewards by the stored preRewardTvl (or a
captured tvlUsd at window start) before annualising, referencing the
RewardDistributed handling and the variables rewardAmount and tvlUsd, or adjust
the documentation text that mentions "pre-reward TVL" to state that the current
tvlUsd is used as the denominator (and ensure the Kava `apr` note remains
accurate).
- Around line 83-90: Replace unsafe Number(...) and .toNumber() conversions with
ethers.utils.formatUnits and finite checks: in fetchTvlUsd (function
fetchTvlUsd) use formatUnits(output, decimals) then parseFloat and guard with
Number.isFinite — return 0 if not finite; similarly replace rewardRaw.toNumber()
usage (variable rewardRaw) with parseFloat(formatUnits(rewardRaw,
rewardDecimals)) and check Number.isFinite before using the value to avoid
precision loss or exceptions. Ensure ethers.utils.formatUnits is
imported/available where these conversions occur.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6a3949c2-861c-482f-a534-1480342f1a8e
📒 Files selected for processing (1)
src/adaptors/scrub/index.js
- Drop 30-day rolling APY approach - Compute APR from single most-recent daily RewardDistributed event formula: rewardAmount / prevTVL * 365 * 100 - Use sdk.api.util.getLogs for both chains (no axios) - Kava: 3-day window split into 5 parallel 10k-block SDK calls (Kava RPC caps eth_getLogs at 10k blocks per request) - Kava latest block via ethers.providers.JsonRpcProvider (sdk.api.util.getLatestBlock does not support Kava) - Arbitrum: single getLogs call; sdk.api.util.getLatestBlock works fine - Verified live: Kava TVL=$55,590 APY=15%, Arb TVL=$6,770 APY=15%
|
The aave-v3 adapter exports pools: Test Suites: 1 passed, 1 total |
|
please check now |
| const reward = parsed.args.rewardAmount; // int256, may be negative on a loss day | ||
| const newTvl = parsed.args.newTotalVaultValue; | ||
|
|
||
| if (reward.lte(0)) continue; |
There was a problem hiding this comment.
this could be misleading if we aren't including negative days right?
|
We always give positive rewards by averaging it out but we can for sure
also include the negative ones
…On Wed, 29 Apr 2026 at 10:58, kr3p ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/adaptors/scrub/index.js
<#2564 (comment)>
:
> + for (let s = fromBlock; s <= latestBlock; s += chunkSize) {
+ chunks.push([s, Math.min(s + chunkSize - 1, latestBlock)]);
+ }
+ const results = await Promise.all(chunks.map(([f, t]) => logsCall(f, t)));
+ return results.flat();
+}
+
+function computeDailyApr(logs, decimals) {
+ // Iterate newest-first to find the most recent positive reward event.
+ for (let i = logs.length - 1; i >= 0; i--) {
+ try {
+ const parsed = rewardIface.parseLog(logs[i]);
+ const reward = parsed.args.rewardAmount; // int256, may be negative on a loss day
+ const newTvl = parsed.args.newTotalVaultValue;
+
+ if (reward.lte(0)) continue;
this could be misleading if we aren't including negative days right?
—
Reply to this email directly, view it on GitHub
<#2564 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AETFHZNOFCC2S32M4JEAUBL4YHG4XAVCNFSM6AAAAACXPPDBXSVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHM2DCOJVHA3TOOBUHE>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
6304e2c to
1b939d6
Compare
|
Recreated as #2678 — same feature and same diff. The original branch was force-pushed during a local-history cleanup, which made this PR unreopenable per GitHub's force-push policy. |
Summary
Adds a yield adapter under
src/adaptors/scrub/tracking the two ScrubVault DepositVault pools, reusing the existing scrub protocol slug.What is ScrubVault?
ScrubVault is a delta-neutral managed vault where users deposit USDt (Kava) or USDC (Arbitrum) and receive share tokens. Capital is deployed off-chain across CEXs and DEXs running a delta-neutral funding-rate and market-making strategy.
Why funds are not sitting in the contract
The vault contract acts as an on-chain accounting and settlement layer. Once deposits are processed, stablecoins move to the strategy wallet and are actively deployed on exchanges. The on-chain
totalVaultValueis the authoritative AUM figure, updated viadistributeRewards()each time PnL is settled. TVL and APY here reflect assets under management, not tokens in the contract.APY methodology
aprfield, basis-points ×100), with on-chainRewardDistributedevent fallback.RewardDistributedevents viaeth_getLogs, annualised over a 30-day window. Returns 0 until reward history accumulates (vault is newly deployed).Pools
0x7bff6c730da681df03364c955b165576186370bc-kava0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d-arbitrumNote: the Arbitrum pool is below the $10k display threshold today — it will appear automatically once TVL grows.
Test results
All 16 adapter tests pass (
npm run test --adapter=scrub).Summary by CodeRabbit