1+ import BigNumber from "bignumber.js" ;
2+ import { FetchOptions , SimpleAdapter } from "../../adapters/types"
3+ import { CHAIN } from "../../helpers/chains"
4+ import { httpGet } from "../../utils/fetchURL" ;
5+ import { getTimestampAtStartOfDayUTC } from "../../utils/date" ;
6+
7+ const chainMapping : any = {
8+ ETH : CHAIN . ETHEREUM ,
9+ BTC : CHAIN . BITCOIN ,
10+ AVAX : CHAIN . AVAX ,
11+ BSC : CHAIN . BSC ,
12+ LTC : CHAIN . LITECOIN ,
13+ BCH : CHAIN . BITCOIN_CASH ,
14+ DOGE : CHAIN . DOGECHAIN ,
15+ GAIA : CHAIN . COSMOS ,
16+ BASE : CHAIN . BASE ,
17+ THOR : CHAIN . THORCHAIN ,
18+ XRP : CHAIN . RIPPLE ,
19+ }
20+
21+ const THORCHAIN_SUPPORTED_CHAINS = [ 'BTC' , 'ETH' , 'LTC' , 'DOGE' , 'GAIA' , 'AVAX' , 'BSC' , 'BCH' , 'BASE' , 'THOR' , 'XRP' ]
22+
23+ interface Pool {
24+ assetLiquidityFees : string
25+ earnings : string
26+ pool : string
27+ rewards : string
28+ runeLiquidityFees : string
29+ saverEarning : string
30+ totalLiquidityFeesRune : string
31+ }
32+
33+ const assetFromString = ( s : string ) => {
34+
35+ const NATIVE_ASSET_DELIMITER = '.'
36+ const SYNTH_ASSET_DELIMITER = '/'
37+ const TRADE_ASSET_DELIMITER = '~'
38+
39+ const isSynth = s . includes ( SYNTH_ASSET_DELIMITER )
40+ const isTrade = s . includes ( TRADE_ASSET_DELIMITER )
41+ const delimiter = isSynth ? SYNTH_ASSET_DELIMITER : isTrade ? TRADE_ASSET_DELIMITER : NATIVE_ASSET_DELIMITER
42+
43+ const data = s . split ( delimiter )
44+ if ( data . length <= 1 || ! data [ 1 ] ) return null
45+
46+ const chain = data [ 0 ] . trim ( )
47+ const symbol = data [ 1 ] . trim ( )
48+ const ticker = symbol . split ( '-' ) [ 0 ]
49+
50+ if ( ! symbol || ! chain ) return null
51+
52+ return { chain, symbol, ticker }
53+ }
54+
55+ const findInterval = ( timestamp : number , intervals : any ) => {
56+ for ( const interval of intervals ) {
57+ if ( interval . startTime <= timestamp && timestamp < interval . endTime ) {
58+ return interval ;
59+ }
60+ }
61+ return null ;
62+ } ;
63+
64+ type IRequest = {
65+ [ key : string ] : Promise < any > ;
66+ }
67+ const requests : IRequest = { }
68+
69+ export async function fetchCacheURL ( url : string ) {
70+ const key = url ;
71+ if ( ! requests [ key ] )
72+ requests [ key ] = httpGet ( url , { headers : { "x-client-id" : "defillama" } } ) ;
73+ return requests [ key ]
74+ }
75+
76+ const sleep = ( ms : number ) => new Promise ( resolve => setTimeout ( resolve , ms ) )
77+
78+
79+ // New function to generate fetch logic for a single chain
80+ const getFetchForChain = ( chainShortName : string ) => {
81+ return async ( _a :any , _b :any , options : FetchOptions ) => {
82+ const startOfDay = getTimestampAtStartOfDayUTC ( options . startOfDay ) ;
83+ const earningsUrl = `https://midgard.ninerealms.com/v2/history/earnings?interval=day&from=${ options . startTimestamp } &to=${ options . endTimestamp } ` ;
84+ const reserveUrl = `https://midgard.ninerealms.com/v2/history/reserve?interval=day&from=${ options . startTimestamp } &to=${ options . endTimestamp } ` ;
85+ const poolsUrl = `https://midgard.ninerealms.com/v2/pools?period=24h` ;
86+
87+ const earnings = await fetchCacheURL ( earningsUrl ) ;
88+ await sleep ( 3000 ) ;
89+ const revenue = await fetchCacheURL ( reserveUrl ) ;
90+ await sleep ( 2000 ) ;
91+ const pools = await fetchCacheURL ( poolsUrl ) ;
92+ await sleep ( 2000 ) ;
93+
94+ const selectedEarningInterval = findInterval ( startOfDay , earnings . intervals ) ;
95+ const selectedRevenueInterval = findInterval ( startOfDay , revenue . intervals ) ;
96+
97+
98+ const poolsByChainEarnings : Pool [ ] = selectedEarningInterval . pools . filter ( ( pool : any ) => assetFromString ( pool . pool ) ?. chain === chainShortName ) ;
99+
100+ const totalRuneDepth = pools . reduce ( ( acum : BigNumber , pool : any ) => acum . plus ( pool . runeDepth ) , BigNumber ( 0 ) ) ;
101+ const poolsByChainData = pools . filter ( ( pool : any ) => assetFromString ( pool . asset ) ?. chain === chainShortName ) ;
102+ const runeDepthPerChain = poolsByChainData . reduce ( ( acum : BigNumber , pool : any ) => acum . plus ( pool . runeDepth ) , BigNumber ( 0 ) ) ;
103+
104+ const protocolRevenue = BigNumber ( selectedRevenueInterval . gasFeeOutbound || 0 ) . minus ( BigNumber ( selectedRevenueInterval . gasReimbursement || 0 ) ) ;
105+
106+ const runePercentagePerChain = totalRuneDepth . isZero ( ) ? BigNumber ( 0 ) : runeDepthPerChain . div ( totalRuneDepth ) ;
107+ const bondingEarnings = selectedEarningInterval . bondingEarnings ? BigNumber ( selectedEarningInterval . bondingEarnings ) : BigNumber ( 0 ) ;
108+ const bondingRewardPerChainBasedOnRuneDepth = bondingEarnings . times ( runePercentagePerChain ) ; // TODO: Artificial distribution according to the liquidity of the pools. But it is a protocol level data
109+ const protocolRevenuePerChainBasedOnRuneDepth = protocolRevenue . times ( runePercentagePerChain ) ;
110+
111+ const dailyFees = poolsByChainEarnings . reduce ( ( acum , pool ) => {
112+ const liquidityFeesPerPoolInDollars = BigNumber ( pool . totalLiquidityFeesRune ) . div ( 1e8 ) . times ( BigNumber ( selectedEarningInterval . runePriceUSD ) ) ;
113+ const saverLiquidityFeesPerPoolInDollars = BigNumber ( pool . saverEarning ) . div ( 1e8 ) . times ( BigNumber ( selectedEarningInterval . runePriceUSD ) ) ;
114+ const totalLiquidityFees = liquidityFeesPerPoolInDollars . plus ( saverLiquidityFeesPerPoolInDollars ) ;
115+ return acum . plus ( totalLiquidityFees ) ;
116+ } , BigNumber ( 0 ) ) ;
117+
118+ const dailySupplysideRevenue = poolsByChainEarnings . reduce ( ( acum , pool ) => {
119+ const liquidityFeesPerPoolInDollars = BigNumber ( pool . totalLiquidityFeesRune ) . div ( 1e8 ) . times ( BigNumber ( selectedEarningInterval . runePriceUSD ) ) ;
120+ const saverLiquidityFeesPerPoolInDollars = BigNumber ( pool . saverEarning ) . div ( 1e8 ) . times ( BigNumber ( selectedEarningInterval . runePriceUSD ) ) ;
121+ const rewardsInDollars = BigNumber ( pool . rewards ) . div ( 1e8 ) . times ( BigNumber ( selectedEarningInterval . runePriceUSD ) ) ;
122+ const totalLiquidityFees = liquidityFeesPerPoolInDollars . plus ( saverLiquidityFeesPerPoolInDollars ) . plus ( rewardsInDollars ) ;
123+ return acum . plus ( totalLiquidityFees ) ;
124+ } , BigNumber ( 0 ) ) ;
125+
126+ const runePriceUSDNum = selectedEarningInterval . runePriceUSD ? Number ( selectedEarningInterval . runePriceUSD ) : 0 ;
127+ const protocolRevenueByChainInDollars = protocolRevenuePerChainBasedOnRuneDepth . div ( 1e8 ) . times ( runePriceUSDNum ) ;
128+ const dailyHoldersRevenue = bondingRewardPerChainBasedOnRuneDepth . div ( 1e8 ) . times ( runePriceUSDNum ) ;
129+ // if (dailyFees.isZero()) throw new Error("No fees found for this day");
130+
131+ return {
132+ dailyFees,
133+ dailyUserFees : dailyFees ,
134+ dailyRevenue : `${ dailyHoldersRevenue . plus ( protocolRevenueByChainInDollars ) } ` ,
135+ dailyProtocolRevenue : protocolRevenueByChainInDollars . gt ( 0 ) ? protocolRevenueByChainInDollars : 0 ,
136+ dailyHoldersRevenue : dailyHoldersRevenue ,
137+ dailySupplySideRevenue : dailySupplysideRevenue ,
138+ timestamp : startOfDay
139+ } ;
140+ } ;
141+ } ;
142+
143+ const adapters : SimpleAdapter = {
144+ adapter : THORCHAIN_SUPPORTED_CHAINS . reduce ( ( acc , chainKey ) => {
145+ ( acc as any ) [ chainMapping [ chainKey ] ] = {
146+ fetch : getFetchForChain ( chainKey ) as any ,
147+ // runAtCurrTime: true,
148+ } ;
149+ return acc ;
150+ } , { } ) ,
151+ } ;
152+
153+ export default adapters
0 commit comments