diff --git a/components/InfoBox/AccountDetailsInfoBox.js b/components/InfoBox/AccountDetailsInfoBox.js index d340f0b6..6165679f 100644 --- a/components/InfoBox/AccountDetailsInfoBox.js +++ b/components/InfoBox/AccountDetailsInfoBox.js @@ -56,7 +56,7 @@ const AccountDetailsInfoBox = () => { subtitles={subtitles} breadcrumbs={ maker - ? [{ title: 'Makers', path: '/hotspots/makers' }] + ? [{ title: 'Makers', path: '/iot/makers' }] : [{ title: 'Overview', path: '/' }] } > diff --git a/components/InfoBox/BlocksInfoPanes/BlockStatisticsPane.js b/components/InfoBox/BlocksInfoPanes/BlockStatisticsPane.js index b1484709..35ec2951 100644 --- a/components/InfoBox/BlocksInfoPanes/BlockStatisticsPane.js +++ b/components/InfoBox/BlocksInfoPanes/BlockStatisticsPane.js @@ -7,7 +7,7 @@ import { useState, useEffect } from 'react' import ElectionTimeWidget from '../../Widgets/ElectionTimeWidget' const BlockStatisticsPane = () => { - let { data: blocks } = useApi('/metrics/blocks') + const { data: blocks } = useApi('/metrics/blocks') const [processingData, setProcessingData] = useState(true) const [blockTimeDay, setBlockTimeDay] = useState() diff --git a/components/InfoBox/Hotspots/MakersPane.js b/components/InfoBox/Common/MakersPane.js similarity index 97% rename from components/InfoBox/Hotspots/MakersPane.js rename to components/InfoBox/Common/MakersPane.js index 1de25327..75d2568a 100644 --- a/components/InfoBox/Hotspots/MakersPane.js +++ b/components/InfoBox/Common/MakersPane.js @@ -7,8 +7,8 @@ import LocationIcon from '../../Icons/Location' import { Tooltip } from 'antd' import { useMakers } from '../../../data/makers' -const MakersPane = () => { - const { makers } = useMakers() +const MakersPane = ({ type = 'lorawan' }) => { + const { makers } = useMakers({ type }) const keyExtractor = useCallback((maker) => maker.address, []) diff --git a/components/InfoBox/HotspotDetailsInfoBox.js b/components/InfoBox/HotspotDetailsInfoBox.js index 8e66074a..21364e88 100644 --- a/components/InfoBox/HotspotDetailsInfoBox.js +++ b/components/InfoBox/HotspotDetailsInfoBox.js @@ -171,9 +171,9 @@ const HotspotDetailsInfoBox = ({ address, isLoading, hotspot }) => { ) const generateBreadcrumbs = (hotspot) => { - if (!hotspot) return [{ title: 'Hotspots', path: '/hotspots' }] + if (!hotspot) return [{ title: 'IOT', path: '/iot' }] return [ - { title: 'Hotspots', path: '/hotspots/latest' }, + { title: 'IOT', path: '/iot' }, ...(hotspot.location ? // if the hotspot has a location, show breadcrumbs for it [ @@ -192,7 +192,7 @@ const HotspotDetailsInfoBox = ({ address, isLoading, hotspot }) => { ), - path: `/hotspots/hex/${hotspot.location}`, + path: `/iot/hex/${hotspot.location}`, }, ] : []), @@ -256,7 +256,12 @@ const HotspotDetailsInfoBox = ({ address, isLoading, hotspot }) => { title="5G Statistics" key="5g-statistics" path="5g-statistics" - hidden={!(maker?.id === MAKER_IDS.FREEDOM_FI || maker?.id === MAKER_IDS.BOBCAT_5G)} + hidden={ + !( + maker?.id === MAKER_IDS.FREEDOM_FI || + maker?.id === MAKER_IDS.BOBCAT_5G + ) + } > {isLoading ? ( diff --git a/components/InfoBox/HotspotsInfoBox.js b/components/InfoBox/HotspotsInfoBox.js deleted file mode 100644 index 2cec805b..00000000 --- a/components/InfoBox/HotspotsInfoBox.js +++ /dev/null @@ -1,30 +0,0 @@ -import InfoBox from './InfoBox' -import TabNavbar, { TabPane } from '../Nav/TabNavbar' -import I18n from '../../copy/I18n' -import StatisticsPane from './Hotspots/StatisticsPane' -import MakersPane from './Hotspots/MakersPane' -import TopCitiesPane from './Hotspots/TopCitiesPane' -import CellStatisticsPane from './Hotspots/CellStatisticsPane' - -const HotspotsInfoBox = () => { - return ( - } metaTitle="Hotspots"> - - - - - - - - - - - - - - - - ) -} - -export default HotspotsInfoBox diff --git a/components/InfoBox/InfoBoxSwitch.js b/components/InfoBox/InfoBoxSwitch.js index 8d765f7a..081d0926 100644 --- a/components/InfoBox/InfoBoxSwitch.js +++ b/components/InfoBox/InfoBoxSwitch.js @@ -1,6 +1,6 @@ -import { Switch, Route } from 'react-router-dom' +import { Switch, Route, Redirect } from 'react-router-dom' import OverviewInfoBox from './OverviewInfoBox' -import HotspotsInfoBox from './HotspotsInfoBox' +import IotInfoBox from './Iot/IotInfoBox' import HotspotDetailsInfoBox from './HotspotDetailsInfoBox' import ValidatorsInfoBox from './ValidatorsInfoBox' import BlocksInfoBox from './BlocksInfoBox' @@ -15,6 +15,7 @@ import MarketInfoBox from './MarketInfoBox' import CommunityToolsInfoBox from './CommunityTools/CommunityToolsInfoBox' import ErrorInfoBox from './ErrorInfoBox' import CityDetailsInfoBox from './CityDetailsInfoBox' +import MobileInfoBox from './Mobile/MobileInfoBox' const InfoBoxSwitch = () => { // Match locales with regular expression containing each locale separated by `|` @@ -22,17 +23,20 @@ const InfoBoxSwitch = () => { return ( - + - + - - + + + + + diff --git a/components/InfoBox/Hotspots/CellStatisticsPane.js b/components/InfoBox/Iot/CellStatisticsPane.js similarity index 88% rename from components/InfoBox/Hotspots/CellStatisticsPane.js rename to components/InfoBox/Iot/CellStatisticsPane.js index d08d0a3f..5fb1f643 100644 --- a/components/InfoBox/Hotspots/CellStatisticsPane.js +++ b/components/InfoBox/Iot/CellStatisticsPane.js @@ -8,11 +8,7 @@ const CellStatisticsPane = () => { return ( - + { + const { setMapLayer } = useMapLayer() + + useEffect(() => { + setMapLayer('default') + }, [setMapLayer]) + + return ( + + + + + + + + + + + + + + ) +} + +export default IotInfoBox diff --git a/components/InfoBox/Hotspots/LatestHotspotsPane.js b/components/InfoBox/Iot/LatestHotspotsPane.js similarity index 100% rename from components/InfoBox/Hotspots/LatestHotspotsPane.js rename to components/InfoBox/Iot/LatestHotspotsPane.js diff --git a/components/InfoBox/Hotspots/StatisticsPane.js b/components/InfoBox/Iot/StatisticsPane.js similarity index 85% rename from components/InfoBox/Hotspots/StatisticsPane.js rename to components/InfoBox/Iot/StatisticsPane.js index 2a93a986..13cb67bf 100644 --- a/components/InfoBox/Hotspots/StatisticsPane.js +++ b/components/InfoBox/Iot/StatisticsPane.js @@ -7,11 +7,14 @@ import useApi from '../../../hooks/useApi' import InfoBoxPaneContainer from '../Common/InfoBoxPaneContainer' import Widget from '../../Widgets/Widget' import { maxBy, last } from 'lodash' +import { useDataCredits } from '../../../data/datacredits' +import LargeBalance from '../../Common/LargeBalance' const StatisticsPane = () => { const { data: stats } = useApi('/metrics/hotspots') const { data: makers } = useApi('/makers') const { latestHotspots } = useLatestHotspots() + const { dataCredits } = useDataCredits() const latestHotspot = useMemo(() => { if (!latestHotspots) return null @@ -41,6 +44,16 @@ const StatisticsPane = () => { series={stats?.dataOnlyCount} isLoading={!stats} /> + } + isLoading={!dataCredits} + /> + } + isLoading={!dataCredits} + /> { value={makers?.length} subtitle={`Latest: ${maxBy(makers, 'id')?.name}`} isLoading={!makers} - linkTo="/hotspots/makers" + linkTo="/iot/makers" /> { const keyExtractor = useCallback((city) => city.cityId, []) - const linkExtractor = useCallback( - (city) => `/hotspots/cities/${city.cityId}`, - [], - ) + const linkExtractor = useCallback((city) => `/iot/cities/${city.cityId}`, []) const renderItem = useCallback((city) => { const cityTitle = city?.longCity ? city.longCity : city.longState diff --git a/components/InfoBox/MarketInfoBox.js b/components/InfoBox/MarketInfoBox.js index 2ecf95bc..ec287961 100644 --- a/components/InfoBox/MarketInfoBox.js +++ b/components/InfoBox/MarketInfoBox.js @@ -45,16 +45,38 @@ const MarketInfoBox = () => { isLoading={!oraclePrices} /> Based on data provided by CoinGecko} + title="HNT Market Price" + tooltip={ + + Based on data provided by{' '} + + CoinGecko + + + } value={} change={round(market?.priceChange, 2)} changeSuffix="%" isLoading={!market} /> Based on data provided by CoinGecko} + title="HNT Market Cap" + tooltip={ + + Based on data provided by{' '} + + CoinGecko + + + } value={ { + const { setMapLayer } = useMapLayer() + + useEffect(() => { + setMapLayer('cbrs') + }, [setMapLayer]) + + return ( + + + + + + + + + + + ) +} + +export default MobileInfoBox diff --git a/components/InfoBox/Mobile/StatisticsPane.js b/components/InfoBox/Mobile/StatisticsPane.js new file mode 100644 index 00000000..5e85425e --- /dev/null +++ b/components/InfoBox/Mobile/StatisticsPane.js @@ -0,0 +1,36 @@ +import TrendWidget from '../../Widgets/TrendWidget' +import useApi from '../../../hooks/useApi' +import InfoBoxPaneContainer from '../Common/InfoBoxPaneContainer' +import StatWidget from '../../Widgets/StatWidget' + +const StatisticsPane = () => { + const { data: stats } = useApi('/metrics/cells') + + return ( + + + + + + + + ) +} + +export default StatisticsPane diff --git a/components/InfoBox/OverviewInfoBox.js b/components/InfoBox/OverviewInfoBox.js index d19e9287..5e819c6b 100644 --- a/components/InfoBox/OverviewInfoBox.js +++ b/components/InfoBox/OverviewInfoBox.js @@ -1,30 +1,33 @@ import { round } from 'lodash' import InfoBox from './InfoBox' import TrendWidget from '../Widgets/TrendWidget' -import StatWidget from '../Widgets/StatWidget' import useApi from '../../hooks/useApi' import InfoBoxPaneContainer from './Common/InfoBoxPaneContainer' import { formatLargeNumber } from '../../utils/format' import Widget from '../Widgets/Widget' +import DaoWidget from '../Widgets/DaoWidget' import Currency from '../Common/Currency' import { useMarket } from '../../data/market' -import { useStats } from '../../data/stats' import { useDataCredits } from '../../data/datacredits' import { useValidatorStats } from '../../data/validators' +import { useStats } from '../../data/stats' +import StatWidget from '../Widgets/StatWidget' const OverviewInfoBox = () => { const { data: hotspots } = useApi('/metrics/hotspots') - const { data: blocks } = useApi('/metrics/blocks') const { stats: validatorStats } = useValidatorStats() + const { data: mobileStats } = useApi('/metrics/cells') + const { data: validatorMetrics } = useApi('/metrics/validators') const { market } = useMarket() - const { stats } = useStats() + const { stats: marketStats } = useStats() const { dataCredits } = useDataCredits() + const { data: blocks } = useApi('/metrics/blocks') return ( - +
+ Welcome to{' '}

Helium Explorer

@@ -32,7 +35,7 @@ const OverviewInfoBox = () => { } description={
- + Helium Explorer is a Block Explorer and Analytics Platform for{' '} { } > - } + icon="/images/hnt.svg" + extra={ +
+ +
+ +
+ } + linkTo="/validators" + /> + + } + linkTo="/iot" /> - + } + linkTo="/mobile" /> Based on data provided by
CoinGecko} + title="HNT Market Price" + tooltip={ + + Based on data provided by{' '} + + CoinGecko + + + } value={} change={round(market?.priceChange, 2)} changeSuffix="%" isLoading={!market} linkTo="/market" /> + + } + subtitle={ + + Vol: + + } + isLoading={!market || !marketStats} + linkTo="/market" + /> { } change={} isLoading={!dataCredits} - linkTo="/market" + linkTo="/iot" /> { isLoading={!market || !validatorStats} linkTo="/validators" /> - - ) diff --git a/components/InfoBox/Validators/StatisticsPane.js b/components/InfoBox/Validators/StatisticsPane.js new file mode 100644 index 00000000..4b2cb0ed --- /dev/null +++ b/components/InfoBox/Validators/StatisticsPane.js @@ -0,0 +1,112 @@ +import InfoBoxPaneContainer from '../Common/InfoBoxPaneContainer' +import Widget from '../../Widgets/Widget' +import VersionsWidget from '../../Widgets/VersionsWidget' +import StatWidget from '../../Widgets/StatWidget' +import { formatLargeNumber, formatPercent } from '../../../utils/format' +import { calculateValidatorAPY } from '../../../utils/validators' +import Currency from '../../Common/Currency' +import { useValidatorStats } from '../../../data/validators' +import { useMarket } from '../../../data/market' +import { useElections } from '../../../data/consensus' +import useApi from '../../../hooks/useApi' +import TrendWidget from '../../Widgets/TrendWidget' +import { useEffect, useState } from 'react' +import { round } from 'lodash' + +const TICKER = 'HNT' + +const StatisticsPane = () => { + const { data: stats } = useApi('/metrics/validators') + const { data: blocks } = useApi('/metrics/blocks') + const { consensusGroups } = useElections() + const { stats: validatorStats } = useValidatorStats() + const { market } = useMarket() + + const [processingData, setProcessingData] = useState(true) + const [blockTimeDay, setBlockTimeDay] = useState() + + useEffect(() => { + if (!!blocks) { + setProcessingData(true) + const blockTimeDayArray = blocks?.blockTimeDay?.map((bt) => { + bt.value = round(bt.value, 2) + return bt + }) + setBlockTimeDay(blockTimeDayArray) + setProcessingData(false) + } + }, [blocks]) + + return ( + + + + +
Active: {validatorStats?.active}
+
Staked: {validatorStats?.staked?.count}
+
+ } + isLoading={!validatorStats} + /> + + + } + isLoading={!market || !validatorStats} + /> + + + + + + + ) +} + +export default StatisticsPane diff --git a/components/InfoBox/ValidatorsInfoBox.js b/components/InfoBox/ValidatorsInfoBox.js index 8acc8fa9..63f067d9 100644 --- a/components/InfoBox/ValidatorsInfoBox.js +++ b/components/InfoBox/ValidatorsInfoBox.js @@ -1,86 +1,16 @@ import InfoBox from './InfoBox' import TabNavbar, { TabPane } from '../Nav/TabNavbar' -import Widget from '../Widgets/Widget' -import { clamp } from 'lodash' -import { formatLargeNumber, formatPercent } from '../../utils/format' -import VersionsWidget from '../Widgets/VersionsWidget' -import { useElections } from '../../data/consensus' -import useApi from '../../hooks/useApi' -import InfoBoxPaneContainer from './Common/InfoBoxPaneContainer' -import StatWidget from '../Widgets/StatWidget' -import { differenceInDays } from 'date-fns' -import { useValidatorStats } from '../../data/validators' -import Currency from '../Common/Currency' -import { useMarket } from '../../data/market' import ElectionsPane from './Common/ElectionsPane' import AllValidatorsPane from './Validators/AllValidatorsPane' import ConsensusGroupPane from './Validators/ConsensusGroupPane' - -const TICKER = 'HNT' +import StatisticsPane from './Validators/StatisticsPane' const ValidatorsInfoBox = () => { - const { data: stats } = useApi('/metrics/validators') - const { consensusGroups } = useElections() - const { stats: validatorStats } = useValidatorStats() - const { market } = useMarket() - return ( - - - - -
Active: {validatorStats?.active}
-
Staked: {validatorStats?.staked?.count}
-
- } - isLoading={!validatorStats} - /> - - - } - isLoading={!market || !validatorStats} - /> - - - + @@ -96,24 +26,4 @@ const ValidatorsInfoBox = () => { ) } -const calculateValidatorAPY = (numValidators) => { - if (!numValidators) return 0 - - const preHalvingTokensPerDay = 300000 / 30 - const postHalvingTokensPerDay = preHalvingTokensPerDay / 2 - const daysTilHalving = clamp( - differenceInDays(new Date('2021-08-01'), new Date()), - 0, - 365, - ) - const daysAfterHalving = 365 - daysTilHalving - const blendedTokensPerDay = - preHalvingTokensPerDay * daysTilHalving + - daysAfterHalving * postHalvingTokensPerDay - const annualTokensPerValidator = blendedTokensPerDay / numValidators - const stake = 10000 - - return annualTokensPerValidator / stake -} - export default ValidatorsInfoBox diff --git a/components/Nav/NavLinks.js b/components/Nav/NavLinks.js index bf2315ce..c013e926 100644 --- a/components/Nav/NavLinks.js +++ b/components/Nav/NavLinks.js @@ -45,15 +45,15 @@ const NavLinks = ({ })} > = ({ + title, + usdAmount, + marketCap, + icon, + linkTo, + tooltip, + tooltipUrl, + extra, +}) => { + return ( +
+ +
+
+
+
+ +
+ {title} + {tooltip && } +
+
+ +
+ + {usdAmount} + + {marketCap} +
+
+ {extra} +
+ +
+ arrow right +
+
+ +
+ ) +} + +export default DaoWidget diff --git a/components/Widgets/StatWidget.js b/components/Widgets/StatWidget.js index 4ac37fdd..13ac5684 100644 --- a/components/Widgets/StatWidget.js +++ b/components/Widgets/StatWidget.js @@ -15,6 +15,7 @@ const StatWidget = ({ valueSuffix, changeSuffix, subtitle, + transparent = false, }) => { const secondLastValue = series && series.length > 1 ? series[series.length - 2]?.[dataKey] : 0 @@ -31,18 +32,21 @@ const StatWidget = ({ + subtitle={ + subtitle || ( + + ) } isLoading={isLoading} linkTo={linkTo} span={span} valueSuffix={valueSuffix} + transparent={transparent} /> ) } diff --git a/components/Widgets/TrendWidget.js b/components/Widgets/TrendWidget.js index 45b9aa5d..743280e8 100644 --- a/components/Widgets/TrendWidget.js +++ b/components/Widgets/TrendWidget.js @@ -42,6 +42,7 @@ const TrendWidget = ({ isLoading = false, periodLabel, linkTo, + transparent = false, }) => { const secondLastValue = series && series.length > 1 ? series[series?.length - 2]?.value : 0 @@ -135,7 +136,13 @@ const TrendWidget = ({ } return ( -
{inner}
+
+ {inner} +
) } diff --git a/components/Widgets/Widget.js b/components/Widgets/Widget.js index db638b75..5edf56dc 100644 --- a/components/Widgets/Widget.js +++ b/components/Widgets/Widget.js @@ -27,6 +27,7 @@ const Widget = ({ linkTo, className, titleIcon, + transparent = false, hidden, }) => { const externalLink = linkTo && /^https?:\/\//.test(linkTo) @@ -37,23 +38,23 @@ const Widget = ({
{titleIcon} {isLoading && !title && } - {title &&
{title}
} + {title &&
{title}
} {(tooltip || tooltipUrl) && ( )}
{icon &&
{icon}
} -
+
{isLoading ? ( ) : (

@@ -96,7 +97,7 @@ const Widget = ({ {(onClick || linkTo) && (

{externalLink ? ( - + ) : ( )} @@ -114,8 +115,10 @@ const Widget = ({ target="_blank" rel="noopener noreferrer" className={classNames( - 'bg-gray-200 p-3 rounded-lg flex transition-all cursor-pointer hover:bg-gray-300', + 'flex cursor-pointer rounded-lg p-3 transition-all', { + 'bg-gray-200': !transparent, + 'hover:bg-gray-300': !transparent, 'col-span-1': span === 1, 'col-span-2': span === 2, }, @@ -131,8 +134,10 @@ const Widget = ({ return ( { const stats = await client.stats.dcBurns() return { + ...stats, totalWeek: stats.lastWeek.total, totalMonth: stats.lastMonth.total, } diff --git a/data/makers.js b/data/makers.js index 8b29a9ba..6f7ee0fb 100644 --- a/data/makers.js +++ b/data/makers.js @@ -1,9 +1,10 @@ import Balance, { CurrencyType } from '@helium/currency' +import qs from 'qs' import { useEffect, useMemo, useState } from 'react' import useApi from '../hooks/useApi' -export const useMakers = () => { - const { data: makersData } = useApi('/makers') +export const useMakers = ({ type = 'lorawan' } = {}) => { + const { data: makersData } = useApi(`/makers?${qs.stringify({ type })}`) const makers = useMemo(() => { if (!makersData) return [] diff --git a/hooks/useSelectedCity.js b/hooks/useSelectedCity.js index 0a095139..d36f7fb7 100644 --- a/hooks/useSelectedCity.js +++ b/hooks/useSelectedCity.js @@ -26,7 +26,7 @@ const useSelectedCity = () => { type: SET_SELECTED_CITY, payload: { ...city, geometry }, }) - history.push(`/hotspots/cities/${city.cityId}`) + history.push(`/iot/cities/${city.cityId}`) }, [dispatch, history], ) diff --git a/hooks/useSelectedHex.js b/hooks/useSelectedHex.js index 66f49118..ac9ed5b9 100644 --- a/hooks/useSelectedHex.js +++ b/hooks/useSelectedHex.js @@ -25,7 +25,7 @@ const useSelectedHex = () => { type: SET_SELECTED_HEX, payload: { ...hex }, }) - history.push(`/hotspots/hex/${index}`) + history.push(`/iot/hex/${index}`) }, [dispatch, history], ) diff --git a/next.config.js b/next.config.js index a279450a..8d22e077 100644 --- a/next.config.js +++ b/next.config.js @@ -11,6 +11,25 @@ module.exports = { }, ] }, + async redirects() { + return [ + { + source: '/hotspots', + destination: '/iot', + permanent: false, + }, + { + source: '/hotspots/hex/:index', + destination: '/iot/hex/:index', + permanent: false, + }, + { + source: '/hotspots/cities/:cityid', + destination: '/iot/cities/:cityid', + permanent: false, + }, + ] + }, webpack: (config, options) => { config.module.rules.push({ test: /\.csv$/, @@ -18,8 +37,8 @@ module.exports = { options: { dynamicTyping: true, header: false, - skipEmptyLines: true - } + skipEmptyLines: true, + }, }) return config diff --git a/package.json b/package.json index f821356d..85e2b300 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "react-mapbox-gl": "^5.1.1", "react-qr-code": "^2.0.0", "react-responsive": "^9.0.0-beta.6", - "react-router-dom": "^5.2.0", + "react-router-dom": "^5.3.3", "react-router-i18n": "^1.1.0", "react-time-ago": "^7.0.0", "react-timestamp": "^5.1.0", @@ -109,6 +109,7 @@ "husky": "^7.0.1", "postcss": "^8.3.0", "prettier": "2.5.1", + "prettier-plugin-tailwindcss": "^0.1.13", "pretty-quick": "^3.1.0", "tailwindcss": "^3.0.23", "typescript": "^4.7.3" diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..c16670dc --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: [require('prettier-plugin-tailwindcss')], +} diff --git a/public/images/iot.svg b/public/images/iot.svg new file mode 100644 index 00000000..f73cbb46 --- /dev/null +++ b/public/images/iot.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/mobile.svg b/public/images/mobile.svg index 115cc353..9583202e 100644 --- a/public/images/mobile.svg +++ b/public/images/mobile.svg @@ -1,3 +1,3 @@ - - + + diff --git a/utils/validators.js b/utils/validators.js new file mode 100644 index 00000000..ee1071b1 --- /dev/null +++ b/utils/validators.js @@ -0,0 +1,22 @@ +import { clamp } from 'lodash' +import { differenceInDays } from 'date-fns' + +export const calculateValidatorAPY = (numValidators) => { + if (!numValidators) return 0 + + const preHalvingTokensPerDay = 300000 / 30 + const postHalvingTokensPerDay = preHalvingTokensPerDay / 2 + const daysTilHalving = clamp( + differenceInDays(new Date('2021-08-01'), new Date()), + 0, + 365, + ) + const daysAfterHalving = 365 - daysTilHalving + const blendedTokensPerDay = + preHalvingTokensPerDay * daysTilHalving + + daysAfterHalving * postHalvingTokensPerDay + const annualTokensPerValidator = blendedTokensPerDay / numValidators + const stake = 10000 + + return annualTokensPerValidator / stake +} diff --git a/yarn.lock b/yarn.lock index e66913dd..354690d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5616,6 +5616,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-plugin-tailwindcss@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.1.13.tgz#ca1071361dc7e2ed5d95a2ee36825ce45f814942" + integrity sha512-/EKQURUrxLu66CMUg4+1LwGdxnz8of7IDvrSLqEtDqhLH61SAlNNUSr90UTvZaemujgl3OH/VHg+fyGltrNixw== + prettier@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" @@ -6234,16 +6239,16 @@ react-responsive@^9.0.0-beta.6: prop-types "^15.6.1" shallow-equal "^1.2.1" -react-router-dom@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363" - integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ== +react-router-dom@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.3.tgz#8779fc28e6691d07afcaf98406d3812fe6f11199" + integrity sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng== dependencies: "@babel/runtime" "^7.12.13" history "^4.9.0" loose-envify "^1.3.1" prop-types "^15.6.2" - react-router "5.2.1" + react-router "5.3.3" tiny-invariant "^1.0.2" tiny-warning "^1.0.0" @@ -6254,10 +6259,10 @@ react-router-i18n@^1.1.0: dependencies: prop-types "^15.7.2" -react-router@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" - integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ== +react-router@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.3.tgz#8e3841f4089e728cf82a429d92cdcaa5e4a3a288" + integrity sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w== dependencies: "@babel/runtime" "^7.12.13" history "^4.9.0"