diff --git a/src/config/networks.ts b/src/config/networks.ts index 64286f09fa..9213bb13b6 100644 --- a/src/config/networks.ts +++ b/src/config/networks.ts @@ -1,7 +1,6 @@ // Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { BN, BN_MILLION } from '@polkadot/util'; import { DefaultParams } from 'consts'; import { ReactComponent as AzeroIconSVG } from 'img/a0_icon.svg'; import { ReactComponent as AzeroInlineSVG } from 'img/a0_inline.svg'; @@ -62,7 +61,6 @@ if (import.meta.env.VITE_DISABLE_MAINNET !== '1') { params: { ...DefaultParams, stakeTarget: 0.5, - yearlyInflationInTokens: BN_MILLION.mul(new BN(30)).toNumber(), }, defaultFeeReserve: 0.1, } as const; @@ -122,7 +120,6 @@ if (import.meta.env.VITE_DISABLE_TESTNET !== '1') { params: { ...DefaultParams, stakeTarget: 0.5, - yearlyInflationInTokens: BN_MILLION.mul(new BN(30)).toNumber(), }, defaultFeeReserve: 0.1, } as const; @@ -181,7 +178,6 @@ if (import.meta.env.VITE_ENABLE_CUSTOM_NETWORK === '1') { params: { ...DefaultParams, stakeTarget: 0.5, - yearlyInflationInTokens: BN_MILLION.mul(new BN(30)).toNumber(), }, defaultFeeReserve: 0.1, } as const; @@ -241,7 +237,6 @@ if (import.meta.env.VITE_DISABLE_DEVNET !== '1') { params: { ...DefaultParams, stakeTarget: 0.5, - yearlyInflationInTokens: BN_MILLION.mul(new BN(30)).toNumber(), }, defaultFeeReserve: 0.1, } as const; @@ -301,7 +296,6 @@ if (import.meta.env.MODE === 'development') { params: { ...DefaultParams, stakeTarget: 0.5, - yearlyInflationInTokens: BN_MILLION.mul(new BN(30)).toNumber(), }, defaultFeeReserve: 0.1, } as const; diff --git a/src/contexts/Api/defaults.ts b/src/contexts/Api/defaults.ts index 3b7762fe60..6aefd3fa29 100644 --- a/src/contexts/Api/defaults.ts +++ b/src/contexts/Api/defaults.ts @@ -38,6 +38,7 @@ if (!isValidConfiguredNetworkName(cachedNetworkName)) { } export const consts: APIConstants = { + chainDecimals: 12, bondDuration: new BigNumber(0), maxNominations: new BigNumber(0), sessionsPerEra: new BigNumber(0), diff --git a/src/contexts/Api/index.tsx b/src/contexts/Api/index.tsx index 3fd8772064..4189638bae 100644 --- a/src/contexts/Api/index.tsx +++ b/src/contexts/Api/index.tsx @@ -33,6 +33,7 @@ import type { AnyApi, NetworkName } from 'types'; import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import * as defaults from './defaults'; import { defaultNetworkName } from './defaults'; +import typesBundle from './typesBundle'; export const APIProvider = ({ children }: { children: React.ReactNode }) => { // Get the initial network and prepare meta tags if necessary. @@ -182,7 +183,10 @@ export const APIProvider = ({ children }: { children: React.ReactNode }) => { if (!provider) return; // initiate new api and set connected. - const newApi = await ApiPromise.create({ provider }); + const newApi = await ApiPromise.create({ + provider, + typesBundle, + }); // set connected here in case event listeners have not yet initialised. setApiStatus('connected'); @@ -271,6 +275,7 @@ export const APIProvider = ({ children }: { children: React.ReactNode }) => { const expectedEraTime = FallbackExpectedEraTime; setConsts({ + chainDecimals: newApi.registry.chainDecimals[0], bondDuration, maxNominations, sessionsPerEra, diff --git a/src/contexts/Api/types.ts b/src/contexts/Api/types.ts index 7bbd76f475..846901b3c4 100644 --- a/src/contexts/Api/types.ts +++ b/src/contexts/Api/types.ts @@ -13,6 +13,7 @@ export interface NetworkState { meta: Network; } export interface APIConstants { + chainDecimals: number; bondDuration: BigNumber; maxNominations: BigNumber; sessionsPerEra: BigNumber; diff --git a/src/contexts/Api/typesBundle.ts b/src/contexts/Api/typesBundle.ts new file mode 100644 index 0000000000..7355d7c533 --- /dev/null +++ b/src/contexts/Api/typesBundle.ts @@ -0,0 +1,22 @@ +const typesBundle = { + spec: { + 'aleph-node': { + runtime: { + AlephSessionApi: [ + { + methods: { + yearly_inflation: { + description: 'Returns inflation from now to now + one year.', + params: [], + type: 'Perbill', + }, + }, + version: 1, + }, + ], + }, + }, + }, +}; + +export default typesBundle; diff --git a/src/contexts/Network/defaults.ts b/src/contexts/Network/defaults.ts index 02af14f760..253d7971e1 100644 --- a/src/contexts/Network/defaults.ts +++ b/src/contexts/Network/defaults.ts @@ -12,7 +12,9 @@ export const activeEra: ActiveEra = { index: new BigNumber(0), start: new BigNumber(0), }; + export const metrics: NetworkMetrics = { + azeroCap: new BigNumber(0), totalIssuance: new BigNumber(0), auctionCounter: new BigNumber(0), earliestStoredSession: new BigNumber(0), diff --git a/src/contexts/Network/index.tsx b/src/contexts/Network/index.tsx index 56875ef581..d3da8689d1 100644 --- a/src/contexts/Network/index.tsx +++ b/src/contexts/Network/index.tsx @@ -40,12 +40,14 @@ export const NetworkMetricsProvider = ({ const subscribeToMetrics = async () => { const unsub = await api.queryMulti( [ + api.query.aleph.azeroCap, api.query.balances.totalIssuance, api.query.staking.minimumActiveStake, ], - ([totalIssuance, minimumActiveStake]: AnyApi) => { + ([azeroCap, totalIssuance, minimumActiveStake]: AnyApi) => { setStateWithRef( { + azeroCap: new BigNumber(azeroCap.toString()), totalIssuance: new BigNumber(totalIssuance.toString()), auctionCounter: metrics.auctionCounter, earliestStoredSession: metrics.earliestStoredSession, diff --git a/src/contexts/Network/types.ts b/src/contexts/Network/types.ts index b3c5dd7e4b..fd7569a770 100644 --- a/src/contexts/Network/types.ts +++ b/src/contexts/Network/types.ts @@ -9,6 +9,7 @@ export interface NetworkMetricsContextInterface { } export interface NetworkMetrics { + azeroCap: BigNumber; totalIssuance: BigNumber; auctionCounter: BigNumber; earliestStoredSession: BigNumber; diff --git a/src/library/Hooks/useFillVariables/index.tsx b/src/library/Hooks/useFillVariables/index.tsx index 4491cb4d03..7f8dabde6f 100644 --- a/src/library/Hooks/useFillVariables/index.tsx +++ b/src/library/Hooks/useFillVariables/index.tsx @@ -5,7 +5,12 @@ import { capitalizeFirstLetter, planckToUnit } from '@polkadot-cloud/utils'; import { useApi } from 'contexts/Api'; import { useNetworkMetrics } from 'contexts/Network'; import { usePoolsConfig } from 'contexts/Pools/PoolsConfig'; -import type { AnyJson } from 'types'; +import type { AnyJson, NetworkName } from 'types'; + +const networkToFirstYearInflation: Partial> = { + 'Aleph Zero': '27M', + 'Aleph Zero Testnet': '1000M', +}; export const useFillVariables = () => { const { network, consts } = useApi(); @@ -17,7 +22,7 @@ export const useFillVariables = () => { } = consts; const { minJoinBond, minCreateBond } = stats; const { metrics } = useNetworkMetrics(); - const { minimumActiveStake } = metrics; + const { azeroCap, minimumActiveStake } = metrics; const fillVariables = (d: AnyJson, keys: string[]) => { const fields: AnyJson = Object.entries(d).filter(([k]: any) => @@ -26,6 +31,16 @@ export const useFillVariables = () => { const transformed = Object.entries(fields).map( ([, [key, val]]: AnyJson) => { const varsToValues = [ + [ + '{AZERO_CAP}', + azeroCap + .shiftedBy(-(consts.chainDecimals + 6)) + .toFormat({ suffix: 'M' }), + ], + [ + '{INFLATION_FIRST_YEAR}', + networkToFirstYearInflation[network.name] ?? '-M', + ], ['{NETWORK_UNIT}', network.unit], ['{NETWORK_NAME}', capitalizeFirstLetter(network.name)], [ diff --git a/src/library/Hooks/useInflation/index.tsx b/src/library/Hooks/useInflation/index.tsx index 6ae63321a2..826c9f9c9e 100644 --- a/src/library/Hooks/useInflation/index.tsx +++ b/src/library/Hooks/useInflation/index.tsx @@ -1,70 +1,65 @@ // Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import BigNumber from 'bignumber.js'; +import type BigNumber from 'bignumber.js'; import { useApi } from 'contexts/Api'; import { useNetworkMetrics } from 'contexts/Network'; import { useStaking } from 'contexts/Staking'; +import { useEffect, useState } from 'react'; -const BIGNUMBER_THOUSAND = new BigNumber(1_000); -const BIGNUMBER_MILLION = new BigNumber(1_000_000); -const BIGNUMBER_BILLION = new BigNumber(1_000_000_000); - -export const useInflation = () => { - const { network } = useApi(); - const { metrics } = useNetworkMetrics(); - const { staking } = useStaking(); - const { params } = network; - const { lastTotalStake } = staking; - const { totalIssuance, auctionCounter } = metrics; - - const { - auctionAdjust, - auctionMax, - maxInflation, - stakeTarget, - yearlyInflationInTokens, - } = params; +const calculateInflation = ( + totalStaked: BigNumber, + totalIssuance: BigNumber, + yearlyInflationInPercentage: number +) => { + const stakedFraction = + totalStaked.isZero() || totalIssuance.isZero() + ? 0 + : totalStaked.dividedBy(totalIssuance).toNumber(); + const baseStakedReturn = + stakedFraction !== 0 ? yearlyInflationInPercentage / stakedFraction : 0; /* For Aleph Zero inflation is calculated based on yearlyInflationInTokens and totalIssuanceInTokens * We multiply stakedReturn by 0.9, as in case of Aleph Zero chain 10% of return goes to treasury */ + const stakedReturn = baseStakedReturn * 0.9; + + return { + inflation: yearlyInflationInPercentage, + stakedFraction, + stakedReturn, + }; +}; - const calculateInflation = ( - totalStaked: BigNumber, - numAuctions: BigNumber - ) => { - const stakedFraction = - totalStaked.isZero() || totalIssuance.isZero() - ? 0 - : totalStaked - .multipliedBy(BIGNUMBER_MILLION) - .dividedBy(totalIssuance) - .toNumber() / BIGNUMBER_MILLION.toNumber(); - const idealStake = - stakeTarget - - Math.min(auctionMax, numAuctions.toNumber()) * auctionAdjust; - const idealInterest = maxInflation / idealStake; +const useYearlyInflation = () => { + const { api } = useApi(); + const [yearlyInflation, setYearlyInflation] = useState(); - const totalIssuanceInTokens = totalIssuance - .div(BIGNUMBER_BILLION) - .div(BIGNUMBER_THOUSAND); + const getYearlyInflation = api?.call?.alephSessionApi?.yearlyInflation; - const inflation = totalIssuanceInTokens.isZero() - ? 0 - : 100 * (yearlyInflationInTokens / totalIssuanceInTokens.toNumber()); + useEffect(() => { + getYearlyInflation?.() + .then((val) => setYearlyInflation(val.toNumber() / 1_000_000_000)) + // eslint-disable-next-line no-console + .catch(console.error); + // `api` object can change in case of network change which should trigger refetch. + }, [api]); - let stakedReturn = stakedFraction ? inflation / stakedFraction : 0; - stakedReturn *= 0.9; + return yearlyInflation; +}; - return { - idealInterest, - idealStake, - inflation, - stakedFraction, - stakedReturn, - }; - }; +export const useInflation = () => { + const { + metrics: { totalIssuance }, + } = useNetworkMetrics(); + const { + staking: { lastTotalStake }, + } = useStaking(); + const yearlyInflation = useYearlyInflation(); - return calculateInflation(lastTotalStake, auctionCounter); + return calculateInflation( + lastTotalStake, + totalIssuance, + (yearlyInflation ?? 0) * 100 + ); }; diff --git a/src/locale/cn/help.json b/src/locale/cn/help.json index 653a62fcd1..14501e626b 100644 --- a/src/locale/cn/help.json +++ b/src/locale/cn/help.json @@ -114,8 +114,8 @@ "inflation": [ "通货膨胀", [ - "{NETWORK_UNIT}具有通货膨胀性.所以无封顶数量.", - "在验证人的奖励是基于抵押金额, 其余归国库所有的情况下, 每年通货膨胀率约为10%." + "{NETWORK_UNIT} is inflationary. Inflation is exponentially decreasing, starting at {INFLATION_FIRST_YEAR} {NETWORK_UNIT} emission in the first year.", + "The max supply is {AZERO_CAP} {NETWORK_UNIT}." ] ], "lastEraPayout": [ diff --git a/src/locale/en/help.json b/src/locale/en/help.json index 09080b8d6e..39d230d5bb 100644 --- a/src/locale/en/help.json +++ b/src/locale/en/help.json @@ -108,8 +108,8 @@ "inflation": [ "Inflation", [ - "{NETWORK_UNIT} is inflationary; there is no maximum number of {NETWORK_UNIT}.", - "Inflation is fixed to 30M {NETWORK_UNIT} tokens annually. This translates to 10% inflation right after launch and slowly decreasing to 0%." + "{NETWORK_UNIT} is inflationary. Inflation is exponentially decreasing, starting at {INFLATION_FIRST_YEAR} {NETWORK_UNIT} emission in the first year.", + "The max supply is {AZERO_CAP} {NETWORK_UNIT}." ] ], "lastEraPayout": [ diff --git a/src/types/@polkadot/api-base.d.ts b/src/types/@polkadot/api-base.d.ts new file mode 100644 index 0000000000..6b8db5ad65 --- /dev/null +++ b/src/types/@polkadot/api-base.d.ts @@ -0,0 +1,23 @@ +import type { + ApiTypes, + AugmentedCall, + DecoratedCallBase, +} from '@polkadot/api-base/types'; +import type { Perbill } from '@polkadot/types/interfaces/runtime'; +import type { Observable } from '@polkadot/types/types'; + +declare module '@polkadot/api-base/types/calls' { + interface AugmentedCalls { + /** 0xbc9d89904f5b923f/1 */ + alephSessionApi?: { + /** + * The API to query account nonce (aka transaction index) + * */ + yearlyInflation?: AugmentedCall Observable>; + /** + * Generic call + * */ + [key: string]: DecoratedCallBase | undefined; + }; + } +}