(() =>
+ 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 @@