Skip to content

feat: add ScrubVault yield pools to scrub project (Kava USDt + Arbitrum USDC)#2678

Open
gaspare100 wants to merge 6 commits into
DefiLlama:masterfrom
gaspare100:feat/scrubvault-yield
Open

feat: add ScrubVault yield pools to scrub project (Kava USDt + Arbitrum USDC)#2678
gaspare100 wants to merge 6 commits into
DefiLlama:masterfrom
gaspare100:feat/scrubvault-yield

Conversation

@gaspare100
Copy link
Copy Markdown
Contributor

@gaspare100 gaspare100 commented May 13, 2026

Replaces #2564, which had to be closed and recreated because the source branch was force-pushed (commit history was rewritten for hygiene; same feature, same diff).


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 totalVaultValue is the authoritative AUM figure, updated via distributeRewards() each time PnL is settled. TVL and APY here reflect assets under management, not tokens in the contract.

APY methodology

  • Kava pool: rolling 30-day APR read from the live Kava subgraph (apr field, basis-points ×100), with on-chain RewardDistributed event fallback.
  • Arbitrum pool: APR computed from RewardDistributed events via eth_getLogs, annualised over a 30-day window. Returns 0 until reward history accumulates (vault is newly deployed).

Pools

Pool ID Chain Token Live TVL Live APY
0x7bff6c730da681df03364c955b165576186370bc-kava Kava USDt ~$42k ~12.5%
0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d-arbitrum Arbitrum USDC ~$2.8k 0% (new)

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

  • New Features
    • Added ScrubVault yield adapter for Kava USDt and Arbitrum USDC, providing per-vault TVL and APY metrics (3-day window annualized), along with vault links and token info; failures for individual vaults are isolated to avoid impacting overall results.

Review Change Stack

Review Change Stack

theg added 4 commits May 13, 2026 23:34
…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)
- 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%
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Warning

Rate limit exceeded

@gaspare100 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 7 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 047468e3-807b-44fe-a76a-48e37232f261

📥 Commits

Reviewing files that changed from the base of the PR and between a3707f2 and fe5729f.

📒 Files selected for processing (1)
  • src/adaptors/scrub/index.js
📝 Walkthrough

Walkthrough

Adds a ScrubVault adapter that reads on-chain totalVaultValue, fetches RewardDistributed logs over a 3-day window (with optional chunking), computes APY from parsed events, and exports pool objects plus timetravel and url metadata.

Changes

ScrubVault Yield Adapter

Layer / File(s) Summary
Adapter configuration and data contracts
src/adaptors/scrub/index.js
Module header, dependencies, vault/project constants (Kava USDt, Arbitrum USDC), stablecoin decimals, block timing and RPC notes, ABI for totalVaultValue, and RewardDistributed event/topic.
Block utilities, TVL and log fetching
src/adaptors/scrub/index.js
getLatestBlockNumber via ethers JSON-RPC or SDK fallback, fetchTvlUsd calling totalVaultValue, and fetchRewardLogs retrieving RewardDistributed logs over a 3-day block window with optional chunked parallel requests and error-safe fallbacks.
APY computation logic
src/adaptors/scrub/index.js
Parses logs into reward/newTVL pairs, sums rewards (including negatives), derives denominator from earliest event (newTotalVaultValue - rewardAmount), annualizes over a fixed 3-day window, and returns a non-negative finite APY or 0.
Main apy export and wiring
src/adaptors/scrub/index.js
Orchestrates per-vault parallel TVL/log fetches, computes APY per vault, constructs pool metadata (id, chain, project, TVL, APY, token, URLs), isolates vault-level errors, and exports module.exports.apy, timetravel: false, and url.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • 0xkr3p

Poem

🐰 I nibble logs by moonlight bright,
Vaults whisper yields into the night,
Three days I count each scattered reward,
I annualize hops, then strike a chord,
Hooray — the APY is in sight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically describes the main change: adding ScrubVault yield pools for two specific deployments (Kava USDt and Arbitrum USDC) to the scrub project adapter.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@github-actions
Copy link
Copy Markdown

The scrub adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 16 passed, 16 total
Snapshots: 0 total
Time: 0.249 s
Ran all test suites.

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬─────────┬────────┬──────────────┬─────────┬──────────────────────────────────────────────────┬────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project │ symbol │ tvlUsd       │ apyBase │ underlyingTokens                                 │ poolMeta                   │ url                                                                                          │
├─────────┼───────────────────────────────────────────────────────┼────────────┼─────────┼────────┼──────────────┼─────────┼──────────────────────────────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ 0       │ '0x7bff6c730da681df03364c955b165576186370bc-kava'     │ 'Kava'     │ 'scrub' │ 'USDt' │ 82777.68156  │ 14.99   │ [ '0x919C1c267BC06a7039e03fcc2eF738525769109c' ] │ 'Delta Neutral USDt Vault' │ 'https://invest.scrub.money/vault/chain/kava/0x7bff6c730da681df03364c955b165576186370bc'     │
│ 1       │ '0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d-arbitrum' │ 'Arbitrum' │ 'scrub' │ 'USDC' │ 31784.507529 │ 14.99   │ [ '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' ] │ 'Delta Neutral USDC Vault' │ 'https://invest.scrub.money/vault/chain/arbitrum/0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴─────────┴────────┴──────────────┴─────────┴──────────────────────────────────────────────────┴────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (1)
src/adaptors/scrub/index.js (1)

158-187: ⚡ Quick win

Consider 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6e60745 and b32eae5.

📒 Files selected for processing (1)
  • src/adaptors/scrub/index.js

Comment thread src/adaptors/scrub/index.js Outdated
…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.
@github-actions
Copy link
Copy Markdown

The scrub adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 16 passed, 16 total
Snapshots: 0 total
Time: 0.262 s
Ran all test suites.

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬─────────┬────────┬──────────────┬─────────┬──────────────────────────────────────────────────┬────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project │ symbol │ tvlUsd       │ apyBase │ underlyingTokens                                 │ poolMeta                   │ url                                                                                          │
├─────────┼───────────────────────────────────────────────────────┼────────────┼─────────┼────────┼──────────────┼─────────┼──────────────────────────────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ 0       │ '0x7bff6c730da681df03364c955b165576186370bc-kava'     │ 'Kava'     │ 'scrub' │ 'USDt' │ 82777.68156  │ 15.1    │ [ '0x919C1c267BC06a7039e03fcc2eF738525769109c' ] │ 'Delta Neutral USDt Vault' │ 'https://invest.scrub.money/vault/chain/kava/0x7bff6c730da681df03364c955b165576186370bc'     │
│ 1       │ '0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d-arbitrum' │ 'Arbitrum' │ 'scrub' │ 'USDC' │ 31784.507529 │ 15      │ [ '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' ] │ 'Delta Neutral USDC Vault' │ 'https://invest.scrub.money/vault/chain/arbitrum/0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴─────────┴────────┴──────────────┴─────────┴──────────────────────────────────────────────────┴────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘

Copy link
Copy Markdown
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: 1

♻️ Duplicate comments (1)
src/adaptors/scrub/index.js (1)

95-100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use formatUnits for TVL conversion too.

This still converts a 256-bit on-chain value through Number(output), so large vaults can report a rounded or wrong tvlUsd even 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

📥 Commits

Reviewing files that changed from the base of the PR and between b32eae5 and a3707f2.

📒 Files selected for processing (1)
  • src/adaptors/scrub/index.js

Comment thread src/adaptors/scrub/index.js Outdated
- 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.
@github-actions
Copy link
Copy Markdown

The scrub adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 16 passed, 16 total
Snapshots: 0 total
Time: 0.271 s
Ran all test suites.

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬─────────┬────────┬──────────────┬─────────┬──────────────────────────────────────────────────┬────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project │ symbol │ tvlUsd       │ apyBase │ underlyingTokens                                 │ poolMeta                   │ url                                                                                          │
├─────────┼───────────────────────────────────────────────────────┼────────────┼─────────┼────────┼──────────────┼─────────┼──────────────────────────────────────────────────┼────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
│ 0       │ '0x7bff6c730da681df03364c955b165576186370bc-kava'     │ 'Kava'     │ 'scrub' │ 'USDt' │ 82777.68156  │ 15.1    │ [ '0x919C1c267BC06a7039e03fcc2eF738525769109c' ] │ 'Delta Neutral USDt Vault' │ 'https://invest.scrub.money/vault/chain/kava/0x7bff6c730da681df03364c955b165576186370bc'     │
│ 1       │ '0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d-arbitrum' │ 'Arbitrum' │ 'scrub' │ 'USDC' │ 31784.507529 │ 15      │ [ '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' ] │ 'Delta Neutral USDC Vault' │ 'https://invest.scrub.money/vault/chain/arbitrum/0x439a923517c4dfd3f3d0abb0c36e356d39cf3f9d' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴─────────┴────────┴──────────────┴─────────┴──────────────────────────────────────────────────┴────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘

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.

1 participant