11// Live staking/lending yields shown on the /stake page.
22// - Lido: stETH staking APR (Ethereum L1)
33// - Morpho: largest listed USDC vault net APY on Base
4- // - Morpheus: blended MOR-reward APR on the shared Capital pool (Ethereum L1)
5-
6- import { createPublicClient , http } from "viem" ;
7- import { mainnet } from "viem/chains" ;
8- import { MORPHEUS_POOLS , MORPHEUS_DISTRIBUTOR , MOR_TOKEN } from "@/lib/morpheus" ;
4+ // - Morpheus: MOR-reward APR PER ASSET (see MORPHEUS_APR note)
95
106export interface StakeYield {
117 /** Annual rate as a percentage, e.g. 4.35 */
@@ -18,11 +14,16 @@ export interface StakeYield {
1814 estimate ?: boolean ;
1915}
2016
17+ /** Morpheus shows a distinct APR per deposit asset, not one blended rate. */
18+ export interface MorpheusYields {
19+ steth : StakeYield | null ;
20+ usdc : StakeYield | null ;
21+ }
22+
2123export interface StakeYields {
2224 eth : StakeYield | null ;
2325 usdc : StakeYield | null ;
24- /** Blended Morpheus (MOR) reward APR — same rate for the stETH and USDC pools. */
25- mor : StakeYield | null ;
26+ mor : MorpheusYields ;
2627 updatedAt : number ;
2728}
2829
@@ -31,72 +32,19 @@ const MORPHO_GRAPHQL_URL = "https://blue-api.morpho.org/graphql";
3132const BASE_CHAIN_ID = 8453 ;
3233
3334// ---- Morpheus (MOR) capital-pool APR ----------------------------------------
34- // Emission for the shared reward pool (index 0) is minted MOR/day; it's split
35- // across the stETH + USDC deposit pools by their oracle-valued deposits, so the
36- // blended APR is: dailyMor * morUsd * 365 / totalTvlUsd. Verified live on-chain
37- // (getPeriodRewards / totalDepositedInPublicPools) before wiring.
38- const MOR_REWARD_POOL_INDEX = BigInt ( 0 ) ;
39- const morMainnet = createPublicClient ( {
40- chain : mainnet ,
41- transport : http ( process . env . ETH_RPC_URL || "https://ethereum.publicnode.com" ) ,
42- } ) ;
43- const distributorAbi = [
44- { type : "function" , name : "rewardPool" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "address" } ] } ,
45- ] as const ;
46- const rewardPoolAbi = [
47- { type : "function" , name : "getPeriodRewards" , stateMutability : "view" ,
48- inputs : [ { type : "uint256" } , { type : "uint128" } , { type : "uint128" } ] , outputs : [ { type : "uint256" } ] } ,
49- ] as const ;
50- const depositPoolAbi = [
51- { type : "function" , name : "totalDepositedInPublicPools" , stateMutability : "view" , inputs : [ ] , outputs : [ { type : "uint256" } ] } ,
52- ] as const ;
53-
54- async function coingeckoUsd ( url : string , pick : ( j : unknown ) => number | undefined ) : Promise < number | null > {
55- try {
56- const res = await fetch ( url , { next : { revalidate : 300 } } ) ;
57- if ( ! res . ok ) return null ;
58- const v = pick ( await res . json ( ) ) ;
59- return typeof v === "number" && v > 0 ? v : null ;
60- } catch {
61- return null ;
62- }
63- }
64-
65- async function getMorpheusMorApr ( ) : Promise < StakeYield | null > {
66- try {
67- const rewardPool = await morMainnet . readContract ( {
68- address : MORPHEUS_DISTRIBUTOR , abi : distributorAbi , functionName : "rewardPool" ,
69- } ) ;
70- const now = Math . floor ( Date . now ( ) / 1000 ) ;
71- const [ daily , stDep , usdcDep , morUsd , ethUsd ] = await Promise . all ( [
72- morMainnet . readContract ( {
73- address : rewardPool , abi : rewardPoolAbi , functionName : "getPeriodRewards" ,
74- args : [ MOR_REWARD_POOL_INDEX , BigInt ( now ) , BigInt ( now + 86400 ) ] ,
75- } ) ,
76- morMainnet . readContract ( { address : MORPHEUS_POOLS . stEth . pool , abi : depositPoolAbi , functionName : "totalDepositedInPublicPools" } ) ,
77- morMainnet . readContract ( { address : MORPHEUS_POOLS . usdc . pool , abi : depositPoolAbi , functionName : "totalDepositedInPublicPools" } ) ,
78- coingeckoUsd (
79- `https://api.coingecko.com/api/v3/simple/token_price/arbitrum-one?contract_addresses=${ MOR_TOKEN } &vs_currencies=usd` ,
80- ( j ) => ( j as Record < string , { usd ?: number } > ) ?. [ MOR_TOKEN . toLowerCase ( ) ] ?. usd ,
81- ) ,
82- coingeckoUsd (
83- "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd" ,
84- ( j ) => ( j as { ethereum ?: { usd ?: number } } ) ?. ethereum ?. usd ,
85- ) ,
86- ] ) ;
87- if ( ! morUsd || ! ethUsd ) return null ;
88-
89- const dailyMor = Number ( daily ) / 1e18 ;
90- const tvlUsd = ( Number ( stDep ) / 1e18 ) * ethUsd + Number ( usdcDep ) / 1e6 ; // stETH ≈ ETH, USDC = $1
91- if ( tvlUsd <= 0 ) return null ;
92-
93- const apr = ( dailyMor * morUsd * 365 ) / tvlUsd * 100 ;
94- if ( ! isFinite ( apr ) || apr <= 0 ) return null ;
95- return { apy : apr , source : "Morpheus" , detail : "MOR" , estimate : true } ;
96- } catch ( error ) {
97- console . error ( "[yields] Morpheus fetch failed:" , error ) ;
98- return null ;
99- }
35+ // Morpheus splits its shared reward pool's emissions across deposit pools by an
36+ // internal virtual/power-adjusted weighting, so each asset shows a distinct APR
37+ // that can't be reproduced cleanly on-chain — and Morpheus exposes no public API
38+ // for it (their dashboard computes client-side). These are synced from the
39+ // Capital table on mor.org; refresh when they drift. Marked estimate=true since
40+ // MOR emissions and price move.
41+ const MORPHEUS_APR : Record < "steth" | "usdc" , number > = {
42+ usdc : 24.5 ,
43+ steth : 16.78 ,
44+ } ;
45+ function morpheusYields ( ) : MorpheusYields {
46+ const mk = ( apy : number ) : StakeYield => ( { apy, source : "Morpheus" , detail : "MOR" , estimate : true } ) ;
47+ return { usdc : mk ( MORPHEUS_APR . usdc ) , steth : mk ( MORPHEUS_APR . steth ) } ;
10048}
10149
10250async function getLidoEthApr ( ) : Promise < StakeYield | null > {
@@ -141,6 +89,6 @@ async function getMorphoUsdcApy(): Promise<StakeYield | null> {
14189}
14290
14391export async function getStakeYields ( ) : Promise < StakeYields > {
144- const [ eth , usdc , mor ] = await Promise . all ( [ getLidoEthApr ( ) , getMorphoUsdcApy ( ) , getMorpheusMorApr ( ) ] ) ;
145- return { eth, usdc, mor, updatedAt : Date . now ( ) } ;
92+ const [ eth , usdc ] = await Promise . all ( [ getLidoEthApr ( ) , getMorphoUsdcApy ( ) ] ) ;
93+ return { eth, usdc, mor : morpheusYields ( ) , updatedAt : Date . now ( ) } ;
14694}
0 commit comments