Add STRATO yield adapter#2825
Conversation
Surfaces STRATO/Mercata yield products on yields.llama.fi, read purely via eth_call against the public STRATO RPC (chain `strato`, protocolId 7862): - saveUSDST savings vault (apyBase from the per-second savings rate) - USDST lending pool / lendUSDST (supply apyBase + borrow apy) - STRATO staking (apyReward in STRATO) - AMM LP pools (TVL from reserves; TVL-only for now) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a Strato yield adapter that reads savings, lending, staking, and AMM contracts, resolves token prices, computes TVL/APY metrics, filters invalid results, and exports the adapter metadata. ChangesStrato adapter
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant DefiLlama
participant StratoAdapter
participant StratoRPC
participant PriceAPI
DefiLlama->>StratoAdapter: invoke apy()
StratoAdapter->>StratoRPC: read savings, lending, staking, and AMM state
StratoAdapter->>PriceAPI: resolve token prices
PriceAPI-->>StratoAdapter: return prices or fallback
StratoAdapter-->>DefiLlama: return filtered pool metrics
🚥 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 strato adapter exports pools: Test Suites: 1 passed, 1 total |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/strato/index.js`:
- Around line 176-185: Update the APY calculation guard surrounding apyReward so
rewards are calculated only when periodStart is at or before now, periodFinish
is after now, and duration is positive. Preserve the existing reward calculation
for currently active periods and leave future scheduled periods at zero.
- Around line 145-150: Update the pool output’s tvlUsd calculation to use
available cash only, rather than totalSupplied (cash plus borrows). Keep
totalSupplyUsd based on totalSupplied and totalBorrowUsd based on totalBorrows,
leaving the remaining fields unchanged.
- Around line 131-147: Update the Strato rate calculation near totalBorrows and
borrowApr to derive the supply per-second rate from the borrow per-second
factor, utilization, and reserveFactor before calling rayPerSecondToApy. Use
that annualized result for apyBase, while preserving borrowApr for
apyBaseBorrow.
🪄 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: d0dbf06c-fc54-41a3-a84d-338e45027ca8
📒 Files selected for processing (1)
src/adaptors/strato/index.js
| const totalSupplied = Number(supplied) / 1e18; | ||
| const totalBorrows = Number(borrows) / 1e18; | ||
| const utilization = totalBorrows / totalSupplied; | ||
|
|
||
| const reserveFactor = Number(cfg.reserveFactor ?? cfg[4]) / 1e4; | ||
| const ltv = Number(cfg.ltv ?? cfg[0]) / 1e4; | ||
| const borrowApr = rayPerSecondToApy(cfg.perSecondFactorRAY ?? cfg[5]); // percent | ||
|
|
||
| return [ | ||
| { | ||
| pool: `${LENDING_POOL}-strato`.toLowerCase(), | ||
| chain: utils.formatChain(CHAIN), | ||
| project: PROJECT, | ||
| symbol: 'USDST', | ||
| tvlUsd: totalSupplied * usdstPrice, | ||
| apyBase: (borrowApr / 100) * utilization * (1 - reserveFactor) * 100, | ||
| apyBaseBorrow: borrowApr, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Compound the adjusted supply rate, not the borrow APY.
Line 146 applies utilization and reserve factor after annual compounding. Derive the supply per-second rate first, then annualize it; otherwise supply APY is overstated.
Proposed fix
- const borrowApr = rayPerSecondToApy(cfg.perSecondFactorRAY ?? cfg[5]); // percent
+ const factor = BigInt(cfg.perSecondFactorRAY ?? cfg[5]);
+ const borrowRatePerSecond = Number(factor - RAY) / 1e27;
+ const borrowApy =
+ (Math.pow(1 + borrowRatePerSecond, SECONDS_PER_YEAR) - 1) * 100;
+ const supplyRatePerSecond =
+ borrowRatePerSecond * utilization * (1 - reserveFactor);
+ const supplyApy =
+ (Math.pow(1 + supplyRatePerSecond, SECONDS_PER_YEAR) - 1) * 100;
...
- apyBase: (borrowApr / 100) * utilization * (1 - reserveFactor) * 100,
- apyBaseBorrow: borrowApr,
+ apyBase: supplyApy,
+ apyBaseBorrow: borrowApy,📝 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.
| const totalSupplied = Number(supplied) / 1e18; | |
| const totalBorrows = Number(borrows) / 1e18; | |
| const utilization = totalBorrows / totalSupplied; | |
| const reserveFactor = Number(cfg.reserveFactor ?? cfg[4]) / 1e4; | |
| const ltv = Number(cfg.ltv ?? cfg[0]) / 1e4; | |
| const borrowApr = rayPerSecondToApy(cfg.perSecondFactorRAY ?? cfg[5]); // percent | |
| return [ | |
| { | |
| pool: `${LENDING_POOL}-strato`.toLowerCase(), | |
| chain: utils.formatChain(CHAIN), | |
| project: PROJECT, | |
| symbol: 'USDST', | |
| tvlUsd: totalSupplied * usdstPrice, | |
| apyBase: (borrowApr / 100) * utilization * (1 - reserveFactor) * 100, | |
| apyBaseBorrow: borrowApr, | |
| const totalSupplied = Number(supplied) / 1e18; | |
| const totalBorrows = Number(borrows) / 1e18; | |
| const utilization = totalBorrows / totalSupplied; | |
| const reserveFactor = Number(cfg.reserveFactor ?? cfg[4]) / 1e4; | |
| const ltv = Number(cfg.ltv ?? cfg[0]) / 1e4; | |
| const factor = BigInt(cfg.perSecondFactorRAY ?? cfg[5]); | |
| const borrowRatePerSecond = Number(factor - RAY) / 1e27; | |
| const borrowApy = | |
| (Math.pow(1 + borrowRatePerSecond, SECONDS_PER_YEAR) - 1) * 100; | |
| const supplyRatePerSecond = | |
| borrowRatePerSecond * utilization * (1 - reserveFactor); | |
| const supplyApy = | |
| (Math.pow(1 + supplyRatePerSecond, SECONDS_PER_YEAR) - 1) * 100; | |
| return [ | |
| { | |
| pool: `${LENDING_POOL}-strato`.toLowerCase(), | |
| chain: utils.formatChain(CHAIN), | |
| project: PROJECT, | |
| symbol: 'USDST', | |
| tvlUsd: totalSupplied * usdstPrice, | |
| apyBase: supplyApy, | |
| apyBaseBorrow: borrowApy, |
🤖 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/strato/index.js` around lines 131 - 147, Update the Strato rate
calculation near totalBorrows and borrowApr to derive the supply per-second rate
from the borrow per-second factor, utilization, and reserveFactor before calling
rayPerSecondToApy. Use that annualized result for apyBase, while preserving
borrowApr for apyBaseBorrow.
| tvlUsd: totalSupplied * usdstPrice, | ||
| apyBase: (borrowApr / 100) * utilization * (1 - reserveFactor) * 100, | ||
| apyBaseBorrow: borrowApr, | ||
| totalSupplyUsd: totalSupplied * usdstPrice, | ||
| totalBorrowUsd: totalBorrows * usdstPrice, | ||
| ltv, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Report available liquidity as lending TVL.
Line 145 uses cash + borrows, counting assets currently held by borrowers as protocol TVL. Use cash for tvlUsd; retain gross supply and debt in totalSupplyUsd and totalBorrowUsd. The downstream runner already uses totalSupplyUsd for lending-project lower-bound filtering.
Proposed fix
- tvlUsd: totalSupplied * usdstPrice,
+ tvlUsd: (Number(cash) / 1e18) * usdstPrice,📝 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.
| tvlUsd: totalSupplied * usdstPrice, | |
| apyBase: (borrowApr / 100) * utilization * (1 - reserveFactor) * 100, | |
| apyBaseBorrow: borrowApr, | |
| totalSupplyUsd: totalSupplied * usdstPrice, | |
| totalBorrowUsd: totalBorrows * usdstPrice, | |
| ltv, | |
| tvlUsd: (Number(cash) / 1e18) * usdstPrice, | |
| apyBase: (borrowApr / 100) * utilization * (1 - reserveFactor) * 100, | |
| apyBaseBorrow: borrowApr, | |
| totalSupplyUsd: totalSupplied * usdstPrice, | |
| totalBorrowUsd: totalBorrows * usdstPrice, | |
| ltv, |
🤖 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/strato/index.js` around lines 145 - 150, Update the pool
output’s tvlUsd calculation to use available cash only, rather than
totalSupplied (cash plus borrows). Keep totalSupplyUsd based on totalSupplied
and totalBorrowUsd based on totalBorrows, leaving the remaining fields
unchanged.
| const now = Math.floor(Date.now() / 1000); | ||
| const duration = Number(periodFinish) - Number(periodStart); | ||
|
|
||
| let apyReward = 0; | ||
| if (Number(periodFinish) > now && duration > 0) { | ||
| const annualReward = | ||
| (Number(BigInt(rewardAmount)) / 1e18) * (SECONDS_PER_YEAR / duration); | ||
| const stakedTokens = Number(totalStakeBI) / 1e18; | ||
| apyReward = (annualReward / stakedTokens) * 100; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not advertise rewards before the period starts.
A future scheduled period passes the current condition and produces a non-zero APY. Require periodStart <= now.
- if (Number(periodFinish) > now && duration > 0) {
+ if (
+ Number(periodStart) <= now &&
+ Number(periodFinish) > now &&
+ duration > 0
+ ) {📝 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.
| const now = Math.floor(Date.now() / 1000); | |
| const duration = Number(periodFinish) - Number(periodStart); | |
| let apyReward = 0; | |
| if (Number(periodFinish) > now && duration > 0) { | |
| const annualReward = | |
| (Number(BigInt(rewardAmount)) / 1e18) * (SECONDS_PER_YEAR / duration); | |
| const stakedTokens = Number(totalStakeBI) / 1e18; | |
| apyReward = (annualReward / stakedTokens) * 100; | |
| } | |
| const now = Math.floor(Date.now() / 1000); | |
| const duration = Number(periodFinish) - Number(periodStart); | |
| let apyReward = 0; | |
| if ( | |
| Number(periodStart) <= now && | |
| Number(periodFinish) > now && | |
| duration > 0 | |
| ) { | |
| const annualReward = | |
| (Number(BigInt(rewardAmount)) / 1e18) * (SECONDS_PER_YEAR / duration); | |
| const stakedTokens = Number(totalStakeBI) / 1e18; | |
| apyReward = (annualReward / stakedTokens) * 100; | |
| } |
🤖 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/strato/index.js` around lines 176 - 185, Update the APY
calculation guard surrounding apyReward so rewards are calculated only when
periodStart is at or before now, periodFinish is after now, and duration is
positive. Preserve the existing reward calculation for currently active periods
and leave future scheduled periods at zero.
|
The strato adapter exports pools: Test Suites: 1 passed, 1 total |
| // unauthenticated JSON-RPC node that the DefiLlama TVL adapter already uses. | ||
| // @defillama/sdk resolves the RPC for chain `strato` from the STRATO_RPC env var. | ||
| const RPC_URL = 'https://noderpc.strato.nexus/rpc'; | ||
| if (!process.env.STRATO_RPC) process.env.STRATO_RPC = RPC_URL; |
There was a problem hiding this comment.
this can be removed, strato is in the latest version of SDK
0xkr3p
left a comment
There was a problem hiding this comment.
hi @michaelbtan, thanks for the PR. Pls resolve major code rabbit issues before we can proceed
What
Adds a yield adapter for STRATO / Mercata (BlockApps' institutional L1, already listed on DefiLlama TVL as
strato, protocol id7862). All pools are read purely viaeth_call(andcoins.llama.fifor pricing) against the public STRATO RPC (https://noderpc.strato.nexus/rpc) — the same RPC the existing TVL adapter uses. No auth required.Pools
apyBase(savings rate)apyReward(STRATO)apyBasesupply +apyBaseBorrowNotes / constraints
eth_call(private state var), and the active pools currently have no swap volume in a trailing window, so fee APY would be 0 regardless. Pools are reported TVL-only; fee APY can be added in a follow-up if a fee getter is exposed on-chain.eth_call(same limitation noted in the TVL adapter), so no CDP pool is included.Verified locally:
npm run test --adapter=strato→PASS(63/63).Summary by CodeRabbit