feat: add ScrubVault yield pools to scrub project (Kava USDt + Arbitrum USDC)#2678
feat: add ScrubVault yield pools to scrub project (Kava USDt + Arbitrum USDC)#2678gaspare100 wants to merge 6 commits into
Conversation
…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)
…R guard, dynamic URL
- 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%
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ 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 ScrubVault adapter that reads on-chain ChangesScrubVault Yield Adapter
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 scrub adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/adaptors/scrub/index.js (1)
158-187: ⚡ Quick winConsider adding error handling for individual vault failures.
If data fetching fails for one vault (e.g., RPC timeout, chain downtime), the entire
apy()function throws and no pools are returned. Wrapping each vault iteration in a try-catch would allow the adapter to return data for the healthy vault(s) while gracefully skipping the failed one.♻️ Suggested resilience improvement
for (const [chainKey, vault] of Object.entries(VAULTS)) { + try { const latestBlock = await getLatestBlockNumber(chainKey, vault.rpcUrl); const [tvlUsd, logs] = await Promise.all([ fetchTvlUsd(chainKey, vault.address, vault.decimals), fetchRewardLogs( chainKey, vault.address, latestBlock, vault.blockTime, vault.logChunkSize, ), ]); const apyBase = computeDailyApr(logs, vault.decimals); const vaultUrl = `https://invest.scrub.money/vault/chain/${chainKey}/${vault.address.toLowerCase()}`; pools.push({ pool: `${vault.address}-${chainKey}`.toLowerCase(), chain: vault.chain, project: PROJECT, symbol: vault.symbol, tvlUsd, apyBase: Math.round(apyBase * 100) / 100, underlyingTokens: [vault.stablecoin], poolMeta: vault.poolMeta, url: vaultUrl, }); + } catch (error) { + console.error(`Failed to fetch data for ${vault.chain} vault:`, error.message); + // Continue to next vault + } }🤖 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/scrub/index.js` around lines 158 - 187, The loop over VAULTS in the apy() flow lacks per-vault error handling: wrap the body that calls getLatestBlockNumber, fetchTvlUsd, fetchRewardLogs, computeDailyApr and pools.push for each vault in a try-catch so a failure for one vault does not abort the entire function; in the catch log the error with identifying info (chainKey and vault.address) and continue to the next vault, ensuring apy() still returns data for healthy pools.
🤖 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/scrub/index.js`:
- Around line 139-140: Replace unsafe BigNumber.toNumber() divisions with
ethers.utils.formatUnits to avoid Number.MAX_SAFE_INTEGER overflow: import
formatUnits and compute rewardUsd by calling formatUnits(reward, decimals) and
converting to Number (e.g., Number(formatUnits(...))); likewise compute prevTvl
from newTvl.sub(reward) via formatUnits(newTvl.sub(reward), decimals). Update
the expressions that reference rewardUsd and prevTvl so they use these
formatUnits-based conversions instead of reward.toNumber()/10**decimals and
newTvl.sub(reward).toNumber()/10**decimals.
---
Nitpick comments:
In `@src/adaptors/scrub/index.js`:
- Around line 158-187: The loop over VAULTS in the apy() flow lacks per-vault
error handling: wrap the body that calls getLatestBlockNumber, fetchTvlUsd,
fetchRewardLogs, computeDailyApr and pools.push for each vault in a try-catch so
a failure for one vault does not abort the entire function; in the catch log the
error with identifying info (chainKey and vault.address) and continue to the
next vault, ensuring apy() still returns data for healthy pools.
🪄 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: 1ada2b52-a366-495e-9e9b-a5c7706bcd4c
📒 Files selected for processing (1)
src/adaptors/scrub/index.js
…th, per-vault error handling - Sum RewardDistributed events over the full 3-day window instead of taking the most recent positive event, so loss days reduce the reported APR and the figure reflects true rolling return. - Replace BigNumber.toNumber() with ethers.utils.formatUnits to avoid Number.MAX_SAFE_INTEGER overflow on large balances. - Wrap each vault iteration in try/catch so an RPC outage on one chain does not block the other chain's pool from being reported. Verified live: Kava USDt $82,777 @ 15.10% APR, Arbitrum USDC $31,784 @ 15.00% APR.
|
The scrub adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/adaptors/scrub/index.js (1)
95-100:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse
formatUnitsfor TVL conversion too.This still converts a 256-bit on-chain value through
Number(output), so large vaults can report a rounded or wrongtvlUsdeven though the APR path was already hardened against the same issue.Proposed fix
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; + return Number(ethers.utils.formatUnits(output, decimals)); }🤖 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/scrub/index.js` around lines 95 - 100, The TVL conversion currently coerces the 256-bit on-chain value via Number(output) which can overflow; replace that arithmetic with a safe big-number formatting call (e.g., ethers.utils.formatUnits) to convert output using the token's decimals. Locate the code that calls sdk.api.abi.call returning output (the TOTAL_VAULT_VALUE_ABI/vaultAddress call) and change the return to use formatUnits(output, decimals) (or the project's equivalent big-number formatter) and then coerce to Number only if/where safe; also add the appropriate import for formatUnits at the top of src/adaptors/scrub/index.js.
🤖 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/scrub/index.js`:
- Around line 107-116: The logsCall wrapper currently swallows RPC/SDK errors
with .catch(() => []), making network or SDK failures look like “no rewards”
(apyBase: 0); remove the catch or replace it with rethrow so errors propagate to
the outer per-vault try/catch instead of returning an empty array — update the
logsCall definition (the const logsCall = (from, to) =>
sdk.api.util.getLogs(...).then((r) => r.output || [])) so that it does not
silently swallow errors and lets the outer error handling handle failed log
fetches.
---
Duplicate comments:
In `@src/adaptors/scrub/index.js`:
- Around line 95-100: The TVL conversion currently coerces the 256-bit on-chain
value via Number(output) which can overflow; replace that arithmetic with a safe
big-number formatting call (e.g., ethers.utils.formatUnits) to convert output
using the token's decimals. Locate the code that calls sdk.api.abi.call
returning output (the TOTAL_VAULT_VALUE_ABI/vaultAddress call) and change the
return to use formatUnits(output, decimals) (or the project's equivalent
big-number formatter) and then coerce to Number only if/where safe; also add the
appropriate import for formatUnits at the top of src/adaptors/scrub/index.js.
🪄 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: 2209ad6e-e9c1-4f52-8d28-b3e12bf813ba
📒 Files selected for processing (1)
src/adaptors/scrub/index.js
- fetchTvlUsd now uses ethers.utils.formatUnits instead of Number(output) / 10**decimals — same overflow safety as the APR path. - Drop the inner .catch in fetchRewardLogs so RPC/SDK failures propagate to the per-vault try/catch in apy(). Previously, a network failure was indistinguishable from "no rewards" and the vault was emitted with apyBase: 0 instead of being skipped. Verified live: Kava USDt $82,777 @ 15.10% APR, Arbitrum USDC $31,784 @ 15.00% APR.
|
The scrub adapter exports pools: Test Suites: 1 passed, 1 total |
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