@@ -285,7 +285,36 @@ async function etherscanClaimReceiver(
285285 return null ;
286286}
287287
288+ << < < < << Updated upstream
288289async function morBackersByRider ( ethUsd : UsdPrice ) : Promise < Record < string , OrbitBacker [ ] > > {
290+ === === =
291+ // A staker's currently-claimable MOR on a pool (still in the Morpheus contract,
292+ // pre-claim). Gnars' 25% of this is revenue ACCRUING to the treasury — it shows
293+ // up here so the treasury figure ticks up like the (also-unrealized) vault fee,
294+ // instead of sitting at 0 until the first claim→split→distribute. Etherscan
295+ // proxy eth_call (datacenter-safe); returns wei, 0 on any failure.
296+ const LATEST_REWARD_SIG = keccak256 ( toHex ( "getLatestUserReward(uint256,address)" ) ) . slice ( 0 , 10 ) ;
297+ async function etherscanLatestReward ( pool : Address , user : Address , key : string ) : Promise < bigint > {
298+ const data = `${ LATEST_REWARD_SIG } ${ "0" . repeat ( 64 ) } ${ pad32 ( user ) . slice ( 2 ) } ` ;
299+ const url =
300+ `https://api.etherscan.io/v2/api?chainid=1&module=proxy&action=eth_call` +
301+ `&to=${ pool } &data=${ data } &tag=latest&apikey=${ key } ` ;
302+ for ( let i = 0 ; i < 4 ; i ++ ) {
303+ try {
304+ const res = await fetch ( url , { cache : "no-store" } ) ;
305+ const j = ( await res . json ( ) ) as { result ?: unknown ; message ?: string } ;
306+ if ( typeof j . result === "string" && / ^ 0 x [ 0 - 9 a - f A - F ] + $ / . test ( j . result ) ) {
307+ try { return BigInt ( j . result ) ; } catch { return BigInt ( 0 ) ; }
308+ }
309+ if ( / r a t e l i m i t / i. test ( String ( j . result ) ) || / r a t e l i m i t / i. test ( String ( j . message ) ) ) { await sleep ( 600 ) ; continue ; }
310+ return BigInt ( 0 ) ;
311+ } catch { await sleep ( 300 ) ; }
312+ }
313+ return BigInt ( 0 ) ;
314+ }
315+
316+ async function morBackersByRider ( ethUsd : number ) : Promise < Record < string , OrbitBacker [ ] > > {
317+ >>> >>> > Stashed changes
289318 const walletToId = new Map < string , RiderId > ( ) ;
290319 for ( const r of RIDER_LIST ) if ( r . wallet ) walletToId . set ( r . wallet . toLowerCase ( ) , r . id ) ;
291320 const referrers = [ ...walletToId . keys ( ) ] . map ( ( a ) => getAddress ( a ) ) ;
@@ -426,9 +455,14 @@ async function morBackersByRider(ethUsd: UsdPrice): Promise<Record<string, Orbit
426455}
427456
428457/**
429- * MOR earned for the Gnars treasury: what's already been distributed to the
430- * Gnars Arbitrum multisig, plus Gnars' 25% share still sitting undistributed in
431- * each staker's split. Best-effort — priced in USD via CoinGecko.
458+ * MOR earned/accruing for the Gnars treasury, in three tiers (Gnars = 25% of the
459+ * staker's rewards throughout):
460+ * 1. directRaw — already distributed to the Gnars Arbitrum multisig.
461+ * 2. in-split — claimed to a staker's split, awaiting distribution (Arbitrum).
462+ * 3. accruing — still unclaimed in the Morpheus pools (mainnet, pending).
463+ * Tier 3 is what keeps the figure alive: it ticks up as MOR accrues, mirroring
464+ * the (also-unrealized) vault fee, instead of reading 0 until the first claim.
465+ * Best-effort — priced in USD via CoinGecko.
432466 */
433467async function gnarsMorEarned (
434468 morByRider : Record < string , OrbitBacker [ ] > ,
@@ -454,14 +488,22 @@ async function readGnarsMor(
454488
455489 // Unique (staker, athlete) pairs → the per-staker splits holding MOR.
456490 const pairs = new Map < string , [ Address , Address ] > ( ) ;
491+ // (pool, staker) targets for the still-unclaimed MOR accruing in Morpheus.
492+ const pendTargets : Array < [ Address , Address ] > = [ ] ;
457493 for ( const [ id , backers ] of Object . entries ( morByRider ) ) {
458494 const ref = walletById . get ( id ) ;
459495 if ( ! ref ) continue ;
460- for ( const b of backers ) pairs . set ( `${ b . address } -${ ref } ` . toLowerCase ( ) , [ b . address , ref ] ) ;
496+ for ( const b of backers ) {
497+ pairs . set ( `${ b . address } -${ ref } ` . toLowerCase ( ) , [ b . address , ref ] ) ;
498+ if ( b . kind === "mor" && b . asset && ETHERSCAN_KEY ) {
499+ const pool = b . asset === "steth" ? MORPHEUS_POOLS . stEth . pool : MORPHEUS_POOLS . usdc . pool ;
500+ pendTargets . push ( [ pool , b . address ] ) ;
501+ }
502+ }
461503 }
462504
463505 try {
464- const [ splitBals , directRaw , morUsd ] = await Promise . all ( [
506+ const [ splitBals , directRaw , morUsd , pendRaw ] = await Promise . all ( [
465507 Promise . all ( [ ...pairs . values ( ) ] . map ( ( [ s , a ] ) => splitMorBalance ( s , a ) . catch ( ( ) => 0 ) ) ) ,
466508 arbitrumClient
467509 . readContract ( {
@@ -472,11 +514,21 @@ async function readGnarsMor(
472514 } )
473515 . then ( ( b ) => Number ( formatUnits ( b , MOR_DECIMALS ) ) )
474516 . catch ( ( ) => 0 ) ,
517+ < < < << << Updated upstream
475518 getTokenPriceUsd ( MOR_TOKEN , "arbitrum-one" ) ,
476519 ] ) ;
477520 const pendingGnars = splitBals . reduce ( ( s , v ) => s + v , 0 ) * 0.25 ; // Gnars = 25% of each split
478521 const mor = pendingGnars + directRaw ;
479522 return { mor , morUsd } ;
523+ === = ===
524+ getMorUsd ( ) ,
525+ Promise . all ( pendTargets . map ( ( [ p , u ] ) => etherscanLatestReward ( p , u , ETHERSCAN_KEY as string ) . catch ( ( ) => BigInt ( 0 ) ) ) ) ,
526+ ] ) ;
527+ const inSplitGnars = splitBals . reduce ( ( s , v ) => s + v , 0 ) * 0.25 ; // claimed, awaiting distribute
528+ const accruingGnars = pendRaw . reduce ( ( s , v ) => s + Number ( formatUnits ( v , MOR_DECIMALS ) ) , 0 ) * 0.25 ; // still in Morpheus
529+ const mor = inSplitGnars + directRaw + accruingGnars ;
530+ return { mor , usd : mor * morUsd } ;
531+ >>> > >>> Stashed changes
480532 } catch {
481533 return { mor : 0 , morUsd : null as UsdPrice } ;
482534 }
0 commit comments