Skip to content

chainflip-lending: fix tvlUsd (was Supplied, should be available liquidity) and migrate to on-chain RPC#2762

Open
permapod-rsk wants to merge 1 commit into
DefiLlama:masterfrom
permapod-rsk:chainflip-lending-fix-tvl-apy
Open

chainflip-lending: fix tvlUsd (was Supplied, should be available liquidity) and migrate to on-chain RPC#2762
permapod-rsk wants to merge 1 commit into
DefiLlama:masterfrom
permapod-rsk:chainflip-lending-fix-tvl-apy

Conversation

@permapod-rsk

@permapod-rsk permapod-rsk commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

chainflip-lending: fix tvlUsd (was Supplied, should be available liquidity) and migrate to on-chain RPC

What this PR does

Fixes a methodology error in tvlUsd and migrates all data to the Chainflip
Substrate node RPC, removing the dependency on an external indexer API. APY is
now also computed from on-chain fields instead of being forwarded from a
third-party endpoint.


The bug — tvlUsd was reporting Supplied, not TVL

The previous adapter fetched pool.tvl from
https://explorer-service-processor.chainflip.io/defi-llama/yield.

That field maps to total_amount on-chain — the total amount supplied by
lenders
, including the portion already lent out to borrowers. In DefiLlama's
taxonomy that figure is Supplied (= TVL + Borrowed), not TVL.

For a pool with near-full utilisation the error is dramatic:

Pool Reported tvlUsd (Supplied ❌) Correct tvlUsd (Available ✅) Utilisation
USDT $480 671 $2 943 99.4 %
USDC $148 479 $4 046 97.3 %
BTC $1 293 303 $1 103 036 14.8 %
ETH $67 063 $66 847 0.5 %
SOL $59 $59 0 %

Total overstatement: ~$814 K across the five pools.

The correct value for tvlUsd is the idle liquidity in each pool —
available_amount from the on-chain RPC — which is exactly what yield
aggregators display as "available to borrow" or "pool liquidity".


Methodology alignment with Aave v3 and Compound v3

In Aave v3's yield-server adapter, tvlUsd for each reserve is the
underlying token balance held by the Pool contract (i.e. liquidity not yet
lent out). In Compound v3 it is totalSupply - totalBorrow per Comet market.
Both represent idle capital, not total deposits.

Chainflip's equivalent is pool.available_amount from cf_lending_pools:

tvlUsd = available_amount / 10^decimals × price

APY methodology

Supply APY is derived from two Permill fields (parts per million, 1 000 000 = 100 %)
returned by cf_lending_pools:

apyBase = (current_interest_rate / 1e6) × (utilisation_rate / 1e6) × 100

Per cf_lending_config, extra_interest = 0: the network takes no continuous
share of borrow interest. The 100 % of current_interest_rate accrues to
lenders. The formula is therefore the direct on-chain equivalent of Aave's
liquidityRate / RAY × 100.

The previous adapter forwarded the apy field from the external indexer API,
which includes one-shot origination fees (1 % per loan, 80 % to lenders)
accumulated over historical loan origination volume. That component cannot be
derived from a single chain snapshot — the same limitation that prevents Aave
adapters from including flash-loan fee revenue in apyBase. The on-chain
formula is conservative and will understate yield for pools with high
origination turnover (most visibly BTC at current low utilisation), but it is
the correct and auditable approach.


Data source

All data comes from a single RPC call to the Chainflip mainnet Substrate node:

POST https://rpc.chainflip.io
{ "method": "cf_lending_pools", "params": [null, null] }

Returns per-asset: available_amount, total_amount, current_interest_rate,
utilisation_rate — everything needed for both tvlUsd and apyBase.

Asset prices are fetched from coins.llama.fi using standard coingecko IDs
(bitcoin, ethereum, solana, tether, usd-coin). No external indexer is used.


Files changed

  • src/adaptors/chainflip-lending/index.ts — full rewrite

Test output (live chain data)

Pool                      tvlUsd        apyBase   utilisation
btc-chainflip-lending     $1 103 036    0.22 %    14.77 %
eth-chainflip-lending     $66 847       ~0 %       0.47 %
sol-chainflip-lending     $59           0 %        0 %
usdt-chainflip-lending    $2 943        22.29 %   99.39 %
usdc-chainflip-lending    $4 046        13.19 %   97.28 %

Note: BTC apyBase (0.22 %) reflects the instantaneous interest rate only.
Actual lender yield is higher due to origination fees on short-term loans, but
those require historical chain indexing to quantify — out of scope for a
snapshot adapter.

Summary by CodeRabbit

  • Bug Fixes
    • Updated Chainflip lending metrics to read directly from on-chain data, improving accuracy and freshness.
    • TVL now reflects available liquidity more precisely, with better handling for asset decimals.
    • APY calculations now use on-chain rate information instead of externally reported values.
    • Token display and pricing lookups have been made more reliable across supported assets.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Chainflip lending adapter is rewritten to query the Chainflip Substrate RPC (cf_lending_pools) instead of an external explorer indexer. A static ASSET_META table replaces indexer-provided metadata, a cfRpc helper handles RPC calls, TVL uses available_amount (idle liquidity), and APY is derived from on-chain Permill rate fields.

Chainflip Lending Adapter Rewrite

Layer / File(s) Summary
ASSET_META table and cfRpc helper
src/adaptors/chainflip-lending/index.ts
Adds a static mapping of Chainflip asset IDs to decimals, coingecko IDs, and token addresses, plus a cfRpc() helper for JSON-RPC POST requests. Module-level docs updated to describe new methodology.
getPools rewrite with on-chain TVL and APY
src/adaptors/chainflip-lending/index.ts
Rewrites getPools to fetch from cf_lending_pools RPC, batch price lookups via coins.llama.fi, compute tvlUsd from available_amount using BigInt precision, and compute apyBase from current_interest_rate and utilisation_rate Permill fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • DefiLlama/yield-server#2703: Also changes a lending adapter's tvlUsd to reflect idle/available liquidity rather than total supplied assets.

Suggested reviewers

  • 0xkr3p

🐇 From indexer to chain, the rabbit hops right,
available_amount gleams with idle delight.
Permill rates whisper APY's true song,
BigInt guards precision all day long.
No more explorer — on-chain is where we belong! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 title accurately summarizes the main change: correcting tvlUsd and switching the adapter to on-chain RPC data.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions

Copy link
Copy Markdown

The chainflip-lending adapter exports pools:

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

Nb of pools: 5
 

Sample pools:
┌─────────┬──────────────────────────┬────────────┬─────────────────────┬────────┬────────────────────┬─────────────────────┬────────────────────────────────────────────────┬──────────────────────────────────────────────────┐
│ (index) │ pool                     │ chain      │ project             │ symbol │ tvlUsd             │ apyBase             │ url                                            │ underlyingTokens                                 │
├─────────┼──────────────────────────┼────────────┼─────────────────────┼────────┼────────────────────┼─────────────────────┼────────────────────────────────────────────────┼──────────────────────────────────────────────────┤
│ 0       │ 'btc-chainflip-lending'  │ 'Bitcoin'  │ 'chainflip-lending' │ 'BTC'  │ 1103423.4108577785 │ 0.21792107260000002 │ 'https://scan.chainflip.io/pools/btc/lending'  │ [ 'coingecko:bitcoin' ]                          │
│ 1       │ 'eth-chainflip-lending'  │ 'Ethereum' │ 'chainflip-lending' │ 'ETH'  │ 66804.85509221249  │ 0.0000945051        │ 'https://scan.chainflip.io/pools/eth/lending'  │ [ 'coingecko:ethereum' ]                         │
│ 2       │ 'usdc-chainflip-lending' │ 'Ethereum' │ 'chainflip-lending' │ 'USDC' │ 4046.0111805159177 │ 13.186431561600001  │ 'https://scan.chainflip.io/pools/usdc/lending' │ [ '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' ] │
│ 3       │ 'usdt-chainflip-lending' │ 'Ethereum' │ 'chainflip-lending' │ 'USDT' │ 2942.4848040437596 │ 22.2913914986       │ 'https://scan.chainflip.io/pools/usdt/lending' │ [ '0xdAC17F958D2ee523a2206206994597C13D831ec7' ] │
│ 4       │ 'sol-chainflip-lending'  │ 'Solana'   │ 'chainflip-lending' │ 'SOL'  │ 59.01402767963954  │ 0                   │ 'https://scan.chainflip.io/pools/sol/lending'  │ [ 'coingecko:solana' ]                           │
└─────────┴──────────────────────────┴────────────┴─────────────────────┴────────┴────────────────────┴─────────────────────┴────────────────────────────────────────────────┴──────────────────────────────────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/chainflip-lending/index.ts (1)

104-106: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Route price lookups through the shared price helper.

This direct coins.llama.fi call bypasses utils.getPriceApiData, so deployments with DL_API_KEY won’t use the configured pro-api path from src/adaptors/utils.js.

Proposed refactor
-  const { data: priceData } = await axios.get(
-    `https://coins.llama.fi/prices/current/${cgIds}`
-  );
+  const priceData = await utils.getPriceApiData(`/prices/current/${cgIds}`);
🤖 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/chainflip-lending/index.ts` around lines 104 - 106, The
Chainflip lending price fetch currently calls coins.llama.fi directly, bypassing
the shared pricing flow. Update the price lookup in the Chainflip adaptor to use
utils.getPriceApiData instead of axios.get so deployments with DL_API_KEY follow
the configured pro-api path. Keep the logic localized around the price retrieval
in the index.ts adaptor and preserve the existing cgIds-based lookup 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/chainflip-lending/index.ts`:
- Around line 88-90: The cfRpc helper currently returns data.result
unconditionally, so JSON-RPC responses containing an error object but no result
will flow downstream as undefined and break onchainPools.map. Update cfRpc to
inspect the parsed axios response payload before returning, check for a JSON-RPC
error field, and throw or otherwise surface that error with its details instead
of returning result. Keep the fix localized to cfRpc in the chainflip-lending
adaptor so callers only receive valid RPC results.

---

Nitpick comments:
In `@src/adaptors/chainflip-lending/index.ts`:
- Around line 104-106: The Chainflip lending price fetch currently calls
coins.llama.fi directly, bypassing the shared pricing flow. Update the price
lookup in the Chainflip adaptor to use utils.getPriceApiData instead of
axios.get so deployments with DL_API_KEY follow the configured pro-api path.
Keep the logic localized around the price retrieval in the index.ts adaptor and
preserve the existing cgIds-based lookup 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: c99cc3db-c3ec-4d0b-a1a7-6094a8e2f722

📥 Commits

Reviewing files that changed from the base of the PR and between d8ed6b6 and 04b6681.

📒 Files selected for processing (1)
  • src/adaptors/chainflip-lending/index.ts

Comment on lines +88 to +90
async function cfRpc(method: string, params: unknown[] = []): Promise<any> {
const { data } = await axios.post(RPC_ENDPOINT, { jsonrpc: '2.0', method, params, id: 1 });
return data.result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle JSON-RPC error payloads before returning result.

A JSON-RPC call can fail with an error object while still returning HTTP 200; returning data.result unconditionally makes the downstream onchainPools.map(...) crash as undefined.

Proposed fix
 async function cfRpc(method: string, params: unknown[] = []): Promise<any> {
   const { data } = await axios.post(RPC_ENDPOINT, { jsonrpc: '2.0', method, params, id: 1 });
+  if (data?.error) {
+    const message = data.error.message ?? JSON.stringify(data.error);
+    throw new Error(`Chainflip RPC ${method} failed: ${message}`);
+  }
+  if (!data || !('result' in data)) {
+    throw new Error(`Chainflip RPC ${method} returned no result`);
+  }
   return data.result;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function cfRpc(method: string, params: unknown[] = []): Promise<any> {
const { data } = await axios.post(RPC_ENDPOINT, { jsonrpc: '2.0', method, params, id: 1 });
return data.result;
async function cfRpc(method: string, params: unknown[] = []): Promise<any> {
const { data } = await axios.post(RPC_ENDPOINT, { jsonrpc: '2.0', method, params, id: 1 });
if (data?.error) {
const message = data.error.message ?? JSON.stringify(data.error);
throw new Error(`Chainflip RPC ${method} failed: ${message}`);
}
if (!data || !('result' in data)) {
throw new Error(`Chainflip RPC ${method} returned no result`);
}
return data.result;
}
🤖 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/chainflip-lending/index.ts` around lines 88 - 90, The cfRpc
helper currently returns data.result unconditionally, so JSON-RPC responses
containing an error object but no result will flow downstream as undefined and
break onchainPools.map. Update cfRpc to inspect the parsed axios response
payload before returning, check for a JSON-RPC error field, and throw or
otherwise surface that error with its details instead of returning result. Keep
the fix localized to cfRpc in the chainflip-lending adaptor so callers only
receive valid RPC results.

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