canto-lending: report available liquidity as TVL#2769
Conversation
- 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.
📝 WalkthroughWalkthroughThe 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. ChangesCanto Lending APY Refactor
Valdora Finance Adaptor
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
Suggested reviewers: 🚥 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 canto-lending adapter exports pools: Test Suites: 1 passed, 1 total |
|
FYI the front end is dead but the protocol is still alive. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/adaptors/valdora-finance/index.js (1)
16-21: 🎯 Functional Correctness | 🔵 TrivialHardcoded 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
📒 Files selected for processing (2)
src/adaptors/canto-lending/index.jssrc/adaptors/valdora-finance/index.js
| 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; | ||
| } |
There was a problem hiding this comment.
🎯 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-lendingRepository: 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:
- 1: https://www.gate.com/crypto-wiki/article/comprehensive-guide-to-canto-layer-1-blockchain-20260206
- 2: https://docs.canto.io/evm-development/overview
- 3: https://canto.mirror.xyz/MAKVlRZKaxA2aDCS0MMxoy2A9f5PRh7SwyRfKltyxQY
- 4: https://staking-explorer.com/explorer/canto
- 5: https://explorer.plexnode.wtf/
- 6: https://testnet.tuber.build/
🏁 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 || trueRepository: 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.
| 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; | ||
| }; |
There was a problem hiding this comment.
🩺 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.
How does the user deposit? We should probably exclude this adapter, if a user is unable to deposit |
Summary
This updates the
canto-lendingadapter so that the displayedtvlUsdrepresents 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-lendingcomputed:tvlUsd = totalSupplyUsd - totalBorrowUsdSince
totalSupplyUsdwas derived fromcash + 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
tvlUsdis now calculated directly from the market's onchaingetCash()value:tvlUsd = getCash * underlyingPriceavailableBorrowUsdis set to the same available liquidity value.totalSupplyUsdandtotalBorrowUsdare still exported separately for lending analytics.apyBaseandapyBaseBorrowcontinue to come from onchainsupplyRatePerBlockandborrowRatePerBlock, now using a Compound-style compounded APY calculation.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:
totalSupplyUsdtotalBorrowUsdavailableBorrowUsdapyBaseapyBaseBorrowSummary by CodeRabbit