diff --git a/src/assets/img/chain/polkadot.png b/src/assets/img/chain/polkadot.png index 15da3eda0..5675d61d5 100644 Binary files a/src/assets/img/chain/polkadot.png and b/src/assets/img/chain/polkadot.png differ diff --git a/src/components/assets/NativeAssetList.vue b/src/components/assets/NativeAssetList.vue index e2051d9bb..8e00b8907 100644 --- a/src/components/assets/NativeAssetList.vue +++ b/src/components/assets/NativeAssetList.vue @@ -129,7 +129,7 @@
- {{ $n(truncate(lockInDappStaking + vestingTtl + reservedTtl, 3)) }} + {{ $n(truncate(lockedAmount, 3)) }}
{{ nativeTokenSymbol }} @@ -252,6 +252,28 @@
+ + +
+
+
{{ $t('governance.governance') }}
+
+ + +
+
+
@@ -310,6 +332,7 @@ export default defineComponent({ const vestingTtl = ref(0); const reservedTtl = ref(0); const lockInDappStaking = ref(0); + const lockInDemocracy = ref(0); const isRocstar = ref(false); const isShibuya = ref(false); const isFaucet = ref(false); @@ -369,6 +392,10 @@ export default defineComponent({ } }; + const lockedAmount = computed(() => + Math.max(vestingTtl.value, lockInDappStaking.value, lockInDemocracy.value, reservedTtl.value) + ); + watch([nativeTokenSymbol, balance, props], setBalanceData, { immediate: false }); watchEffect(() => { @@ -377,11 +404,14 @@ export default defineComponent({ // Memo: `vesting ` -> there has been inputted 1 space here const vesting = accountDataRef.locks.find((it) => u8aToString(it.id) === 'vesting '); const dappStake = accountDataRef.locks.find((it) => u8aToString(it.id) === 'dapstake'); + const democracy = accountDataRef.locks.find((it) => u8aToString(it.id) === 'democrac'); const reserved = accountDataRef.reserved; + if (vesting) { const amount = String(vesting.amount); vestingTtl.value = Number(ethers.utils.formatEther(amount)); } + if (dappStake) { const amount = String(dappStake.amount); lockInDappStaking.value = Number(ethers.utils.formatEther(amount)); @@ -393,6 +423,10 @@ export default defineComponent({ const amount = reserved.toString(); reservedTtl.value = Number(ethers.utils.formatEther(amount)); } + + if (democracy) { + lockInDemocracy.value = Number(ethers.utils.formatEther(democracy.amount.toString())); + } }); // Ref: https://stackoverflow.com/questions/48143381/css-expand-contract-animation-to-show-hide-content @@ -414,6 +448,7 @@ export default defineComponent({ isShibuya, vestingTtl, lockInDappStaking, + lockInDemocracy, isFaucet, transferableBalance, isModalTransfer, @@ -437,6 +472,7 @@ export default defineComponent({ handleModalFaucet, handleModalEvmWithdraw, expandAsset, + lockedAmount, }; }, }); diff --git a/src/components/dashboard/InflationRateChart.vue b/src/components/dashboard/InflationRateChart.vue index 64c411dda..bc555c320 100644 --- a/src/components/dashboard/InflationRateChart.vue +++ b/src/components/dashboard/InflationRateChart.vue @@ -11,7 +11,7 @@
- {{ estimatedInflation?.toFixed(1) }}% + {{ estimatedInflation?.toFixed(2) }}%
@@ -22,7 +22,7 @@ diff --git a/src/hooks/useBalance.ts b/src/hooks/useBalance.ts index 8bc1844f1..9bdb7804b 100644 --- a/src/hooks/useBalance.ts +++ b/src/hooks/useBalance.ts @@ -9,7 +9,7 @@ import { useStore } from 'src/store'; import { computed, onUnmounted, ref, Ref, watch } from 'vue'; import { isValidEvmAddress } from '@astar-network/astar-sdk-core'; import { useDapps } from 'src/staking-v3'; -import { Option, Vec, u32 } from '@polkadot/types'; +import { Option, Vec, u128, u32 } from '@polkadot/types'; // Temporarily moved here until uplift polkadot js for astar.js export const getVested = ({ @@ -223,11 +223,20 @@ export class AccountData { } public getUsableTransactionBalance(): BN { - return this.free.sub(this.frozen); + // refs. + // https://wiki.polkadot.network/docs/learn-account-balances + // https://github.com/paritytech/polkadot-sdk/blob/e8da320734ae44803f89dd2b35b3cfea0e1ecca1/substrate/frame/balances/src/impl_fungible.rs#L44 + const existentialDeposit = $api?.consts.balances.existentialDeposit; + if (!existentialDeposit) { + return new BN(0); + } + + const untouchable = BN.max(this.frozen.sub(this.reserved), existentialDeposit); + return this.free.sub(untouchable); } public getUsableFeeBalance(): BN { - return this.free.sub(this.frozen); + return this.getUsableTransactionBalance(); } public free: BN; @@ -241,26 +250,19 @@ export class AccountData { public locks: (PalletBalancesBalanceLock | BalanceLockTo212)[]; } -// FIXME: the class might be inherited by AccountData -export class AccountDataH160 { +export class AccountDataH160 extends AccountData { constructor( - public free: BN, - public reserved: BN, - public frozen: BN, - public flags: BN, - public vested: BN, - public vesting: ExtendedVestingInfo[], - public vestedClaimable: BN, - public remainingVests: BN, - public locks: (PalletBalancesBalanceLock | BalanceLockTo212)[] - ) {} - - public getUsableTransactionBalance(): BN { - return this.free.sub(this.frozen); - } - - public getUsableFeeBalance(): BN { - return this.free.sub(this.flags); + free: BN, + reserved: BN, + frozen: BN, + flags: BN, + vested: BN, + vesting: ExtendedVestingInfo[], + vestedClaimable: BN, + remainingVests: BN, + locks: (PalletBalancesBalanceLock | BalanceLockTo212)[] + ) { + super(free, reserved, frozen, flags, vested, vesting, vestedClaimable, remainingVests, locks); } } diff --git a/src/hooks/useGovernance.ts b/src/hooks/useGovernance.ts index a543e662d..1b4c741b8 100644 --- a/src/hooks/useGovernance.ts +++ b/src/hooks/useGovernance.ts @@ -1,6 +1,7 @@ import { computed, ref, onMounted } from 'vue'; import { useNetworkInfo } from './useNetworkInfo'; import axios from 'axios'; +import { endpointKey } from 'src/config/chainEndpoints'; export type GovernanceData = { title: string; @@ -11,6 +12,7 @@ export type GovernanceData = { const proposals = ref([]); const ongoingReferenda = ref(); +const hasProposals = computed(() => proposals.value.length > 0); const fetchProposals = async (network: string): Promise => { try { @@ -43,15 +45,11 @@ const fetchOngoingReferenda = async (network: string): Promise { + (referenda: { title: string; referendumIndex: number; state: string }) => { return { title: referenda.title, index: referenda.referendumIndex, - state: referenda.referendumState.state, + state: referenda.state ?? 'Unknown', url: `https://${network}.subsquare.io/democracy/referenda/${referenda.referendumIndex}`, }; } @@ -67,14 +65,17 @@ const fetchOngoingReferenda = async (network: string): Promise(() => { return networkNameSubstrate.value.toLowerCase(); }); const isGovernanceEnabled = computed(() => { - return networkLowercase.value === 'shibuya'; + return ( + currentNetworkIdx.value === endpointKey.ASTAR || + currentNetworkIdx.value === endpointKey.SHIBUYA + ); }); const governanceUrl = computed(() => { @@ -98,5 +99,6 @@ export function useGovernance() { proposals, ongoingReferenda, governanceUrl, + hasProposals, }; } diff --git a/src/hooks/useInflation.ts b/src/hooks/useInflation.ts index 7010cac7b..fc551d690 100644 --- a/src/hooks/useInflation.ts +++ b/src/hooks/useInflation.ts @@ -1,9 +1,8 @@ -import { computed, watch, ref, Ref, ComputedRef } from 'vue'; +import { computed, ref, Ref, ComputedRef, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStore } from 'src/store'; import { container } from 'src/v2/common'; import { - BurnEvent, IBalancesRepository, IInflationRepository, ITokenApiRepository, @@ -28,15 +27,20 @@ type UseInflation = { estimateRealizedInflation: () => Promise; }; +const estimatedInflation = ref(undefined); +const maximumInflationData = ref<[number, number][]>([]); +const realizedInflationData = ref<[number, number][]>([]); +const realizedAdjustableStakersPart = ref(0); + export function useInflation(): UseInflation { const store = useStore(); const { t } = useI18n(); const { eraLengths, currentEraInfo } = useDappStaking(); const { networkNameSubstrate } = useNetworkInfo(); - const estimatedInflation = ref(undefined); - const maximumInflationData = ref<[number, number][]>([]); - const realizedInflationData = ref<[number, number][]>([]); - const realizedAdjustableStakersPart = ref(0); + // const estimatedInflation = ref(undefined); + // const maximumInflationData = ref<[number, number][]>([]); + // const realizedInflationData = ref<[number, number][]>([]); + // const realizedAdjustableStakersPart = ref(0); const activeInflationConfiguration = computed( () => store.getters['general/getActiveInflationConfiguration'] @@ -63,23 +67,6 @@ export function useInflation(): UseInflation { return await inflationRepository.getInflationParams(); }; - const getBurnEvents = async (): Promise => { - // Ignore burn events with less than 1M ASTAR. They are not impacting charts a lot small burn amounts - // could be a spam. - const minBurn = BigInt('1000000000000000000000000'); - const tokenApiRepository = container.get(Symbols.TokenApiRepository); - const burnEvents = await tokenApiRepository.getBurnEvents( - networkNameSubstrate.value.toLowerCase() - ); - - return burnEvents.filter((item) => item.amount >= minBurn); - }; - - /** - * Estimates the realized inflation rate percentage based on the actual total issuance at the beginning - * and estimated total issuance at the end of the current cycle. - * According to the https://github.com/AstarNetwork/astar-apps/issues/1259 - */ const estimateRealizedInflation = async (): Promise => { let inflation: number | undefined; @@ -97,25 +84,6 @@ export function useInflation(): UseInflation { const initialTotalIssuance = await balancesRepository.getTotalIssuance(period1StartBlock - 1); // - const realizedTotalIssuance = await balancesRepository.getTotalIssuance(); - const burnEvents = await getBurnEvents(); - // Add first and last block so charts can be easily drawn. - burnEvents.splice(0, 0, { - blockNumber: period1StartBlock, - amount: BigInt(0), - user: '', - timestamp: 0, - }); - burnEvents.push({ - blockNumber: currentBlock.value, - amount: BigInt(0), - user: '', - timestamp: 0, - }); - - const totalBurn = burnEvents.reduce((acc, item) => acc + item.amount, BigInt(0)); - // Used to calculate inflation rate. - const initialTotalIssuanceWithoutBurn = initialTotalIssuance - totalBurn; - const { periodsPerCycle, standardEraLength, @@ -126,25 +94,13 @@ export function useInflation(): UseInflation { standardEraLength * periodsPerCycle * (standardErasPerBuildAndEarnPeriod + standardErasPerVotingPeriod); - const blockDifference = BigInt(currentBlock.value - period1StartBlock); - const slope = - BigInt((realizedTotalIssuance - initialTotalIssuanceWithoutBurn).toString()) / - blockDifference; // Estimate total issuance at the end of the current cycle. - const endOfCycleBlock = period1StartBlock + cycleLengthInBlocks; - const endOfCycleTotalIssuance = Number( - ethers.utils.formatEther( - slope * BigInt(endOfCycleBlock - period1StartBlock) + initialTotalIssuanceWithoutBurn - ) - ); + const endOfCycleBlock = Math.max(period1StartBlock + cycleLengthInBlocks, currentBlock.value); - // Estimated inflation at the end of the current cycle. + // Calculate realized inflation. inflation = - (100 * - (endOfCycleTotalIssuance - - Number(ethers.utils.formatEther(initialTotalIssuanceWithoutBurn.toString())))) / - endOfCycleTotalIssuance; + 100 * (Number(realizedTotalIssuance - initialTotalIssuance) / Number(initialTotalIssuance)); // Calculate maximum and realized inflation for each era in the cycle. calculateMaximumInflationData( @@ -154,16 +110,8 @@ export function useInflation(): UseInflation { cycleLengthInBlocks, inflationParameters.value?.maxInflationRate ?? 0, eraLengths.value.standardEraLength, - burnEvents - ); - - calculateRealizedInflationData( - period1StartBlock, - currentBlock.value, - slope, - eraLengths.value.standardEraLength, - initialTotalIssuance, - burnEvents + eraLengths.value.standardErasPerVotingPeriod, + eraLengths.value.standardErasPerBuildAndEarnPeriod ); calculateAdjustableStakerRewards( @@ -172,6 +120,8 @@ export function useInflation(): UseInflation { inflationParameters.value.adjustableStakersPart, inflationParameters.value.idealStakingRate ); + + fetchRealizedInflationData(currentBlock.value, realizedTotalIssuance); } catch (error) { console.error('Error calculating realized inflation', error); } @@ -185,60 +135,53 @@ export function useInflation(): UseInflation { firstBlockIssuance: bigint, cycleLengthInBlocks: number, maxInflation: number, - eraLength: number, - burnEvents: BurnEvent[] + standardEraLength: number, + standardErasPerVotingPeriod: number, + standardErasPerBuildAndEarnPeriod: number ): void => { const result: [number, number][] = []; const inflation = BigInt(Math.floor(maxInflation * 100)) * BigInt('10000000000000000'); const cycleProgression = (firstBlockIssuance * inflation) / BigInt('1000000000000000000'); const cycleLength = BigInt(cycleLengthInBlocks); - // One sample per era. - for (let j = 0; j < burnEvents.length - 1; j++) { - for ( - let i = burnEvents[j].blockNumber; - i <= burnEvents[j + 1].blockNumber + eraLength; - i += eraLength - ) { - const inflation = - (cycleProgression * BigInt(i - firstBlock)) / cycleLength + - firstBlockIssuance - - burnEvents[j].amount; + // One sample per era (take into consideration that voting era takes multiple standard era lengths). + let era = 0; + const getEraLength = (era: number, block: number): number => + era === 1 || era % (standardErasPerBuildAndEarnPeriod + 2) === 0 + ? standardEraLength * standardErasPerVotingPeriod + : standardEraLength; - result.push([i, Number(ethers.utils.formatEther(inflation.toString()))]); - } + for (let i = firstBlock - 1; i <= lastBlock; i += getEraLength(era, i)) { + const inflation = + (cycleProgression * BigInt(i - firstBlock)) / cycleLength + firstBlockIssuance; + + result.push([i, Number(ethers.utils.formatEther(inflation.toString()))]); + era++; } maximumInflationData.value = result; }; - const calculateRealizedInflationData = ( - firstBlock: number, - lastBlock: number, - slope: bigint, - eraLength: number, - firstBlockIssuance: bigint, - burnEvents: BurnEvent[] - ): void => { - const result: [number, number][] = []; - - for (let j = 0; j < burnEvents.length - 1; j++) { - for ( - let i = burnEvents[j].blockNumber; - i <= burnEvents[j + 1].blockNumber + eraLength; - i += eraLength - ) { - const currentBlockIssuance = Number( - ethers.utils.formatEther( - slope * BigInt(i - firstBlock) + firstBlockIssuance - burnEvents[j].amount - ) - ); - - result.push([i, currentBlockIssuance]); - } - } + const fetchRealizedInflationData = async ( + currentBlock: number, + totalIssuance: bigint + ): Promise => { + const tokenApiRepository = container.get(Symbols.TokenApiRepository); + const issuanceHistory = await tokenApiRepository.getTokeIssuanceHistory( + networkNameSubstrate.value.toLowerCase() + ); - realizedInflationData.value = result; + // Current issuance is not included in the history yet. + issuanceHistory.push({ + block: currentBlock, + timestamp: Date.now(), + balance: totalIssuance, + }); + + realizedInflationData.value = issuanceHistory.map((item) => [ + item.block, + Number(ethers.utils.formatEther(item.balance.toString())), + ]); }; const calculateAdjustableStakerRewards = ( @@ -256,6 +199,15 @@ export function useInflation(): UseInflation { realizedAdjustableStakersPart.value = Number(result.toFixed(3)); }; + watch(eraLengths, async () => { + if ( + (eraLengths.value && maximumInflationData.value.length === 0) || + realizedInflationData.value.length === 0 + ) { + estimateRealizedInflation(); + } + }); + return { activeInflationConfiguration: activeInflationConfiguration, estimatedInflation, diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index 5bb25d63c..126bf4689 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -1146,5 +1146,7 @@ export default { governance: { newProposals: 'New proposals', ongoingReferenda: 'Ongoing referenda', + governance: 'Governance', + noProposals: 'No proposals at this time. Visit Subsquare to create a new one.', }, }; diff --git a/src/staking-v3/components/data/DataList.vue b/src/staking-v3/components/data/DataList.vue index 841e6dc92..05f355997 100644 --- a/src/staking-v3/components/data/DataList.vue +++ b/src/staking-v3/components/data/DataList.vue @@ -127,7 +127,7 @@