Add Quiver Protocol adapter (Robinhood Chain)#2835
Conversation
📝 WalkthroughWalkthroughAdds a DefiLlama adapter for Quiver Protocol vaults on Robinhood chain. It enumerates v3/v4 vaults, calculates concentrated and idle token balances, values harvest fees, annualizes net fees into APY, and exports adapter metadata. ChangesQuiver Protocol adapter
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant DefiLlama
participant QuiverVaults
participant HarvestEvents
participant PriceOracle
DefiLlama->>QuiverVaults: Enumerate vaults and read strategy state
DefiLlama->>QuiverVaults: Read idle and concentrated token balances
DefiLlama->>HarvestEvents: Fetch trailing Harvest events
DefiLlama->>PriceOracle: Get token USD prices
HarvestEvents-->>DefiLlama: Return harvested fees
PriceOracle-->>DefiLlama: Return token valuations
DefiLlama-->>DefiLlama: Apply fee split and annualize APY
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
|
The quiver-protocol 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/quiver-protocol/index.js (2)
169-172: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRedundant binary search to resolve
toBlock.
blockAtTimestamp(now)starts its search fromhi = await provider.getBlockNumber(), and since block timestamps are always ≤ wall-clock time, the loop will simply converge back to that samehi— spending O(log n) sequentialgetBlockround-trips to re-derive a number already known.toBlockcan just be the latest block number directly (e.g. haveblockAtTimestampoptionally short-circuit, or hoist a singleprovider.getBlockNumber()call and reuse it), avoiding the extra RPC round trips on every adapter run.♻️ Possible fix
+const latestBlockNumber = async () => sdk.getProvider(SDK_CHAIN).getBlockNumber(); + const [fromBlock, toBlock] = await Promise.all([ blockAtTimestamp(windowStart).then((b) => Math.max(b, FACTORY_DEPLOY_BLOCK)), - blockAtTimestamp(now), + latestBlockNumber(), ]);🤖 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/quiver-protocol/index.js` around lines 169 - 172, Update the block-range construction around blockAtTimestamp and toBlock to avoid binary-searching the current time: fetch the latest block number once with provider.getBlockNumber() and reuse it as toBlock, while retaining the timestamp lookup and FACTORY_DEPLOY_BLOCK clamping for fromBlock.
208-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded epoch duplicates
FACTORY_DEPLOY_BLOCK's date and is recomputed per vault for no reason.
1784198950is an undocumented magic number that corresponds to roughly the same 2026-07-16 date already captured in theFACTORY_DEPLOY_BLOCKcomment (line 14) — two independent sources of truth for the same fact. If this literal ever drifts from the real deploy timestamp, every vault'sapyBaseduring the ramp-up window is skewed with no obvious signal why. It's also identical for every vault yet recomputed inside the per-vault.map()callback (lines 186-228).Consider deriving it once (e.g.
(await provider.getBlock(FACTORY_DEPLOY_BLOCK)).timestamp) and hoisting the constant/computation outside the loop.🤖 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/quiver-protocol/index.js` around lines 208 - 215, Replace the hardcoded epoch in the per-vault APY calculation with the timestamp derived from FACTORY_DEPLOY_BLOCK, obtaining it once before the vault .map() callback and reusing it for every vault. Update the windowDaysEffective calculation to reference this shared deploy timestamp while preserving the existing clamping behavior.
🤖 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/quiver-protocol/index.js`:
- Around line 44-52: Update the multiCall helper to use permitFailure: true and
filter failed or missing outputs before mapping results; also revise the
Promise.all wrapping sdk.getEventLogs calls to use Promise.allSettled and retain
only fulfilled results. Preserve successful vault/strategy data so a single RPC
or pool failure does not abort the adapter feed.
---
Nitpick comments:
In `@src/adaptors/quiver-protocol/index.js`:
- Around line 169-172: Update the block-range construction around
blockAtTimestamp and toBlock to avoid binary-searching the current time: fetch
the latest block number once with provider.getBlockNumber() and reuse it as
toBlock, while retaining the timestamp lookup and FACTORY_DEPLOY_BLOCK clamping
for fromBlock.
- Around line 208-215: Replace the hardcoded epoch in the per-vault APY
calculation with the timestamp derived from FACTORY_DEPLOY_BLOCK, obtaining it
once before the vault .map() callback and reusing it for every vault. Update the
windowDaysEffective calculation to reference this shared deploy timestamp while
preserving the existing clamping behavior.
🪄 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: f32d2c1d-43b9-486f-ad2f-1f50bf8066a0
📒 Files selected for processing (1)
src/adaptors/quiver-protocol/index.js
| const call = (target, abi, params = []) => | ||
| sdk.api.abi | ||
| .call({ target, abi, params, chain: SDK_CHAIN }) | ||
| .then((r) => r.output); | ||
|
|
||
| const multiCall = (calls, abi) => | ||
| sdk.api.abi | ||
| .multiCall({ calls, abi, chain: SDK_CHAIN, permitFailure: false }) | ||
| .then((r) => r.output.map((o) => o.output)); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file='src/adaptors/quiver-protocol/index.js'
echo '--- FILE OUTLINE ---'
ast-grep outline "$file" --view expanded || true
echo
echo '--- LINES 1-260 ---'
nl -ba "$file" | sed -n '1,260p'
echo
echo '--- SEARCH permitFailure / getEventLogs / Promise.all ---'
rg -n "permitFailure|getEventLogs|Promise\.all|multiCall\(" "$file" .Repository: DefiLlama/yield-server
Length of output: 1021
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file='src/adaptors/quiver-protocol/index.js'
echo '--- FILE SIZE ---'
wc -l "$file"
echo
echo '--- LINES 35-240 WITH NUMBERS ---'
awk 'NR>=35 && NR<=240 { printf "%4d %s\n", NR, $0 }' "$file"
echo
echo '--- SEARCH IN FILE ---'
rg -n "permitFailure|getEventLogs|Promise\.all|multiCall\(|call\(" "$file"Repository: DefiLlama/yield-server
Length of output: 9335
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- permitFailure true patterns ---'
rg -n "permitFailure:\s*true" src
echo
echo '--- multiCall failure handling patterns ---'
rg -n "r\.output|\.filter\(.+output|permitFailure" src | sed -n '1,220p'Repository: DefiLlama/yield-server
Length of output: 50378
🌐 Web query:
@defillama/sdk abi multiCall permitFailure false rejects failed calls documentation
💡 Result:
In the @defillama/sdk, the permitFailure: false configuration (which is typically the default behavior) ensures that if any individual call within a multiCall operation fails, the entire multiCall execution throws an error [1][2]. This approach is used when data integrity is critical, as it prevents the adapter from processing partial or potentially incorrect data [2]. By setting permitFailure: false (or omitting the property), you enforce "strict" behavior: - If a single RPC request or contract call within the batch fails, the SDK will halt and return an error for the entire operation [1][3]. - This is preferred in scenarios where missing or failed data would lead to inaccurate results, such as incorrect TVL calculations or flawed de-duplication logic [2][4]. Conversely, setting permitFailure: true allows the multiCall to succeed even if some individual calls fail [1][4]. In this case: - The SDK returns the results for the successful calls and typically returns null or an indication of failure for the unsuccessful ones [1][5]. - Developers often use this when dealing with unreliable contracts or volatile infrastructure, but it requires implementing proper null guards or validation logic to ensure that failed calls are handled correctly downstream [1][5]. When using permitFailure: true, you are responsible for filtering out or handling these failed entries to avoid propagating errors or publishing inflated/deflated data [1][5][4]. As indicated by recent development discussions, maintainers often prefer explicit blacklisting or pre-filtering of problematic contracts over relying solely on permitFailure: true, as the latter can sometimes mask underlying issues and lead to silent errors [4][3].
Citations:
- 1: fix(origami): tolerate per-vault read failures so TVL stops freezing DefiLlama-Adapters#19692
- 2: DefiLlama/DefiLlama-Adapters@d6182eb
- 3: fix: permit failure multicall on Krystal tvl DefiLlama-Adapters#15559
- 4: Fix: Contango-v2 DefiLlama-Adapters#12080
- 5: ersanyakit/DefiLlama-Adapters@77b359e
Make the fan-out calls partial-failure tolerant
multiCall(..., permitFailure: false) and the Promise.all around sdk.getEventLogs both fail the whole adapter on a single bad vault/strategy or RPC hiccup. Use permitFailure: true plus filtering, or Promise.allSettled, so one broken pool doesn’t drop the entire feed.
🤖 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/quiver-protocol/index.js` around lines 44 - 52, Update the
multiCall helper to use permitFailure: true and filter failed or missing outputs
before mapping results; also revise the Promise.all wrapping sdk.getEventLogs
calls to use Promise.allSettled and retain only fulfilled results. Preserve
successful vault/strategy data so a single RPC or pool failure does not abort
the adapter feed.
Adds a yields adapter for Quiver Protocol (protocolId 8239, Liquidity Manager on Robinhood Chain — TVL adapter and fees adapter already merged: DefiLlama/DefiLlama-Adapters#20112, DefiLlama/dimension-adapters#8247).
Methodology
tvlUsd: strategy position (liquidity between tick bounds at current pool tick; V4 via StateView) + idle strategy balances, priced via the DefiLlama price API.apyBase: depositor share (90%; perf-fee bps read live from FeeConfig) of gross LP fees from on-chainHarvestevents over the trailing 7d (clamped to protocol age), annualized against current TVL.coins.llama.fi/blockhas no Robinhood support yet, so the adapter binary-searches the block at the window start on-chain; sdk provider key isrobinhoodchain.Test
npm run test --adapter=quiver-protocol: 65 passed, 65 total. 10 pools returned; current TVLs are small (young protocol, factories deployed 2026-07-16) so pools surface in the UI as they cross the $10k floor.Summary by CodeRabbit