|
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 |
| 1 | +import { Chain } from "../../adapters/types"; |
| 2 | +import { CHAIN } from "../../helpers/chains"; |
| 3 | +import { Adapter, FetchOptions, FetchResultFees } from "../../adapters/types"; |
| 4 | +import { addTokensReceived } from "../../helpers/token"; |
| 5 | + |
| 6 | +const graph = (_chain: Chain): any => { |
| 7 | + return async (timestamp: number, _: any, options: FetchOptions): Promise<FetchResultFees> => { |
| 8 | + const dailyFees = await addTokensReceived({ |
| 9 | + targets: ['0x546e7b1f4b4Df6CDb19fbDdFF325133EBFE04BA7', '0x6Ee1f539DDf1515eE49B58A5E9ae84C2E7643490'], // v3 and v4 fee collectors |
| 10 | + tokens: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'], |
| 11 | + options |
| 12 | + }) |
| 13 | + const dailyRevenue = dailyFees.clone(); |
| 14 | + const dailyProtocolRevenue = dailyRevenue.clone(0.25); // 25% of revenue goes to protocol |
| 15 | + const dailyHoldersRevenue = dailyRevenue.clone(0.75); // 75% of revenue goes to holders |
| 16 | + return { dailyFees, dailyRevenue: dailyRevenue, dailyProtocolRevenue: dailyProtocolRevenue, dailyHoldersRevenue: dailyHoldersRevenue, timestamp } |
| 17 | + } |
31 | 18 | } |
32 | 19 |
|
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 | 20 |
|
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 | | - } |
| 21 | +const adapter: Adapter = { |
| 22 | + adapter: { |
| 23 | + [CHAIN.ETHEREUM]: { |
| 24 | + fetch: graph(CHAIN.ETHEREUM), |
| 25 | + start: '2025-02-19', |
| 26 | + }, |
| 27 | + }, |
| 28 | + methodology: { |
| 29 | + Fees: 'Swap fees paid by users.', |
| 30 | + Revenue: 'Swap fees paid by users.', |
| 31 | + ProtocolRevenue: '25% of revenue goes to protocol treasury.', |
| 32 | + HoldersRevenue: '75% of revenue goes to THOR stakers. Stakers can choose in which token they want to receive their rewards either THOR or USDC.', |
60 | 33 | } |
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 | 34 | } |
75 | 35 |
|
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 |
| 36 | +export default adapter; |
0 commit comments