Skip to content

canto-lending: report available liquidity as TVL#2769

Open
permapod-rsk wants to merge 5 commits into
DefiLlama:masterfrom
permapod:pr-2746-review-fix
Open

canto-lending: report available liquidity as TVL#2769
permapod-rsk wants to merge 5 commits into
DefiLlama:masterfrom
permapod:pr-2746-review-fix

Conversation

@permapod-rsk

@permapod-rsk permapod-rsk commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This updates the canto-lending adapter so that the displayed tvlUsd represents the liquidity currently available in the market, while keeping the broader lending accounting fields (totalSupplyUsd, totalBorrowUsd,
availableBorrowUsd) explicit.

Motivation

For lending markets, the value shown as TVL should reflect the liquidity that is actually available in the pool/reserve, consistent with the way Aave v3 exposes reserve liquidity and with the intended lending-market display
semantics.

Previously, canto-lending computed:

tvlUsd = totalSupplyUsd - totalBorrowUsd

Since totalSupplyUsd was derived from cash + borrows - reserves, this effectively reduced to available cash minus reserves. That made the displayed TVL less directly tied to the onchain available liquidity value.

What changed

  • tvlUsd is now calculated directly from the market's onchain getCash() value:
    • tvlUsd = getCash * underlyingPrice
  • availableBorrowUsd is set to the same available liquidity value.
  • totalSupplyUsd and totalBorrowUsd are still exported separately for lending analytics.
  • apyBase and apyBaseBorrow continue to come from onchain supplyRatePerBlock and borrowRatePerBlock, now using a Compound-style compounded APY calculation.
  • Reward APY calculation was split into a dedicated helper so base APY and reward APY are not mixed in the same function.

Expected result

For Canto Lending markets, DefiLlama will now display TVL as the amount of liquidity currently available to withdraw/borrow from the market, while preserving the full lending context through:

  • totalSupplyUsd
  • totalBorrowUsd
  • availableBorrowUsd
  • apyBase
  • apyBaseBorrow

Summary by CodeRabbit

  • New Features
    • Added support for Valdora Finance staking APY and TVL data on ZIGChain.
    • Improved Canto lending APY and reward yield calculations for more accurate returns.
  • Bug Fixes
    • Updated available liquidity and reward APY calculations to better handle invalid or non-positive values.

- Switch to Promise.allSettled for validator commission fetching so that
  a single unresponsive validator does not abort the entire APY pipeline;
  failed results are filtered out and the average is computed over the
  remaining ones (returns 0 if all fail)
- Deduct the 10% performance fee and the 2% community tax from the APR
  formula; the displayed APY now reflects net yield for the staker
- Add pricePerShare field by querying the exchange_rate from the staker
  contract, covering the stZIG→ZIG conversion ratio for liquid assets
- Validator addresses remain hardcoded for now: the chain does not expose
  a public endpoint to enumerate Valdora's curated validator set
The contract does not expose an exchange_rate query endpoint,
causing HTTP 500 errors. Compute pricePerShare from
funds_raised / total_supply instead.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The canto-lending adaptor's APY, reward APY, and TVL calculations were refactored into new helper functions (calculateApy, calculateRewardApy, calculateAvailableTvl) and wired into getApy. A new valdora-finance adaptor module was added, implementing APY computation via LCD/contract queries for the ZIGChain stZIG staking pool.

Changes

Canto Lending APY Refactor

Layer / File(s) Summary
New APY/TVL/reward helper functions
src/adaptors/canto-lending/index.js
Replaces price/TVL-based calculateApy with a block-compounding version, adds calculateRewardApy with null guard for non-finite/non-positive totals, and adds calculateAvailableTvl computed from cash instead of supply-minus-borrow.
getApy integration of new helpers
src/adaptors/canto-lending/index.js
Updates getApy to derive tvlUsd/availableBorrowUsd from calculateAvailableTvl and reward APYs from calculateRewardApy using comp speeds and wCantoUsd.

Valdora Finance Adaptor

Layer / File(s) Summary
Valdora Finance adaptor implementation
src/adaptors/valdora-finance/index.js
Adds constants for chain/contract/fees, helpers for LCD fetch, CosmWasm contract queries, validator commission averaging, and stZIG APR calculation; apy fetches contract and price data, validates numeric inputs, and returns a pool descriptor; exports protocolId, timetravel, url, and apy.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant apy as Valdora apy()
  participant Contract as CosmWasm Contract
  participant LCD as LCD Endpoints
  participant Price as ZIG Price API

  apy->>Contract: query funds_raised, total_supply
  apy->>LCD: fetch mint annual provisions
  apy->>LCD: fetch staking pool bonded tokens
  apy->>LCD: fetch validator commission rates
  apy->>Price: fetch current ZIG price
  apy->>apy: compute stZIG APR, tvlUsd, pricePerShare
  apy-->>apy: return filtered pool object
Loading

Suggested reviewers: 0xkr3p

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 Canto lending change: TVL now reflects available liquidity.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

The canto-lending adapter exports pools:

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

Nb of pools: 10
 

Sample pools:
┌─────────┬────────────────────────────────────────────────────┬─────────┬─────────────────┬───────────────┬────────────────────┬────────────────────┬───────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────────────┬───────────────────┬─────────────────┬──────────────────────────────────────────────┬────────────────────┬───────────────────┬────────────────────┬──────┬────────────┐
│ (index) │ pool                                               │ chain   │ project         │ symbol        │ tvlUsd             │ apyBase            │ apyReward │ underlyingTokens                                 │ rewardTokens                                     │ apyBaseBorrow     │ apyRewardBorrow │ borrowToken                                  │ totalSupplyUsd     │ totalBorrowUsd    │ availableBorrowUsd │ ltv  │ borrowable │
├─────────┼────────────────────────────────────────────────────┼─────────┼─────────────────┼───────────────┼────────────────────┼────────────────────┼───────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┼───────────────────┼─────────────────┼──────────────────────────────────────────────┼────────────────────┼───────────────────┼────────────────────┼──────┼────────────┤
│ 0       │ '0xde59f060d7ee2b612e7360e6c1b97c4d8289ca2e-canto' │ 'Canto' │ 'canto-lending' │ 'USDC'        │ 1349705.9934045828 │ 14.581621489828889 │ null      │ [ '0x80b5a32E4F032B2a058b4F29EC95EEfEEB87aDcd' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 44.61680145413665 │ null            │ '0x80b5a32E4F032B2a058b4F29EC95EEfEEB87aDcd' │ 2138873.441011394  │ 789167.4476068108 │ 1349705.9934045828 │ 0.85 │ true       │
│ 1       │ '0xf1f89df149bc5f2b6b29783915d1f9fe2d24459c-canto' │ 'Canto' │ 'canto-lending' │ 'fBILL'       │ 63539.741094192854 │ 0                  │ null      │ [ '0x79ECCE8E2D17603877Ff15BC29804CbCB590EC08' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x79ECCE8E2D17603877Ff15BC29804CbCB590EC08' │ 63539.741094192854 │ 0                 │ 63539.741094192854 │ 0.9  │ true       │
│ 2       │ '0xd6a97e43fc885a83e97d599796458a331e580800-canto' │ 'Canto' │ 'canto-lending' │ 'NOTE/USDC'   │ 29205.560721730642 │ 0                  │ null      │ [ '0x9571997a66D63958e1B3De9647C22bD6b9e7228c' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x9571997a66D63958e1B3De9647C22bD6b9e7228c' │ 29205.560721730642 │ 0                 │ 29205.560721730642 │ 0    │ true       │
│ 3       │ '0xb49a395b39a0b410675406bee7bd06330cb503e3-canto' │ 'Canto' │ 'canto-lending' │ 'ETH/WCANTO'  │ 28186.904539324387 │ 0                  │ null      │ [ '0x216400ba362d8FCE640085755e47075109718C8B' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x216400ba362d8FCE640085755e47075109718C8B' │ 28186.904539324387 │ 0                 │ 28186.904539324387 │ 0    │ true       │
│ 4       │ '0x6b46ba92d7e94ffa658698764f5b8dfd537315a9-canto' │ 'Canto' │ 'canto-lending' │ 'USDT'        │ 14304.984920391084 │ 4.632869682793483  │ null      │ [ '0xd567B3d7B8FE3C79a1AD8dA978812cfC4Fa05e75' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 23.71313243797144 │ null            │ '0xd567B3d7B8FE3C79a1AD8dA978812cfC4Fa05e75' │ 18172.495803032794 │ 3867.51088264171  │ 14304.984920391084 │ 0.85 │ true       │
│ 5       │ '0x897709fc83ba7a4271d22ed4c01278cc1da8d6f8-canto' │ 'Canto' │ 'canto-lending' │ 'ifBILL'      │ 13959.785241845066 │ 0                  │ null      │ [ '0x45bafad5a6a531Bc18Cf6CE5B02C58eA4D20589b' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x45bafad5a6a531Bc18Cf6CE5B02C58eA4D20589b' │ 13959.785241845066 │ 0                 │ 13959.785241845066 │ 0.9  │ true       │
│ 6       │ '0xc0d6574b2fe71eed8cd305df0da2323237322557-canto' │ 'Canto' │ 'canto-lending' │ 'WCANTO/ATOM' │ 5682.850683550666  │ 0                  │ null      │ [ '0x30838619C55B787BafC3A4cD9aEa851C1cfB7b19' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x30838619C55B787BafC3A4cD9aEa851C1cfB7b19' │ 5682.850683550666  │ 0                 │ 5682.850683550666  │ 0    │ true       │
│ 7       │ '0x830b9849e7d79b92408a86a557e7baaacbec6030-canto' │ 'Canto' │ 'canto-lending' │ 'ETH'         │ 1950.8270092852365 │ 0                  │ null      │ [ '0x5FD55A1B9FC24967C4dB09C513C3BA0DFa7FF687' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x5FD55A1B9FC24967C4dB09C513C3BA0DFa7FF687' │ 1950.8270092852365 │ 0                 │ 1950.8270092852365 │ 0    │ true       │
│ 8       │ '0xf0cd6b5ce8a01d1b81f1d8b76643866c5816b49f-canto' │ 'Canto' │ 'canto-lending' │ 'NOTE/USDT'   │ 1399.8648338181704 │ 0                  │ null      │ [ '0x35DB1f3a6A6F07f82C76fCC415dB6cFB1a7df833' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0x35DB1f3a6A6F07f82C76fCC415dB6cFB1a7df833' │ 1399.8648338181704 │ 0                 │ 1399.8648338181704 │ 0    │ true       │
│ 9       │ '0x617383f201076e7ce0f6e625d1a983b3d1bd277a-canto' │ 'Canto' │ 'canto-lending' │ 'ATOM'        │ 3.663395183232669  │ 0                  │ null      │ [ '0xecEEEfCEE421D8062EF8d6b4D814efe4dc898265' ] │ [ '0x826551890Dc65655a0Aceca109aB11AbDbD7a07B' ] │ 0                 │ null            │ '0xecEEEfCEE421D8062EF8d6b4D814efe4dc898265' │ 3.663395183232669  │ 0                 │ 3.663395183232669  │ 0    │ true       │
└─────────┴────────────────────────────────────────────────────┴─────────┴─────────────────┴───────────────┴────────────────────┴────────────────────┴───────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────────────┴───────────────────┴─────────────────┴──────────────────────────────────────────────┴────────────────────┴───────────────────┴────────────────────┴──────┴────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@permapod-rsk

Copy link
Copy Markdown
Contributor Author

FYI the front end is dead but the protocol is still alive.

@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: 2

🧹 Nitpick comments (1)
src/adaptors/valdora-finance/index.js (1)

16-21: 🎯 Functional Correctness | 🔵 Trivial

Hardcoded validator list for commission averaging.

The average commission is computed only from these 4 fixed validator addresses (Lines 16-21), independent of the staker contract's actual current delegations. If the protocol rebalances delegations to different/additional validators, this average will silently drift from reality without any code change trigger.

🤖 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/valdora-finance/index.js` around lines 16 - 21, The commission
averaging in the Valdora adaptor is tied to a hardcoded VALIDATORS list instead
of the staker contract’s live delegations. Update the logic in
src/adaptors/valdora-finance/index.js so the average commission is derived from
the current validator set fetched from the contract or chain state, and keep
VALIDATORS only as a fallback if needed. Make sure the code path that computes
the average uses the dynamic validator data rather than the fixed array.
🤖 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/canto-lending/index.js`:
- Around line 218-223: The APY math in calculateApy (and the related
calculateRewardApy path) is using a hardcoded 6-second BLOCK_TIME, which should
instead come from chain configuration. Update the annualization logic to read
the block interval from the existing Canto chain config or a shared config
source, and make sure both APY helpers use that dynamic value consistently.

In `@src/adaptors/valdora-finance/index.js`:
- Around line 39-45: `getAverageValidatorCommission` and `getStzigApr` are
silently defaulting to 0 when upstream data is missing or all validator queries
fail, unlike `apy()` which throws on invalid input. Update these helpers to
detect empty/failed results as an error condition and throw a descriptive
failure instead of returning 0, using the existing functions
`getAverageValidatorCommission`, `getStzigApr`, and `apy` as the consistency
points for the behavior change.

---

Nitpick comments:
In `@src/adaptors/valdora-finance/index.js`:
- Around line 16-21: The commission averaging in the Valdora adaptor is tied to
a hardcoded VALIDATORS list instead of the staker contract’s live delegations.
Update the logic in src/adaptors/valdora-finance/index.js so the average
commission is derived from the current validator set fetched from the contract
or chain state, and keep VALIDATORS only as a fallback if needed. Make sure the
code path that computes the average uses the dynamic validator data rather than
the fixed array.
🪄 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: 1f82851c-bc15-4532-805b-4dfafd10a367

📥 Commits

Reviewing files that changed from the base of the PR and between 3feea3d and e46bc83.

📒 Files selected for processing (2)
  • src/adaptors/canto-lending/index.js
  • src/adaptors/valdora-finance/index.js

Comment on lines +218 to +223
function calculateApy(rate) {
const BLOCK_TIME = 6;
const YEARLY_BLOCKS = (365 * 24 * 60 * 60) / BLOCK_TIME;
const safeTvl = tvl === 0 ? 1 : tvl;
const apy = (((rate / 1e18) * YEARLY_BLOCKS * price) / safeTvl) * 100;
return apy;

return (Math.pow(rate / 1e18 + 1, YEARLY_BLOCKS) - 1) * 100;
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect poolInfo output shape: confirm supplyRate/borrowRate/comp*Speeds are per-block, 1e18-scaled
fd -a 'canto' src/adaptors/canto-lending
rg -nP -C4 '\b(supplyRate|borrowRate|compSupplySpeeds|compBorrowSpeeds|supplyRatePerBlock|borrowRatePerBlock|getCash)\b' src/adaptors/canto-lending

Repository: DefiLlama/yield-server

Length of output: 9426


🌐 Web query:

Canto blockchain average block time seconds

💡 Result:

The Canto blockchain's average block time has evolved due to network upgrades. Historically, the Canto EVM maintained a block time of approximately 6 seconds [1][2]. Recent infrastructure updates, specifically through the "Callisto" upgrade, transitioned the network to the Cosmos SDK v0.50 with the ABCI 2.0 interface and Optimistic Block Execution. This upgrade significantly reduced block times to less than 2 seconds [3]. Furthermore, the Canto roadmap for its "Cyclone Stack" includes additional performance improvements—such as Optimistic Parallel Execution and MemIAVL integration—intended to achieve even faster, sub-second block times [3]. Because block times can fluctuate based on network conditions and specific validator performance, real-time block explorers may display slightly varying averages, often ranging between 5 and 6 seconds in older snapshots or depending on the indexing status of the explorer [4][5][6]. However, the technical target and optimized performance for the current architecture are significantly lower, moving toward sub-second finality [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant section of the adaptor
sed -n '200,310p' src/adaptors/canto-lending/index.js

# Look for any repo-local note about Canto block time or APY annualization
rg -n "block time|YEARLY_BLOCKS|calculateApy|supplyRatePerBlock|borrowRatePerBlock|compSupplySpeeds|compBorrowSpeeds" src README.md docs . 2>/dev/null || true

Repository: DefiLlama/yield-server

Length of output: 50378


Replace the hardcoded 6s block interval
calculateApy and calculateRewardApy annualize per-block rates with a fixed BLOCK_TIME = 6; Canto’s block time no longer stays pinned to that assumption, so the APY drifts unless this comes from chain config.

🤖 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/canto-lending/index.js` around lines 218 - 223, The APY math in
calculateApy (and the related calculateRewardApy path) is using a hardcoded
6-second BLOCK_TIME, which should instead come from chain configuration. Update
the annualization logic to read the block interval from the existing Canto chain
config or a shared config source, and make sure both APY helpers use that
dynamic value consistently.

Comment on lines +39 to +45
const commissions = results
.filter((r) => r.status === 'fulfilled')
.map((r) => r.value);

if (!commissions.length) return 0;
return commissions.reduce((sum, value) => sum + value, 0) / commissions.length;
};

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

Silent fallback masks upstream failures.

getAverageValidatorCommission returns 0 when every validator query fails (Line 43-44), and getStzigApr returns 0 when bondedZig is falsy (Line 57). Elsewhere in this same file, apy() explicitly throws on invalid/missing data (Lines 76, 82, 86). If the LCD is degraded or all 4 validator queries fail, this silently computes a 0 commission (inflating APR) or 0 APR (understating it) instead of surfacing the failure like the rest of the module does.

Suggested consistency fix
   if (!commissions.length) return 0;
+  // consider throwing instead, to match the fail-fast pattern used in apy()
+  // throw new Error('Unable to fetch validator commissions');
   return commissions.reduce((sum, value) => sum + value, 0) / commissions.length;

Also applies to: 56-57

🤖 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/valdora-finance/index.js` around lines 39 - 45,
`getAverageValidatorCommission` and `getStzigApr` are silently defaulting to 0
when upstream data is missing or all validator queries fail, unlike `apy()`
which throws on invalid input. Update these helpers to detect empty/failed
results as an error condition and throw a descriptive failure instead of
returning 0, using the existing functions `getAverageValidatorCommission`,
`getStzigApr`, and `apy` as the consistency points for the behavior change.

@0xkr3p

0xkr3p commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

FYI the front end is dead but the protocol is still alive.

How does the user deposit? We should probably exclude this adapter, if a user is unable to deposit

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.

2 participants