From 204c961dba2fa2900ac825081af5fc46a26ed43f Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Wed, 11 Dec 2024 21:18:34 +0700 Subject: [PATCH 01/33] separate payout state --- packages/app/src/controllers/Subscan/index.ts | 9 ++- packages/app/src/controllers/Subscan/types.ts | 8 +- .../app/src/hooks/useSubscanData/index.tsx | 81 +++++++++++-------- packages/app/src/pages/Overview/Payouts.tsx | 32 ++++---- packages/app/src/pages/Payouts/index.tsx | 38 ++++----- .../src/queries/useTokenPrice.tsx | 4 +- packages/plugin-staking-api/src/types.ts | 11 ++- 7 files changed, 103 insertions(+), 80 deletions(-) diff --git a/packages/app/src/controllers/Subscan/index.ts b/packages/app/src/controllers/Subscan/index.ts index 9e0a3a9f65..0fa708ab41 100644 --- a/packages/app/src/controllers/Subscan/index.ts +++ b/packages/app/src/controllers/Subscan/index.ts @@ -6,6 +6,7 @@ import { format, fromUnixTime, getUnixTime, subDays } from 'date-fns' import { poolMembersPerPage } from 'library/List/defaults' import type { PoolMember } from 'types' import type { + PayoutsAndClaims, SubscanData, SubscanEraPoints, SubscanPayout, @@ -255,7 +256,7 @@ export class Subscan { } // Take non-zero rewards in most-recent order. - static removeNonZeroAmountAndSort = (payouts: SubscanPayout[]) => { + static removeNonZeroAmountAndSort = (payouts: PayoutsAndClaims) => { const list = payouts .filter((p) => Number(p.amount) > 0) .sort((a, b) => b.block_timestamp - a.block_timestamp) @@ -269,11 +270,11 @@ export class Subscan { } // Calculate the earliest date of a payout list. - static payoutsFromDate = (payouts: SubscanPayout[], locale: Locale) => { + static payoutsFromDate = (payouts: PayoutsAndClaims, locale: Locale) => { if (!payouts.length) { return undefined } - const filtered = this.removeNonZeroAmountAndSort(payouts || []) + const filtered = this.removeNonZeroAmountAndSort(payouts) if (!filtered.length) { return undefined } @@ -287,7 +288,7 @@ export class Subscan { } // Calculate the latest date of a payout list. - static payoutsToDate = (payouts: SubscanPayout[], locale: Locale) => { + static payoutsToDate = (payouts: PayoutsAndClaims, locale: Locale) => { if (!payouts.length) { return undefined } diff --git a/packages/app/src/controllers/Subscan/types.ts b/packages/app/src/controllers/Subscan/types.ts index 9e4d34f427..78d96f56b3 100644 --- a/packages/app/src/controllers/Subscan/types.ts +++ b/packages/app/src/controllers/Subscan/types.ts @@ -5,7 +5,13 @@ export type PayoutType = 'payouts' | 'unclaimedPayouts' | 'poolClaims' export type SubscanData = Partial> -export type SubscanPayoutData = Partial> +export interface SubscanPayoutData { + payouts: SubscanPayout[] + unclaimedPayouts: SubscanPayout[] + poolClaims: SubscanPoolClaim[] +} + +export type PayoutsAndClaims = (SubscanPayout | SubscanPoolClaim)[] export type SubscanRequestBody = | RewardSlashRequestBody diff --git a/packages/app/src/hooks/useSubscanData/index.tsx b/packages/app/src/hooks/useSubscanData/index.tsx index 418c385efa..41792f7e04 100644 --- a/packages/app/src/hooks/useSubscanData/index.tsx +++ b/packages/app/src/hooks/useSubscanData/index.tsx @@ -1,49 +1,61 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { setStateWithRef } from '@w3ux/utils' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { usePlugins } from 'contexts/Plugins' import { Subscan } from 'controllers/Subscan' import type { PayoutType, - SubscanData, SubscanPayout, - SubscanPayoutData, + SubscanPoolClaim, } from 'controllers/Subscan/types' import { isCustomEvent } from 'controllers/utils' import { useEffect, useRef, useState } from 'react' import { useEventListener } from 'usehooks-ts' import { useErasToTimeLeft } from '../useErasToTimeLeft' -export const useSubscanData = (keys: PayoutType[]) => { +export const useSubscanData = () => { const { activeEra } = useApi() const { pluginEnabled } = usePlugins() const { erasToSeconds } = useErasToTimeLeft() const { activeAccount } = useActiveAccounts() - // Store the most up to date subscan data state. - const [data, setData] = useState({}) - const dataRef = useRef(data) + // Store payouts data for the active account. + const [payouts, setPayouts] = useState([]) + + // Store unclaimed payouts data for the active account. + const [unclaimedPayouts, setUnclaimedPayouts] = useState([]) + + // Store pool claims data for the active account. + const [poolClaims, setPoolClaims] = useState([]) // Listen for updated data callback. When there are new data, fetch the updated values directly // from `Subscan` and commit to component state. const subscanPayoutsUpdatedCallback = (e: Event) => { // NOTE: Subscan has to be enabled to continue. - if (isCustomEvent(e) && pluginEnabled('subscan')) { + if (isCustomEvent(e) && pluginEnabled('subscan') && activeAccount) { const { keys: receivedKeys }: { keys: PayoutType[] } = e.detail - // Filter out any keys that are not provided to the hook active account is still present. - if (activeAccount) { - const newData: SubscanData = {} - receivedKeys - .filter((key) => keys.includes(key)) - .forEach((key) => { - newData[key] = Subscan.payoutData[activeAccount]?.[key] || [] - }) + if (receivedKeys.includes('payouts')) { + setPayouts( + (Subscan.payoutData[activeAccount]?.['payouts'] || + []) as SubscanPayout[] + ) + } + + if (receivedKeys.includes('unclaimedPayouts')) { + setUnclaimedPayouts( + (Subscan.payoutData[activeAccount]?.['unclaimedPayouts'] || + []) as SubscanPayout[] + ) + } - setStateWithRef({ ...dataRef.current, ...newData }, setData, dataRef) + if (receivedKeys.includes('poolClaims')) { + setPoolClaims( + (Subscan.payoutData[activeAccount]?.['poolClaims'] || + []) as SubscanPoolClaim[] + ) } } } @@ -56,17 +68,6 @@ export const useSubscanData = (keys: PayoutType[]) => { documentRef ) - // Get data or return an empty array if it is undefined. - const getData = (withKeys: PayoutType[]): SubscanPayoutData => { - const result: SubscanPayoutData = {} - - withKeys.forEach((key: PayoutType) => { - const keyData = (data[key] || []) as SubscanPayout[] - result[key] = keyData - }) - return result - } - // Inject block_timestamp for unclaimed payouts. We take the timestamp of the start of the // following payout era - this is the time payouts become available to claim by validators. const injectBlockTimestamp = (entries: SubscanPayout[]) => { @@ -85,13 +86,25 @@ export const useSubscanData = (keys: PayoutType[]) => { // Populate state on initial render if data is already available. useEffect(() => { if (activeAccount) { - const newData: SubscanData = {} - keys.forEach((key: PayoutType) => { - newData[key] = Subscan.payoutData[activeAccount]?.[key] || [] - }) - setStateWithRef({ ...dataRef.current, ...newData }, setData, dataRef) + setPayouts( + (Subscan.payoutData[activeAccount]?.['payouts'] || + []) as SubscanPayout[] + ) + setUnclaimedPayouts( + (Subscan.payoutData[activeAccount]?.['unclaimedPayouts'] || + []) as SubscanPayout[] + ) + setPoolClaims( + (Subscan.payoutData[activeAccount]?.['poolClaims'] || + []) as SubscanPoolClaim[] + ) } }, [activeAccount]) - return { data, getData, injectBlockTimestamp } + return { + payouts, + unclaimedPayouts, + poolClaims, + injectBlockTimestamp, + } } diff --git a/packages/app/src/pages/Overview/Payouts.tsx b/packages/app/src/pages/Overview/Payouts.tsx index cc1bd3ad6b..e8d9faa702 100644 --- a/packages/app/src/pages/Overview/Payouts.tsx +++ b/packages/app/src/pages/Overview/Payouts.tsx @@ -35,18 +35,13 @@ export const Payouts = () => { const { syncing } = useSyncing() const { plugins } = usePlugins() const { containerRefs } = useUi() - const { getData, injectBlockTimestamp } = useSubscanData([ - 'payouts', - 'unclaimedPayouts', - 'poolClaims', - ]) - const notStaking = !syncing && inSetup() + let { unclaimedPayouts } = useSubscanData() + const { payouts, poolClaims, injectBlockTimestamp } = useSubscanData() - // Get data safely from subscan hook. - const data = getData(['payouts', 'unclaimedPayouts', 'poolClaims']) + const notStaking = !syncing && inSetup() // Inject `block_timestamp` for unclaimed payouts. - data['unclaimedPayouts'] = injectBlockTimestamp(data?.unclaimedPayouts || []) + unclaimedPayouts = injectBlockTimestamp(unclaimedPayouts) // Ref to the graph container. const graphInnerRef = useRef(null) @@ -62,9 +57,9 @@ export const Payouts = () => { new Date(), 14, units, - data.payouts, - data.poolClaims, - data.unclaimedPayouts + payouts, + poolClaims, + unclaimedPayouts ) let formatFrom = new Date() let formatTo = new Date() @@ -130,9 +125,18 @@ export const Payouts = () => { transition: 'opacity 0.5s', }} > - +
- +
diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index c50f9da85a..f5506f21eb 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -9,6 +9,7 @@ import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' import { useUi } from 'contexts/UI' import { Subscan } from 'controllers/Subscan' +import type { PayoutsAndClaims } from 'controllers/Subscan/types' import { useSubscanData } from 'hooks/useSubscanData' import { useSyncing } from 'hooks/useSyncing' import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers' @@ -34,11 +35,8 @@ export const Payouts = ({ page: { key } }: PageProps) => { const { inSetup } = useStaking() const { syncing } = useSyncing() const { containerRefs } = useUi() - const { getData, injectBlockTimestamp } = useSubscanData([ - 'payouts', - 'unclaimedPayouts', - 'poolClaims', - ]) + let { unclaimedPayouts } = useSubscanData() + const { payouts, poolClaims, injectBlockTimestamp } = useSubscanData() const notStaking = !syncing && inSetup() const [payoutsList, setPayoutLists] = useState([]) @@ -49,33 +47,23 @@ export const Payouts = ({ page: { key } }: PageProps) => { }) const { width, height, minHeight } = formatSize(size, 280) - // Get data safely from subscan hook. - const data = getData(['payouts', 'unclaimedPayouts', 'poolClaims']) - // Inject `block_timestamp` for unclaimed payouts. - data['unclaimedPayouts'] = injectBlockTimestamp(data?.unclaimedPayouts || []) + unclaimedPayouts = injectBlockTimestamp(unclaimedPayouts) + const payoutsAndClaims = (payouts as PayoutsAndClaims).concat(poolClaims) const payoutsFromDate = Subscan.payoutsFromDate( - (data?.payouts || []).concat(data?.poolClaims || []), + payoutsAndClaims, locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) - const payoutsToDate = Subscan.payoutsToDate( - (data?.payouts || []).concat(data?.poolClaims || []), + payoutsAndClaims, locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) useEffect(() => { // filter zero rewards and order via block timestamp, most recent first. - setPayoutLists( - Subscan.removeNonZeroAmountAndSort( - (data?.payouts || []).concat(data?.poolClaims || []) - ) - ) - }, [ - JSON.stringify(data?.payouts || {}), - JSON.stringify(data?.poolClaims || {}), - ]) + setPayoutLists(Subscan.removeNonZeroAmountAndSort(payoutsAndClaims)) + }, [JSON.stringify(payouts), JSON.stringify(poolClaims)]) return ( <> @@ -132,12 +120,16 @@ export const Payouts = ({ page: { key } }: PageProps) => { transition: 'opacity 0.5s', }} > - + diff --git a/packages/plugin-staking-api/src/queries/useTokenPrice.tsx b/packages/plugin-staking-api/src/queries/useTokenPrice.tsx index 55d3ace3d6..a43b8d5cc1 100644 --- a/packages/plugin-staking-api/src/queries/useTokenPrice.tsx +++ b/packages/plugin-staking-api/src/queries/useTokenPrice.tsx @@ -5,7 +5,7 @@ import type { ApolloError } from '@apollo/client' import { gql, useQuery } from '@apollo/client' import type { TokenPriceResult, UseTokenPriceResult } from '../types' -const TOKEN_PRICE_QUERY = gql` +const QUERY = gql` query TokenPrice($ticker: String!) { tokenPrice(ticker: $ticker) { price @@ -19,7 +19,7 @@ export const useTokenPrice = ({ }: { ticker: string }): UseTokenPriceResult => { - const { loading, error, data, refetch } = useQuery(TOKEN_PRICE_QUERY, { + const { loading, error, data, refetch } = useQuery(QUERY, { variables: { ticker }, }) return { loading, error, data, refetch } diff --git a/packages/plugin-staking-api/src/types.ts b/packages/plugin-staking-api/src/types.ts index b2ad34a2e6..acb977f8f0 100644 --- a/packages/plugin-staking-api/src/types.ts +++ b/packages/plugin-staking-api/src/types.ts @@ -16,11 +16,18 @@ export type TokenPriceResult = { tokenPrice: TokenPrice } | null -export interface UseTokenPriceResult { +interface Query { loading: boolean error: ApolloError | undefined - data: TokenPriceResult refetch: ( variables?: Partial | undefined ) => Promise> } + +export type UseTokenPriceResult = Query & { + data: TokenPriceResult +} + +export type AllRewardsResult = Query & { + data: unknown +} From 89f3fc1d72cdfe52640304cd1aae7d962848827d Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 11:32:02 +0700 Subject: [PATCH 02/33] refactor rewards to new structure --- packages/app/src/controllers/Subscan/index.ts | 65 +++++--- packages/app/src/controllers/Subscan/types.ts | 18 ++- .../app/src/hooks/useSubscanData/index.tsx | 27 ++-- packages/app/src/library/Graphs/PayoutBar.tsx | 21 +-- .../app/src/library/Graphs/PayoutLine.tsx | 7 +- packages/app/src/library/Graphs/Utils.ts | 140 ++++++++---------- packages/app/src/library/Graphs/types.ts | 16 +- packages/app/src/pages/Overview/Payouts.tsx | 38 +++-- .../src/pages/Payouts/PayoutList/index.tsx | 18 +-- packages/app/src/pages/Payouts/index.tsx | 2 +- packages/app/tests/graphs.test.ts | 58 ++++---- packages/plugin-staking-api/src/index.tsx | 1 + .../src/queries/useRewards.tsx | 33 +++++ packages/plugin-staking-api/src/types.ts | 13 +- 14 files changed, 262 insertions(+), 195 deletions(-) create mode 100644 packages/plugin-staking-api/src/queries/useRewards.tsx diff --git a/packages/app/src/controllers/Subscan/index.ts b/packages/app/src/controllers/Subscan/index.ts index 0fa708ab41..d227228e39 100644 --- a/packages/app/src/controllers/Subscan/index.ts +++ b/packages/app/src/controllers/Subscan/index.ts @@ -4,6 +4,7 @@ import type { Locale } from 'date-fns' import { format, fromUnixTime, getUnixTime, subDays } from 'date-fns' import { poolMembersPerPage } from 'library/List/defaults' +import type { NominatorReward } from 'plugin-staking-api/src/types' import type { PoolMember } from 'types' import type { PayoutsAndClaims, @@ -11,6 +12,7 @@ import type { SubscanEraPoints, SubscanPayout, SubscanPoolClaim, + SubscanPoolClaimRaw, SubscanPoolMember, SubscanRequestBody, } from './types' @@ -85,8 +87,8 @@ export class Subscan { static fetchNominatorPayouts = async ( address: string ): Promise<{ - payouts: SubscanPayout[] - unclaimedPayouts: SubscanPayout[] + payouts: NominatorReward[] + unclaimedPayouts: NominatorReward[] }> => { try { const result = await this.makeRequest(this.ENDPOINTS.rewardSlash, { @@ -96,7 +98,7 @@ export class Subscan { page: 0, }) - const payouts = + let payouts = result?.list?.filter( ({ block_timestamp }: SubscanPayout) => block_timestamp !== 0 ) || [] @@ -104,21 +106,38 @@ export class Subscan { let unclaimedPayouts = result?.list?.filter((l: SubscanPayout) => l.block_timestamp === 0) || [] - // Further filter unclaimed payouts to ensure that payout records of `stash` and // `validator_stash` are not repeated for an era. NOTE: This was introduced to remove errornous // data where there were duplicated payout records (with different amounts) for a stash - // validator - era record. from Subscan. unclaimedPayouts = unclaimedPayouts.filter( (u: SubscanPayout) => - !payouts.find( - (p: SubscanPayout) => - p.stash === u.stash && - p.validator_stash === u.validator_stash && - p.era === u.era - ) + !payouts + .find( + (p: SubscanPayout) => + p.stash === u.stash && + p.validator_stash === u.validator_stash && + p.era === u.era + ) + .map((p: SubscanPayout) => ({ + era: p.era, + reward: p.amount, + claimed: false, + timestamp: p.block_timestamp, + validator: p.validator_stash, + type: 'nominator', + })) ) + payouts = payouts.map((p: SubscanPayout) => ({ + era: p.era, + reward: p.amount, + claimed: true, + timestamp: p.block_timestamp, + validator: p.validator_stash, + type: 'nominator', + })) + return { payouts, unclaimedPayouts } } catch (e) { // Silently fail request and return empty records. @@ -140,9 +159,15 @@ export class Subscan { return [] } // Remove claims with a `block_timestamp`. - const poolClaims = result.list.filter( - (l: SubscanPoolClaim) => l.block_timestamp !== 0 - ) + const poolClaims = result.list + .filter((l: SubscanPoolClaimRaw) => l.block_timestamp !== 0) + .map((l: SubscanPoolClaimRaw) => ({ + ...l, + reward: l.amount, + timestamp: l.block_timestamp, + type: 'pool', + })) + return poolClaims } catch (e) { // Silently fail request and return empty record. @@ -239,7 +264,7 @@ export class Subscan { // Remove unclaimed payouts and dispatch update event. static removeUnclaimedPayouts = (address: string, eraPayouts: string[]) => { const newUnclaimedPayouts = (this.payoutData[address]?.unclaimedPayouts || - []) as SubscanPayout[] + []) as NominatorReward[] eraPayouts.forEach(([era]) => { newUnclaimedPayouts.filter((u) => String(u.era) !== era) @@ -258,15 +283,13 @@ export class Subscan { // Take non-zero rewards in most-recent order. static removeNonZeroAmountAndSort = (payouts: PayoutsAndClaims) => { const list = payouts - .filter((p) => Number(p.amount) > 0) - .sort((a, b) => b.block_timestamp - a.block_timestamp) + .filter((p) => Number(p.reward) > 0) + .sort((a, b) => b.timestamp - a.timestamp) // Calculates from the current date. const fromTimestamp = getUnixTime(subDays(new Date(), this.MAX_PAYOUT_DAYS)) // Ensure payouts not older than `MAX_PAYOUT_DAYS` are returned. - return list.filter( - ({ block_timestamp }) => block_timestamp >= fromTimestamp - ) + return list.filter(({ timestamp }) => timestamp >= fromTimestamp) } // Calculate the earliest date of a payout list. @@ -279,7 +302,7 @@ export class Subscan { return undefined } return format( - fromUnixTime(filtered[filtered.length - 1].block_timestamp), + fromUnixTime(filtered[filtered.length - 1].timestamp), 'do MMM', { locale, @@ -297,7 +320,7 @@ export class Subscan { return undefined } - return format(fromUnixTime(filtered[0].block_timestamp), 'do MMM', { + return format(fromUnixTime(filtered[0].timestamp), 'do MMM', { locale, }) } diff --git a/packages/app/src/controllers/Subscan/types.ts b/packages/app/src/controllers/Subscan/types.ts index 78d96f56b3..44c7fd9093 100644 --- a/packages/app/src/controllers/Subscan/types.ts +++ b/packages/app/src/controllers/Subscan/types.ts @@ -1,6 +1,8 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only +import type { NominatorReward } from 'plugin-staking-api/src/types' + export type PayoutType = 'payouts' | 'unclaimedPayouts' | 'poolClaims' export type SubscanData = Partial> @@ -11,7 +13,7 @@ export interface SubscanPayoutData { poolClaims: SubscanPoolClaim[] } -export type PayoutsAndClaims = (SubscanPayout | SubscanPoolClaim)[] +export type PayoutsAndClaims = (NominatorReward | SubscanPoolClaim)[] export type SubscanRequestBody = | RewardSlashRequestBody @@ -43,11 +45,11 @@ export interface SubscanRequestPagination { } export type SubscanResult = - | SubscanPayout[] + | NominatorReward[] | SubscanPoolClaim[] | SubscanPoolMember[] -export interface SubscanPoolClaim { +export interface SubscanPoolClaimBase { account_display: { address: string display: string @@ -63,6 +65,16 @@ export interface SubscanPoolClaim { pool_id: number } +export type SubscanPoolClaimRaw = SubscanPoolClaimBase & { + amount: string + block_timestamp: number +} + +export type SubscanPoolClaim = SubscanPoolClaimBase & { + reward: string + timestamp: number +} + export interface SubscanPayout { era: number stash: string diff --git a/packages/app/src/hooks/useSubscanData/index.tsx b/packages/app/src/hooks/useSubscanData/index.tsx index 41792f7e04..09f570f952 100644 --- a/packages/app/src/hooks/useSubscanData/index.tsx +++ b/packages/app/src/hooks/useSubscanData/index.tsx @@ -5,12 +5,9 @@ import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { usePlugins } from 'contexts/Plugins' import { Subscan } from 'controllers/Subscan' -import type { - PayoutType, - SubscanPayout, - SubscanPoolClaim, -} from 'controllers/Subscan/types' +import type { PayoutType, SubscanPoolClaim } from 'controllers/Subscan/types' import { isCustomEvent } from 'controllers/utils' +import type { NominatorReward } from 'plugin-staking-api/src/types' import { useEffect, useRef, useState } from 'react' import { useEventListener } from 'usehooks-ts' import { useErasToTimeLeft } from '../useErasToTimeLeft' @@ -22,10 +19,12 @@ export const useSubscanData = () => { const { activeAccount } = useActiveAccounts() // Store payouts data for the active account. - const [payouts, setPayouts] = useState([]) + const [payouts, setPayouts] = useState([]) // Store unclaimed payouts data for the active account. - const [unclaimedPayouts, setUnclaimedPayouts] = useState([]) + const [unclaimedPayouts, setUnclaimedPayouts] = useState( + [] + ) // Store pool claims data for the active account. const [poolClaims, setPoolClaims] = useState([]) @@ -40,14 +39,14 @@ export const useSubscanData = () => { if (receivedKeys.includes('payouts')) { setPayouts( (Subscan.payoutData[activeAccount]?.['payouts'] || - []) as SubscanPayout[] + []) as NominatorReward[] ) } if (receivedKeys.includes('unclaimedPayouts')) { setUnclaimedPayouts( (Subscan.payoutData[activeAccount]?.['unclaimedPayouts'] || - []) as SubscanPayout[] + []) as NominatorReward[] ) } @@ -68,14 +67,14 @@ export const useSubscanData = () => { documentRef ) - // Inject block_timestamp for unclaimed payouts. We take the timestamp of the start of the + // Inject timestamp for unclaimed payouts. We take the timestamp of the start of the // following payout era - this is the time payouts become available to claim by validators. - const injectBlockTimestamp = (entries: SubscanPayout[]) => { + const injectBlockTimestamp = (entries: NominatorReward[]) => { if (!entries) { return entries } entries.forEach((p) => { - p.block_timestamp = activeEra.start + p.timestamp = activeEra.start .multipliedBy(0.001) .minus(erasToSeconds(activeEra.index.minus(p.era).minus(1))) .toNumber() @@ -88,11 +87,11 @@ export const useSubscanData = () => { if (activeAccount) { setPayouts( (Subscan.payoutData[activeAccount]?.['payouts'] || - []) as SubscanPayout[] + []) as NominatorReward[] ) setUnclaimedPayouts( (Subscan.payoutData[activeAccount]?.['unclaimedPayouts'] || - []) as SubscanPayout[] + []) as NominatorReward[] ) setPoolClaims( (Subscan.payoutData[activeAccount]?.['poolClaims'] || diff --git a/packages/app/src/library/Graphs/PayoutBar.tsx b/packages/app/src/library/Graphs/PayoutBar.tsx index 02490c3e62..8b459c34ff 100644 --- a/packages/app/src/library/Graphs/PayoutBar.tsx +++ b/packages/app/src/library/Graphs/PayoutBar.tsx @@ -56,22 +56,15 @@ export const PayoutBar = ({ const { unit, units, colors } = useNetwork().networkData const notStaking = !syncing && inSetup() && !membership - // remove slashes from payouts (graph does not support negative values). - const payoutsNoSlash = payouts?.filter((p) => p.event_id !== 'Slashed') || [] - - // remove slashes from unclaimed payouts. - const unclaimedPayoutsNoSlash = - unclaimedPayouts?.filter((p) => p.event_id !== 'Slashed') || [] - // get formatted rewards data for graph. const { allPayouts, allPoolClaims, allUnclaimedPayouts } = formatRewardsForGraphs( new Date(), days, units, - payoutsNoSlash, + payouts, poolClaims, - unclaimedPayoutsNoSlash + unclaimedPayouts ) const { p: graphPayouts } = allPayouts @@ -93,7 +86,7 @@ export const PayoutBar = ({ const data = { labels: graphPayouts.map((item: AnyApi) => { - const dateObj = format(fromUnixTime(item.block_timestamp), 'do MMM', { + const dateObj = format(fromUnixTime(item.timestamp), 'do MMM', { locale: locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat, }) return `${dateObj}` @@ -103,7 +96,7 @@ export const PayoutBar = ({ { order: 1, label: t('payout'), - data: graphPayouts.map((item: AnyApi) => item.amount), + data: graphPayouts.map((item: AnyApi) => item.reward.toFixed(5)), borderColor: colorPayouts, backgroundColor: colorPayouts, pointRadius: 0, @@ -112,7 +105,7 @@ export const PayoutBar = ({ { order: 2, label: t('poolClaim'), - data: graphPoolClaims.map((item: AnyApi) => item.amount), + data: graphPoolClaims.map((item: AnyApi) => item.reward.toFixed(5)), borderColor: colorPoolClaims, backgroundColor: colorPoolClaims, pointRadius: 0, @@ -120,7 +113,9 @@ export const PayoutBar = ({ }, { order: 3, - data: graphUnclaimedPayouts.map((item: AnyApi) => item.amount), + data: graphUnclaimedPayouts.map((item: AnyApi) => + item.reward.toFixed(5) + ), label: t('unclaimedPayouts'), borderColor: colorPayouts, backgroundColor: colors.pending[mode], diff --git a/packages/app/src/library/Graphs/PayoutLine.tsx b/packages/app/src/library/Graphs/PayoutLine.tsx index b072df8456..6376a53b4b 100644 --- a/packages/app/src/library/Graphs/PayoutLine.tsx +++ b/packages/app/src/library/Graphs/PayoutLine.tsx @@ -59,9 +59,6 @@ export const PayoutLine = ({ const notStaking = !syncing && inSetup() && !poolMembership const inPoolOnly = !syncing && inSetup() && !!poolMembership - // remove slashes from payouts (graph does not support negative values). - const payoutsNoSlash = payouts?.filter((p) => p.event_id !== 'Slashed') || [] - // define the most recent date that we will show on the graph. const fromDate = new Date() @@ -69,7 +66,7 @@ export const PayoutLine = ({ fromDate, days, units, - payoutsNoSlash, + payouts, poolClaims, [] // Note: we are not using `unclaimedPayouts` here. ) @@ -155,7 +152,7 @@ export const PayoutLine = ({ datasets: [ { label: t('payout'), - data: combinedPayouts.map((item: AnyApi) => item?.amount ?? 0), + data: combinedPayouts.map((item: AnyApi) => item.reward.toFixed(5)), borderColor: color, pointStyle: undefined, pointRadius: 0, diff --git a/packages/app/src/library/Graphs/Utils.ts b/packages/app/src/library/Graphs/Utils.ts index 6ab83ab5f2..ee1d59ff27 100644 --- a/packages/app/src/library/Graphs/Utils.ts +++ b/packages/app/src/library/Graphs/Utils.ts @@ -14,10 +14,11 @@ import { startOfDay, subDays, } from 'date-fns' +import type { NominatorReward } from 'plugin-staking-api/src/types' import { planckToUnitBn } from 'utils' import type { PayoutDayCursor } from './types' -// Given payouts, calculate daily income and fill missing days with zero amounts. +// Given payouts, calculate daily income and fill missing days with zero rewards. export const calculateDailyPayouts = ( payouts: AnyApi, fromDate: Date, @@ -30,8 +31,7 @@ export const calculateDailyPayouts = ( // remove days that are beyond end day limit payouts = payouts.filter( - (p: AnyApi) => - daysPassed(fromUnixTime(p.block_timestamp), fromDate) <= maxDays + (p: AnyApi) => daysPassed(fromUnixTime(p.timestamp), fromDate) <= maxDays ) // return now if no payouts. @@ -50,14 +50,13 @@ export const calculateDailyPayouts = ( let curDay: Date = fromDate // current payout cursor. let curPayout: PayoutDayCursor = { - amount: new BigNumber(0), - event_id: '', + reward: new BigNumber(0), } for (const payout of payouts) { p++ // extract day from current payout. - const thisDay = startOfDay(fromUnixTime(payout.block_timestamp)) + const thisDay = startOfDay(fromUnixTime(payout.timestamp)) // initialise current day if first payout. if (p === 1) { @@ -67,9 +66,8 @@ export const calculateDailyPayouts = ( // handle surpassed maximum days. if (daysPassed(thisDay, fromDate) >= maxDays) { dailyPayouts.push({ - amount: planckToUnitBn(curPayout.amount, units), - event_id: getEventId(curPayout), - block_timestamp: getUnixTime(curDay), + reward: planckToUnitBn(curPayout.reward, units), + timestamp: getUnixTime(curDay), }) break } @@ -81,43 +79,38 @@ export const calculateDailyPayouts = ( if (daysDiff > 0) { // add current payout cursor to dailyPayouts. dailyPayouts.push({ - amount: planckToUnitBn(curPayout.amount, units), - event_id: getEventId(curPayout), - block_timestamp: getUnixTime(curDay), + reward: planckToUnitBn(curPayout.reward, units), + timestamp: getUnixTime(curDay), }) // update day cursor to the new day. curDay = thisDay // reset current payout cursor for the new day. curPayout = { - amount: new BigNumber(payout.amount), - event_id: new BigNumber(payout.amount).isLessThan(0) - ? 'Slash' - : 'Reward', + reward: new BigNumber(payout.reward), } } else { - // in same day. Aadd payout amount to current payout cursor. - curPayout.amount = curPayout.amount.plus(payout.amount) + // in same day. Aadd payout reward to current payout cursor. + curPayout.reward = curPayout.reward.plus(payout.reward) } // if only 1 payout exists, or at the last unresolved payout, exit here. if ( payouts.length === 1 || - (p === payouts.length && !curPayout.amount.isZero()) + (p === payouts.length && !curPayout.reward.isZero()) ) { dailyPayouts.push({ - amount: planckToUnitBn(curPayout.amount, units), - event_id: getEventId(curPayout), - block_timestamp: getUnixTime(curDay), + reward: planckToUnitBn(curPayout.reward, units), + timestamp: getUnixTime(curDay), }) break } } - // return payout amounts as plain numbers. + // return payout rewards as plain numbers. return dailyPayouts.map((q: AnyApi) => ({ ...q, - amount: Number(q.amount.toString()), + reward: Number(q.reward.toString()), })) } @@ -139,32 +132,32 @@ export const calculatePayoutAverages = ( // average period end. const end = Math.max(0, i - avgDays) - // the total amount earned in period. + // the total reward earned in period. let total = 0 // period length to be determined. let num = 0 for (let j = i; j >= end; j--) { if (payouts[j]) { - total += payouts[j].amount + total += payouts[j].reward } // increase by one to treat non-existent as zero value num += 1 } if (total === 0) { - total = payouts[i].amount + total = payouts[i].reward } payoutsAverages.push({ - amount: total / num, - block_timestamp: payouts[i].block_timestamp, + reward: total / num, + timestamp: payouts[i].timestamp, }) } // return an array with the expected number of items payoutsAverages = payoutsAverages.filter( - (p: AnyApi) => daysPassed(fromUnixTime(p.block_timestamp), fromDate) <= days + (p: AnyApi) => daysPassed(fromUnixTime(p.timestamp), fromDate) <= days ) return payoutsAverages @@ -177,9 +170,9 @@ export const formatRewardsForGraphs = ( fromDate: Date, days: number, units: number, - payouts: AnyApi, + payouts: NominatorReward[], poolClaims: AnyApi, - unclaimedPayouts: AnyApi + unclaimedPayouts: NominatorReward[] ) => { // process nominator payouts. const allPayouts = processPayouts(payouts, fromDate, days, units, 'nominate') @@ -273,35 +266,35 @@ const getPreMaxDaysPayouts = ( // remove payouts that are not within `avgDays` `days` pre-graph window. payouts.filter( (p: AnyApi) => - daysPassed(fromUnixTime(p.block_timestamp), fromDate) > days && - daysPassed(fromUnixTime(p.block_timestamp), fromDate) <= days + avgDays + daysPassed(fromUnixTime(p.timestamp), fromDate) > days && + daysPassed(fromUnixTime(p.timestamp), fromDate) <= days + avgDays ) // Combine payouts and pool claims. // -// combines payouts and pool claims into daily records. Removes the `event_id` field from records. +// combines payouts and pool claims into daily records. export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { - // we first check if actual payouts exist, e.g. there are non-zero payout - // amounts present in either payouts or pool claims. - const poolClaimExists = poolClaims.find((p: AnyApi) => p.amount > 0) || null - const payoutExists = payouts.find((p: AnyApi) => p.amount > 0) || null + // we first check if actual payouts exist, e.g. there are non-zero payout rewards present in + // either payouts or pool claims. + const poolClaimExists = poolClaims.find((p: AnyApi) => p.reward > 0) || null + const payoutExists = payouts.find((p: AnyApi) => p.reward > 0) || null - // if no pool claims exist but payouts do, return payouts w.o. event_id - // also do this if there are no payouts period. + // if no pool claims exist but payouts do, return payouts. Also do this if there are no payouts + // period. if ( (!poolClaimExists && payoutExists) || (!payoutExists && !poolClaimExists) ) { return payouts.map((p: AnyApi) => ({ - amount: p.amount, - block_timestamp: p.block_timestamp, + reward: p.reward, + timestamp: p.timestamp, })) } - // if no payouts exist but pool claims do, return pool claims w.o. event_id + // if no payouts exist but pool claims do, return pool claims if (!payoutExists && poolClaimExists) { return poolClaims.map((p: AnyApi) => ({ - amount: p.amount, - block_timestamp: p.block_timestamp, + reward: p.reward, + timestamp: p.timestamp, })) } @@ -311,19 +304,19 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { let payoutDays: AnyJson[] = [] // prefill `dates` with all pool claim and payout days poolClaims.forEach((p: AnyApi) => { - const dayStart = getUnixTime(startOfDay(fromUnixTime(p.block_timestamp))) + const dayStart = getUnixTime(startOfDay(fromUnixTime(p.timestamp))) if (!payoutDays.includes(dayStart)) { payoutDays.push(dayStart) } }) payouts.forEach((p: AnyApi) => { - const dayStart = getUnixTime(startOfDay(fromUnixTime(p.block_timestamp))) + const dayStart = getUnixTime(startOfDay(fromUnixTime(p.timestamp))) if (!payoutDays.includes(dayStart)) { payoutDays.push(dayStart) } }) - // sort payoutDays by `block_timestamp`; + // sort payoutDays by `timestamp`; payoutDays = payoutDays.sort((a: AnyApi, b: AnyApi) => a - b) // Iterate payout days. @@ -333,25 +326,25 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { // loop pool claims and consume / combine payouts payoutDays.forEach((d: AnyApi) => { - let amount = 0 + let reward = 0 // check payouts exist on this day const payoutsThisDay = payouts.filter((p: AnyApi) => - isSameDay(fromUnixTime(p.block_timestamp), fromUnixTime(d)) + isSameDay(fromUnixTime(p.timestamp), fromUnixTime(d)) ) // check pool claims exist on this day const poolClaimsThisDay = poolClaims.filter((p: AnyApi) => - isSameDay(fromUnixTime(p.block_timestamp), fromUnixTime(d)) + isSameDay(fromUnixTime(p.timestamp), fromUnixTime(d)) ) - // add amounts + // add rewards if (payoutsThisDay.concat(poolClaimsThisDay).length) { for (const payout of payoutsThisDay) { - amount += payout.amount + reward += payout.reward } } rewards.push({ - amount, - block_timestamp: d, + reward, + timestamp: d, }) }) return rewards @@ -363,10 +356,10 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { export const getLatestReward = (payouts: AnyApi, poolClaims: AnyApi) => { // get most recent payout const payoutExists = - payouts.find((p: AnyApi) => new BigNumber(p.amount).isGreaterThan(0)) ?? + payouts.find((p: AnyApi) => new BigNumber(p.reward).isGreaterThan(0)) ?? null const poolClaimExists = - poolClaims.find((p: AnyApi) => new BigNumber(p.amount).isGreaterThan(0)) ?? + poolClaims.find((p: AnyApi) => new BigNumber(p.reward).isGreaterThan(0)) ?? null // calculate which payout was most recent @@ -381,7 +374,7 @@ export const getLatestReward = (payouts: AnyApi, poolClaims: AnyApi) => { } else { // both `payoutExists` and `poolClaimExists` are present lastReward = - payoutExists.block_timestamp > poolClaimExists.block_timestamp + payoutExists.timestamp > poolClaimExists.timestamp ? payoutExists : poolClaimExists } @@ -400,16 +393,15 @@ export const prefillMissingDays = ( const payoutStartDay = subDays(startOfDay(fromDate), maxDays) const payoutEndDay = !payouts.length ? startOfDay(fromDate) - : startOfDay(fromUnixTime(payouts[payouts.length - 1].block_timestamp)) + : startOfDay(fromUnixTime(payouts[payouts.length - 1].timestamp)) const daysToPreFill = daysPassed(payoutStartDay, payoutEndDay) if (daysToPreFill > 0) { for (let i = 1; i < daysToPreFill; i++) { newPayouts.push({ - amount: 0, - event_id: 'Reward', - block_timestamp: getUnixTime(subDays(payoutEndDay, i)), + reward: 0, + timestamp: getUnixTime(subDays(payoutEndDay, i)), }) } } @@ -425,7 +417,7 @@ export const postFillMissingDays = ( maxDays: number ) => { const newPayouts = [] - const payoutsEndDay = startOfDay(fromUnixTime(payouts[0].block_timestamp)) + const payoutsEndDay = startOfDay(fromUnixTime(payouts[0].timestamp)) const daysSinceLast = Math.min( daysPassed(payoutsEndDay, startOfDay(fromDate)), maxDays @@ -433,15 +425,14 @@ export const postFillMissingDays = ( for (let i = daysSinceLast; i > 0; i--) { newPayouts.push({ - amount: 0, - event_id: 'Reward', - block_timestamp: getUnixTime(addDays(payoutsEndDay, i)), + reward: 0, + timestamp: getUnixTime(addDays(payoutsEndDay, i)), }) } return newPayouts } -// Fill gap days within payouts with zero amounts. +// Fill gap days within payouts with zero rewards. export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { const finalPayouts: AnyApi = [] @@ -449,7 +440,7 @@ export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { let curDay = fromDate for (const p of payouts) { - const thisDay = fromUnixTime(p.block_timestamp) + const thisDay = fromUnixTime(p.timestamp) const gapDays = Math.max(0, daysPassed(thisDay, curDay) - 1) if (gapDays > 0) { @@ -457,9 +448,8 @@ export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { if (gapDays > 0) { for (let j = 1; j <= gapDays; j++) { finalPayouts.push({ - amount: 0, - event_id: 'Reward', - block_timestamp: getUnixTime(subDays(curDay, j)), + reward: 0, + timestamp: getUnixTime(subDays(curDay, j)), }) } } @@ -478,17 +468,13 @@ export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { export const normalisePayouts = (payouts: AnyApi) => payouts.map((p: AnyApi) => ({ ...p, - block_timestamp: getUnixTime(startOfDay(fromUnixTime(p.block_timestamp))), + timestamp: getUnixTime(startOfDay(fromUnixTime(p.timestamp))), })) // Utility: days passed since 2 dates. export const daysPassed = (from: Date, to: Date) => differenceInDays(startOfDay(to), startOfDay(from)) -// Utility: extract whether an event id should be a slash or reward, based on the net day amount. -const getEventId = (c: PayoutDayCursor) => - c.amount.isLessThan(0) ? 'Slash' : 'Reward' - // Utility: Formats a width and height pair. export const formatSize = ( { diff --git a/packages/app/src/library/Graphs/types.ts b/packages/app/src/library/Graphs/types.ts index 401b77c110..8b9603b245 100644 --- a/packages/app/src/library/Graphs/types.ts +++ b/packages/app/src/library/Graphs/types.ts @@ -3,7 +3,8 @@ import type BigNumber from 'bignumber.js' import type { AnyApi } from 'common-types' -import type { SubscanPayoutData } from 'controllers/Subscan/types' +import type { SubscanPoolClaim } from 'controllers/Subscan/types' +import type { NominatorReward } from 'plugin-staking-api/src/types' export interface BondedProps { active: BigNumber @@ -21,7 +22,7 @@ export interface EraPointsProps { export interface PayoutBarProps { days: number height: string - data: SubscanPayoutData + data: GraphPayoutData } export interface PayoutLineProps { @@ -29,7 +30,13 @@ export interface PayoutLineProps { average: number height: string background?: string - data: SubscanPayoutData + data: GraphPayoutData +} + +export interface GraphPayoutData { + payouts: NominatorReward[] + unclaimedPayouts: NominatorReward[] + poolClaims: SubscanPoolClaim[] } export interface CardHeaderWrapperProps { @@ -42,8 +49,7 @@ export interface CardWrapperProps { } export interface PayoutDayCursor { - amount: BigNumber - event_id: string + reward: BigNumber } export interface GeoDonutProps { diff --git a/packages/app/src/pages/Overview/Payouts.tsx b/packages/app/src/pages/Overview/Payouts.tsx index e8d9faa702..d4b76b8f4e 100644 --- a/packages/app/src/pages/Overview/Payouts.tsx +++ b/packages/app/src/pages/Overview/Payouts.tsx @@ -3,8 +3,11 @@ import { useSize } from '@w3ux/hooks' import { Odometer } from '@w3ux/react-odometer' +import type { AnyJson } from '@w3ux/types' import { minDecimalPlaces } from '@w3ux/utils' import BigNumber from 'bignumber.js' +import { useActiveAccounts } from 'contexts/ActiveAccounts' +import { useApi } from 'contexts/Api' import { useNetwork } from 'contexts/Network' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' @@ -19,11 +22,12 @@ import { formatRewardsForGraphs, formatSize } from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' import { StatusLabel } from 'library/StatusLabel' import { DefaultLocale, locales } from 'locales' +import { ApolloProvider, client, useRewards } from 'plugin-staking-api' import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { planckToUnitBn } from 'utils' -export const Payouts = () => { +export const PayoutsInner = () => { const { i18n, t } = useTranslation('pages') const { networkData: { @@ -31,17 +35,29 @@ export const Payouts = () => { brand: { token: Token }, }, } = useNetwork() + const { activeEra } = useApi() const { inSetup } = useStaking() const { syncing } = useSyncing() const { plugins } = usePlugins() + const { network } = useNetwork() const { containerRefs } = useUi() - let { unclaimedPayouts } = useSubscanData() - const { payouts, poolClaims, injectBlockTimestamp } = useSubscanData() + const { poolClaims } = useSubscanData() + const { activeAccount } = useActiveAccounts() - const notStaking = !syncing && inSetup() + const { data } = useRewards({ + chain: network, + who: activeAccount || '', + fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), + }) + + const allRewards = data?.allRewards ?? [] + const payouts = + allRewards.filter((reward: AnyJson) => reward.claimed === true) ?? [] - // Inject `block_timestamp` for unclaimed payouts. - unclaimedPayouts = injectBlockTimestamp(unclaimedPayouts) + const unclaimedPayouts = + allRewards.filter((reward: AnyJson) => reward.claimed === false) ?? [] + + const notStaking = !syncing && inSetup() // Ref to the graph container. const graphInnerRef = useRef(null) @@ -65,9 +81,7 @@ export const Payouts = () => { let formatTo = new Date() let formatOpts = {} if (lastReward !== null) { - formatFrom = fromUnixTime( - lastReward?.block_timestamp ?? getUnixTime(new Date()) - ) + formatFrom = fromUnixTime(lastReward?.timestamp ?? getUnixTime(new Date())) formatTo = new Date() formatOpts = { addSuffix: true, @@ -143,3 +157,9 @@ export const Payouts = () => { ) } + +export const Payouts = () => ( + + + +) diff --git a/packages/app/src/pages/Payouts/PayoutList/index.tsx b/packages/app/src/pages/Payouts/PayoutList/index.tsx index da97c0b7d2..8508970317 100644 --- a/packages/app/src/pages/Payouts/PayoutList/index.tsx +++ b/packages/app/src/pages/Payouts/PayoutList/index.tsx @@ -109,18 +109,8 @@ export const PayoutListInner = ({ {listPayouts.map((p: AnyApi, index: number) => { const label = - p.event_id === 'PaidOut' - ? t('payouts.poolClaim') - : p.event_id === 'Rewarded' - ? t('payouts.payout') - : p.event_id - - const labelClass = - p.event_id === 'PaidOut' - ? 'claim' - : p.event_id === 'Rewarded' - ? 'reward' - : undefined + p.type === 'pool' ? t('payouts.poolClaim') : t('payouts.payout') + const labelClass = p.type === 'pool' ? 'claim' : 'reward' // get validator if it exists const validator = validators.find( @@ -158,7 +148,7 @@ export const PayoutListInner = ({

<> - {p.event_id === 'Slashed' ? '-' : '+'} + + {planckToUnitBn( new BigNumber(p.amount), units @@ -196,7 +186,7 @@ export const PayoutListInner = ({
{formatDistance( - fromUnixTime(p.block_timestamp), + fromUnixTime(p.timestamp), new Date(), { addSuffix: true, diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index f5506f21eb..0d2df3f619 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -47,7 +47,7 @@ export const Payouts = ({ page: { key } }: PageProps) => { }) const { width, height, minHeight } = formatSize(size, 280) - // Inject `block_timestamp` for unclaimed payouts. + // Inject `timestamp` for unclaimed payouts. unclaimedPayouts = injectBlockTimestamp(unclaimedPayouts) const payoutsAndClaims = (payouts as PayoutsAndClaims).concat(poolClaims) diff --git a/packages/app/tests/graphs.test.ts b/packages/app/tests/graphs.test.ts index cf062de510..074ec3ff24 100644 --- a/packages/app/tests/graphs.test.ts +++ b/packages/app/tests/graphs.test.ts @@ -13,36 +13,30 @@ import { expect, test } from 'vitest' // payouts that were made 2, 3 and 4 days ago. const mockPayouts = [ { - amount: '10000000000', - block_timestamp: getUnixTime(subDays(new Date(), 2)), + reward: '10000000000', + timestamp: getUnixTime(subDays(new Date(), 2)), }, { - amount: '15000000000', - block_timestamp: getUnixTime(subDays(new Date(), 3)), + reward: '15000000000', + timestamp: getUnixTime(subDays(new Date(), 3)), }, { - amount: '5000000000', - block_timestamp: getUnixTime(subDays(new Date(), 4)), + reward: '5000000000', + timestamp: getUnixTime(subDays(new Date(), 4)), }, ] -// Get the correct amount of days passed between 2 payout timestamps. +// Get the correct reward of days passed between 2 payout timestamps. // // `daysPassed` is a utility function that is used throughout the graph data accumulation process. test('days passed works', () => { const payouts = normalisePayouts(mockPayouts) // days passed works on `mockPayouts`. - expect( - daysPassed(fromUnixTime(payouts[0].block_timestamp), startOfToday()) - ).toBe(2) - expect( - daysPassed(fromUnixTime(payouts[1].block_timestamp), startOfToday()) - ).toBe(3) - expect( - daysPassed(fromUnixTime(payouts[2].block_timestamp), startOfToday()) - ).toBe(4) - - // max amount of missing days to process should be correct. + expect(daysPassed(fromUnixTime(payouts[0].timestamp), startOfToday())).toBe(2) + expect(daysPassed(fromUnixTime(payouts[1].timestamp), startOfToday())).toBe(3) + expect(daysPassed(fromUnixTime(payouts[2].timestamp), startOfToday())).toBe(4) + + // max reward of missing days to process should be correct. for (let i = 1; i < 368; i++) { expect(daysPassed(subDays(new Date(), i), new Date())).toBe(i) } @@ -61,7 +55,7 @@ test('post fill missing days works', () => { // post fill the missing days for mock payouts. const missingDays = postFillMissingDays(payouts, fromDate, maxDays) - // amount of missing days returned should be correct. + // reward of missing days returned should be correct. expect(missingDays.length).toBe(2) // concatenated payouts are correct @@ -72,12 +66,12 @@ test('post fill missing days works', () => { if (i > 0) { expect( daysPassed( - fromUnixTime(concatPayouts[i].block_timestamp), - fromUnixTime(concatPayouts[i - 1].block_timestamp) + fromUnixTime(concatPayouts[i].timestamp), + fromUnixTime(concatPayouts[i - 1].timestamp) ) ).toBe(1) - expect(concatPayouts[i].block_timestamp).toBeLessThan( - concatPayouts[i - 1].block_timestamp + expect(concatPayouts[i].timestamp).toBeLessThan( + concatPayouts[i - 1].timestamp ) } } @@ -96,7 +90,7 @@ test('pre fill missing days works', () => { // post fill the missing days for mock payouts. const missingDays = prefillMissingDays(payouts, fromDate, maxDays) - // expect amount of missing days to be 2 + // expect reward of missing days to be 2 expect(missingDays.length).toBe(2) // concatenated payouts are correct @@ -107,12 +101,12 @@ test('pre fill missing days works', () => { if (i > 0) { expect( daysPassed( - fromUnixTime(concatPayouts[i].block_timestamp), - fromUnixTime(concatPayouts[i - 1].block_timestamp) + fromUnixTime(concatPayouts[i].timestamp), + fromUnixTime(concatPayouts[i - 1].timestamp) ) ).toBe(1) - expect(concatPayouts[i].block_timestamp).toBeLessThan( - concatPayouts[i - 1].block_timestamp + expect(concatPayouts[i].timestamp).toBeLessThan( + concatPayouts[i - 1].timestamp ) } } @@ -143,12 +137,12 @@ test('pre fill and post fill missing days work together', () => { if (i > 0) { expect( daysPassed( - fromUnixTime(finalPayouts[i].block_timestamp), - fromUnixTime(finalPayouts[i - 1].block_timestamp) + fromUnixTime(finalPayouts[i].timestamp), + fromUnixTime(finalPayouts[i - 1].timestamp) ) ).toBe(1) - expect(finalPayouts[i].block_timestamp).toBeLessThan( - finalPayouts[i - 1].block_timestamp + expect(finalPayouts[i].timestamp).toBeLessThan( + finalPayouts[i - 1].timestamp ) } } diff --git a/packages/plugin-staking-api/src/index.tsx b/packages/plugin-staking-api/src/index.tsx index f1e220ec69..09defe18d3 100644 --- a/packages/plugin-staking-api/src/index.tsx +++ b/packages/plugin-staking-api/src/index.tsx @@ -4,6 +4,7 @@ import { ApolloProvider } from '@apollo/client' export * from './Client' +export * from './queries/useRewards' export * from './queries/useTokenPrice' export { ApolloProvider } diff --git a/packages/plugin-staking-api/src/queries/useRewards.tsx b/packages/plugin-staking-api/src/queries/useRewards.tsx new file mode 100644 index 0000000000..3a5525d0db --- /dev/null +++ b/packages/plugin-staking-api/src/queries/useRewards.tsx @@ -0,0 +1,33 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { gql, useQuery } from '@apollo/client' +import type { AllRewardsResult } from '../types' + +const QUERY = gql` + query AllRewards($chain: String!, $who: String!, $fromEra: Int!) { + allRewards(chain: $chain, who: $who, fromEra: $fromEra) { + claimed + era + reward + timestamp + validator + type + } + } +` + +export const useRewards = ({ + chain, + who, + fromEra, +}: { + chain: string + who: string + fromEra: number +}): AllRewardsResult => { + const { loading, error, data, refetch } = useQuery(QUERY, { + variables: { chain, who, fromEra }, + }) + return { loading, error, data, refetch } +} diff --git a/packages/plugin-staking-api/src/types.ts b/packages/plugin-staking-api/src/types.ts index acb977f8f0..58f72d4ea7 100644 --- a/packages/plugin-staking-api/src/types.ts +++ b/packages/plugin-staking-api/src/types.ts @@ -29,5 +29,16 @@ export type UseTokenPriceResult = Query & { } export type AllRewardsResult = Query & { - data: unknown + data: { + allRewards: NominatorReward[] + } +} + +export interface NominatorReward { + era: number + reward: number + claimed: boolean + timestamp: number + validator: string + type: string } From 5672b8f1e3e1513a6875118b68bb5774629bf3f8 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 11:32:44 +0700 Subject: [PATCH 03/33] mv `Payouts` to folder --- .../app/src/pages/Overview/{Payouts.tsx => Payouts/index.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/app/src/pages/Overview/{Payouts.tsx => Payouts/index.tsx} (100%) diff --git a/packages/app/src/pages/Overview/Payouts.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx similarity index 100% rename from packages/app/src/pages/Overview/Payouts.tsx rename to packages/app/src/pages/Overview/Payouts/index.tsx From 972e12945d18de9d19973d0fd01c2a3518640949 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 11:43:02 +0700 Subject: [PATCH 04/33] use `nominating` and `inPool` props --- packages/app/src/library/Graphs/PayoutBar.tsx | 18 +++++------------- packages/app/src/library/Graphs/PayoutLine.tsx | 17 +++++------------ packages/app/src/library/Graphs/types.ts | 4 ++++ .../app/src/pages/Overview/Payouts/index.tsx | 10 ++++++++++ packages/app/src/pages/Payouts/index.tsx | 12 ++++++++++++ 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/app/src/library/Graphs/PayoutBar.tsx b/packages/app/src/library/Graphs/PayoutBar.tsx index 8b459c34ff..df04aa7f9d 100644 --- a/packages/app/src/library/Graphs/PayoutBar.tsx +++ b/packages/app/src/library/Graphs/PayoutBar.tsx @@ -15,13 +15,9 @@ import { Tooltip, } from 'chart.js' import type { AnyApi } from 'common-types' -import { useActiveAccounts } from 'contexts/ActiveAccounts' -import { useBalances } from 'contexts/Balances' import { useNetwork } from 'contexts/Network' -import { useStaking } from 'contexts/Staking' import { useTheme } from 'contexts/Themes' import { format, fromUnixTime } from 'date-fns' -import { useSyncing } from 'hooks/useSyncing' import { DefaultLocale, locales } from 'locales' import { Bar } from 'react-chartjs-2' import { useTranslation } from 'react-i18next' @@ -44,17 +40,13 @@ export const PayoutBar = ({ days, height, data: { payouts, poolClaims, unclaimedPayouts }, + nominating, + inPool, }: PayoutBarProps) => { const { i18n, t } = useTranslation('library') const { mode } = useTheme() - const { inSetup } = useStaking() - const { getPoolMembership } = useBalances() - const { syncing } = useSyncing(['balances']) - const { activeAccount } = useActiveAccounts() - - const membership = getPoolMembership(activeAccount) const { unit, units, colors } = useNetwork().networkData - const notStaking = !syncing && inSetup() && !membership + const staking = nominating || inPool // get formatted rewards data for graph. const { allPayouts, allPoolClaims, allUnclaimedPayouts } = @@ -72,12 +64,12 @@ export const PayoutBar = ({ const { p: graphPoolClaims } = allPoolClaims // determine color for payouts - const colorPayouts = notStaking + const colorPayouts = !staking ? colors.transparent[mode] : colors.primary[mode] // determine color for poolClaims - const colorPoolClaims = notStaking + const colorPoolClaims = !staking ? colors.transparent[mode] : colors.secondary[mode] diff --git a/packages/app/src/library/Graphs/PayoutLine.tsx b/packages/app/src/library/Graphs/PayoutLine.tsx index 6376a53b4b..f257cf2e41 100644 --- a/packages/app/src/library/Graphs/PayoutLine.tsx +++ b/packages/app/src/library/Graphs/PayoutLine.tsx @@ -14,12 +14,8 @@ import { Tooltip, } from 'chart.js' import type { AnyApi } from 'common-types' -import { useActiveAccounts } from 'contexts/ActiveAccounts' -import { useBalances } from 'contexts/Balances' import { useNetwork } from 'contexts/Network' -import { useStaking } from 'contexts/Staking' import { useTheme } from 'contexts/Themes' -import { useSyncing } from 'hooks/useSyncing' import { Line } from 'react-chartjs-2' import { useTranslation } from 'react-i18next' import graphColors from 'styles/graphs/index.json' @@ -46,18 +42,15 @@ export const PayoutLine = ({ height, background, data: { payouts, poolClaims }, + nominating, + inPool, }: PayoutLineProps) => { const { t } = useTranslation('library') const { mode } = useTheme() - const { inSetup } = useStaking() - const { syncing } = useSyncing(['balances']) - const { getPoolMembership } = useBalances() - const { activeAccount } = useActiveAccounts() + const staking = nominating || inPool const { unit, units, colors } = useNetwork().networkData - const poolMembership = getPoolMembership(activeAccount) - const notStaking = !syncing && inSetup() && !poolMembership - const inPoolOnly = !syncing && inSetup() && !!poolMembership + const inPoolOnly = !nominating && inPool // define the most recent date that we will show on the graph. const fromDate = new Date() @@ -86,7 +79,7 @@ export const PayoutLine = ({ ) // determine color for payouts - const color = notStaking + const color = !staking ? colors.primary[mode] : !inPoolOnly ? colors.primary[mode] diff --git a/packages/app/src/library/Graphs/types.ts b/packages/app/src/library/Graphs/types.ts index 8b9603b245..f88c6cbcb3 100644 --- a/packages/app/src/library/Graphs/types.ts +++ b/packages/app/src/library/Graphs/types.ts @@ -23,6 +23,8 @@ export interface PayoutBarProps { days: number height: string data: GraphPayoutData + nominating: boolean + inPool: boolean } export interface PayoutLineProps { @@ -31,6 +33,8 @@ export interface PayoutLineProps { height: string background?: string data: GraphPayoutData + nominating: boolean + inPool: boolean } export interface GraphPayoutData { diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index d4b76b8f4e..4e1ed8c2bd 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -8,6 +8,7 @@ import { minDecimalPlaces } from '@w3ux/utils' import BigNumber from 'bignumber.js' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' +import { useBalances } from 'contexts/Balances' import { useNetwork } from 'contexts/Network' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' @@ -42,8 +43,13 @@ export const PayoutsInner = () => { const { network } = useNetwork() const { containerRefs } = useUi() const { poolClaims } = useSubscanData() + const { getPoolMembership } = useBalances() const { activeAccount } = useActiveAccounts() + const membership = getPoolMembership(activeAccount) + const nominating = !inSetup() + const inPool = membership !== null + const { data } = useRewards({ chain: network, who: activeAccount || '', @@ -143,6 +149,8 @@ export const PayoutsInner = () => { days={19} height="150px" data={{ payouts, unclaimedPayouts, poolClaims }} + nominating={nominating} + inPool={inPool} />
{ average={10} height="65px" data={{ payouts, unclaimedPayouts, poolClaims }} + nominating={nominating} + inPool={inPool} />
diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index 0d2df3f619..642a893194 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -4,6 +4,8 @@ import { useSize } from '@w3ux/hooks' import type { AnyApi, PageProps } from 'common-types' import { MaxPayoutDays } from 'consts' +import { useActiveAccounts } from 'contexts/ActiveAccounts' +import { useBalances } from 'contexts/Balances' import { useHelp } from 'contexts/Help' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' @@ -35,9 +37,15 @@ export const Payouts = ({ page: { key } }: PageProps) => { const { inSetup } = useStaking() const { syncing } = useSyncing() const { containerRefs } = useUi() + const { getPoolMembership } = useBalances() let { unclaimedPayouts } = useSubscanData() + const { activeAccount } = useActiveAccounts() const { payouts, poolClaims, injectBlockTimestamp } = useSubscanData() + const notStaking = !syncing && inSetup() + const membership = getPoolMembership(activeAccount) + const nominating = !inSetup() + const inPool = membership !== null const [payoutsList, setPayoutLists] = useState([]) @@ -124,12 +132,16 @@ export const Payouts = ({ page: { key } }: PageProps) => { days={MaxPayoutDays} height="165px" data={{ payouts, unclaimedPayouts, poolClaims }} + nominating={nominating} + inPool={inPool} />
From 723d07cd967c705cc3ec86cda57d7c926b1a4050 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 12:11:59 +0700 Subject: [PATCH 05/33] payouts list use staking api --- .../pages/Overview/Payouts/ActiveGraph.tsx | 71 ++++++++++++++++ .../pages/Overview/Payouts/InactiveGraph.tsx | 27 ++++++ .../app/src/pages/Overview/Payouts/index.tsx | 28 +++---- .../app/src/pages/Payouts/ActiveGraph.tsx | 83 +++++++++++++++++++ .../app/src/pages/Payouts/InactiveGraph.tsx | 26 ++++++ .../src/pages/Payouts/PayoutList/index.tsx | 10 +-- packages/app/src/pages/Payouts/index.tsx | 51 ++++-------- 7 files changed, 237 insertions(+), 59 deletions(-) create mode 100644 packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx create mode 100644 packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx create mode 100644 packages/app/src/pages/Payouts/ActiveGraph.tsx create mode 100644 packages/app/src/pages/Payouts/InactiveGraph.tsx diff --git a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx new file mode 100644 index 0000000000..e9e3271fdd --- /dev/null +++ b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx @@ -0,0 +1,71 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useActiveAccounts } from 'contexts/ActiveAccounts' +import { useApi } from 'contexts/Api' +import { useNetwork } from 'contexts/Network' +import { useSubscanData } from 'hooks/useSubscanData' +import { PayoutBar } from 'library/Graphs/PayoutBar' +import { PayoutLine } from 'library/Graphs/PayoutLine' +import { ApolloProvider, client, useRewards } from 'plugin-staking-api' +import type { NominatorReward } from 'plugin-staking-api/src/types' + +export const ActiveGraphInner = ({ + nominating, + inPool, + lineMarginTop, +}: { + nominating: boolean + inPool: boolean + lineMarginTop: string +}) => { + const { activeEra } = useApi() + const { network } = useNetwork() + const { poolClaims } = useSubscanData() + const { activeAccount } = useActiveAccounts() + + const { data } = useRewards({ + chain: network, + who: activeAccount || '', + fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), + }) + const allRewards = data?.allRewards ?? [] + const payouts = + allRewards.filter((reward: NominatorReward) => reward.claimed === true) ?? + [] + const unclaimedPayouts = + allRewards.filter((reward: NominatorReward) => reward.claimed === false) ?? + [] + + return ( + <> + +
+ +
+ + ) +} + +export const ActiveGraph = (props: { + nominating: boolean + inPool: boolean + lineMarginTop: string +}) => ( + + + +) diff --git a/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx new file mode 100644 index 0000000000..ac7a0ff9d7 --- /dev/null +++ b/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx @@ -0,0 +1,27 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { PayoutBar } from 'library/Graphs/PayoutBar' +import { PayoutLine } from 'library/Graphs/PayoutLine' + +export const InactiveGraph = () => ( + <> + +
+ +
+ +) diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index 4e1ed8c2bd..4ee9430359 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -17,8 +17,6 @@ import { formatDistance, fromUnixTime, getUnixTime } from 'date-fns' import { useSubscanData } from 'hooks/useSubscanData' import { useSyncing } from 'hooks/useSyncing' import { CardHeaderWrapper } from 'library/Card/Wrappers' -import { PayoutBar } from 'library/Graphs/PayoutBar' -import { PayoutLine } from 'library/Graphs/PayoutLine' import { formatRewardsForGraphs, formatSize } from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' import { StatusLabel } from 'library/StatusLabel' @@ -27,6 +25,8 @@ import { ApolloProvider, client, useRewards } from 'plugin-staking-api' import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { planckToUnitBn } from 'utils' +import { ActiveGraph } from './ActiveGraph' +import { InactiveGraph } from './InactiveGraph' export const PayoutsInner = () => { const { i18n, t } = useTranslation('pages') @@ -49,6 +49,8 @@ export const PayoutsInner = () => { const membership = getPoolMembership(activeAccount) const nominating = !inSetup() const inPool = membership !== null + const staking = nominating || inPool + const notStaking = !syncing && !staking const { data } = useRewards({ chain: network, @@ -63,8 +65,6 @@ export const PayoutsInner = () => { const unclaimedPayouts = allRewards.filter((reward: AnyJson) => reward.claimed === false) ?? [] - const notStaking = !syncing && inSetup() - // Ref to the graph container. const graphInnerRef = useRef(null) @@ -145,23 +145,15 @@ export const PayoutsInner = () => { transition: 'opacity 0.5s', }} > - -
- -
+ ) : ( + + )}

diff --git a/packages/app/src/pages/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Payouts/ActiveGraph.tsx new file mode 100644 index 0000000000..5850ed9dd3 --- /dev/null +++ b/packages/app/src/pages/Payouts/ActiveGraph.tsx @@ -0,0 +1,83 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { AnyApi } from 'common-types' +import { MaxPayoutDays } from 'consts' +import { useActiveAccounts } from 'contexts/ActiveAccounts' +import { useApi } from 'contexts/Api' +import { useNetwork } from 'contexts/Network' +import { Subscan } from 'controllers/Subscan' +import type { PayoutsAndClaims } from 'controllers/Subscan/types' +import { useSubscanData } from 'hooks/useSubscanData' +import { PayoutBar } from 'library/Graphs/PayoutBar' +import { PayoutLine } from 'library/Graphs/PayoutLine' +import { ApolloProvider, client, useRewards } from 'plugin-staking-api' +import type { NominatorReward } from 'plugin-staking-api/src/types' +import { useEffect } from 'react' + +export const ActiveGraphInner = ({ + nominating, + inPool, + setPayoutLists, +}: { + nominating: boolean + inPool: boolean + setPayoutLists: (payouts: AnyApi[]) => void +}) => { + const { activeEra } = useApi() + const { network } = useNetwork() + const { poolClaims } = useSubscanData() + const { activeAccount } = useActiveAccounts() + + const { data } = useRewards({ + chain: network, + who: activeAccount || '', + fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), + }) + + const allRewards = data?.allRewards ?? [] + const payouts = + allRewards.filter((reward: NominatorReward) => reward.claimed === true) ?? + [] + const unclaimedPayouts = + allRewards.filter((reward: NominatorReward) => reward.claimed === false) ?? + [] + + useEffect(() => { + // filter zero rewards and order via timestamp, most recent first. + const payoutsList = (allRewards as PayoutsAndClaims).concat( + poolClaims + ) as PayoutsAndClaims + setPayoutLists(Subscan.removeNonZeroAmountAndSort(payoutsList)) + }, [JSON.stringify(payouts), JSON.stringify(poolClaims)]) + + return ( + <> + + + + ) +} + +export const ActiveGraph = (props: { + nominating: boolean + inPool: boolean + setPayoutLists: (payouts: AnyApi[]) => void +}) => ( + + + +) diff --git a/packages/app/src/pages/Payouts/InactiveGraph.tsx b/packages/app/src/pages/Payouts/InactiveGraph.tsx new file mode 100644 index 0000000000..6169f03258 --- /dev/null +++ b/packages/app/src/pages/Payouts/InactiveGraph.tsx @@ -0,0 +1,26 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { MaxPayoutDays } from 'consts' +import { PayoutBar } from 'library/Graphs/PayoutBar' +import { PayoutLine } from 'library/Graphs/PayoutLine' + +export const InactiveGraph = () => ( + <> + + + +) diff --git a/packages/app/src/pages/Payouts/PayoutList/index.tsx b/packages/app/src/pages/Payouts/PayoutList/index.tsx index 8508970317..c50c456bce 100644 --- a/packages/app/src/pages/Payouts/PayoutList/index.tsx +++ b/packages/app/src/pages/Payouts/PayoutList/index.tsx @@ -113,9 +113,7 @@ export const PayoutListInner = ({ const labelClass = p.type === 'pool' ? 'claim' : 'reward' // get validator if it exists - const validator = validators.find( - (v) => v.address === p.validator_stash - ) + const validator = validators.find((v) => v.address === p.validator) // get pool if it exists const pool = bondedPools.find(({ id }) => id === p.pool_id) @@ -150,7 +148,7 @@ export const PayoutListInner = ({ <> + {planckToUnitBn( - new BigNumber(p.amount), + new BigNumber(p.reward), units ).toString()}{' '} {unit} @@ -167,9 +165,9 @@ export const PayoutListInner = ({
{label === t('payouts.payout') && (batchIndex > 0 ? ( - + ) : ( -
{ellipsisFn(p.validator_stash)}
+
{ellipsisFn(p.validator)}
))} {label === t('payouts.poolClaim') && (pool ? ( diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index 642a893194..fc2f82a540 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -3,7 +3,6 @@ import { useSize } from '@w3ux/hooks' import type { AnyApi, PageProps } from 'common-types' -import { MaxPayoutDays } from 'consts' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useBalances } from 'contexts/Balances' import { useHelp } from 'contexts/Help' @@ -11,22 +10,20 @@ import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' import { useUi } from 'contexts/UI' import { Subscan } from 'controllers/Subscan' -import type { PayoutsAndClaims } from 'controllers/Subscan/types' -import { useSubscanData } from 'hooks/useSubscanData' import { useSyncing } from 'hooks/useSyncing' import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers' -import { PayoutBar } from 'library/Graphs/PayoutBar' -import { PayoutLine } from 'library/Graphs/PayoutLine' import { formatSize } from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' import { PluginLabel } from 'library/PluginLabel' import { StatBoxList } from 'library/StatBoxList' import { StatusLabel } from 'library/StatusLabel' import { DefaultLocale, locales } from 'locales' -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { ButtonHelp } from 'ui-buttons' import { PageRow, PageTitle } from 'ui-structure' +import { ActiveGraph } from './ActiveGraph' +import { InactiveGraph } from './InactiveGraph' import { PayoutList } from './PayoutList' import { LastEraPayoutStat } from './Stats/LastEraPayout' @@ -38,16 +35,15 @@ export const Payouts = ({ page: { key } }: PageProps) => { const { syncing } = useSyncing() const { containerRefs } = useUi() const { getPoolMembership } = useBalances() - let { unclaimedPayouts } = useSubscanData() const { activeAccount } = useActiveAccounts() - const { payouts, poolClaims, injectBlockTimestamp } = useSubscanData() const notStaking = !syncing && inSetup() const membership = getPoolMembership(activeAccount) const nominating = !inSetup() const inPool = membership !== null + const staking = nominating || inPool - const [payoutsList, setPayoutLists] = useState([]) + const [payoutsList, setPayoutLists] = useState([]) const ref = useRef(null) const size = useSize(ref, { @@ -55,24 +51,15 @@ export const Payouts = ({ page: { key } }: PageProps) => { }) const { width, height, minHeight } = formatSize(size, 280) - // Inject `timestamp` for unclaimed payouts. - unclaimedPayouts = injectBlockTimestamp(unclaimedPayouts) - - const payoutsAndClaims = (payouts as PayoutsAndClaims).concat(poolClaims) const payoutsFromDate = Subscan.payoutsFromDate( - payoutsAndClaims, + payoutsList, locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) const payoutsToDate = Subscan.payoutsToDate( - payoutsAndClaims, + payoutsList, locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) - useEffect(() => { - // filter zero rewards and order via block timestamp, most recent first. - setPayoutLists(Subscan.removeNonZeroAmountAndSort(payoutsAndClaims)) - }, [JSON.stringify(payouts), JSON.stringify(poolClaims)]) - return ( <> @@ -128,21 +115,15 @@ export const Payouts = ({ page: { key } }: PageProps) => { transition: 'opacity 0.5s', }} > - - + {staking ? ( + + ) : ( + + )}
From 25e8adc14afefe63d13087ed8f96d4f35088b906 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 12:25:50 +0700 Subject: [PATCH 06/33] tidy up plugin management --- packages/app/src/contexts/Plugins/index.tsx | 3 --- packages/app/src/library/StatusLabel/index.tsx | 1 - .../app/src/pages/Overview/Payouts/index.tsx | 10 +++++----- packages/app/src/pages/Payouts/index.tsx | 18 ++++++++++++------ packages/locales/src/resources/cn/pages.json | 3 +++ packages/locales/src/resources/en/pages.json | 3 +++ 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/app/src/contexts/Plugins/index.tsx b/packages/app/src/contexts/Plugins/index.tsx index 8edc2f330d..275a6692d0 100644 --- a/packages/app/src/contexts/Plugins/index.tsx +++ b/packages/app/src/contexts/Plugins/index.tsx @@ -53,9 +53,6 @@ export const PluginsProvider = ({ children }: { children: ReactNode }) => { Subscan.resetData() } else if (isReady && !activeEra.index.isZero()) { Subscan.network = network - if (activeAccount) { - Subscan.handleFetchPayouts(activeAccount) - } } }, [plugins.includes('subscan'), isReady, network, activeAccount, activeEra]) diff --git a/packages/app/src/library/StatusLabel/index.tsx b/packages/app/src/library/StatusLabel/index.tsx index 8920286936..b22662c06a 100644 --- a/packages/app/src/library/StatusLabel/index.tsx +++ b/packages/app/src/library/StatusLabel/index.tsx @@ -27,7 +27,6 @@ export const StatusLabel = ({ const { inSetup } = useStaking() const { getPoolMembership } = useBalances() const { activeAccount } = useActiveAccounts() - const membership = getPoolMembership(activeAccount) // syncing or not staking diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index 4ee9430359..51f696c72b 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -39,9 +39,9 @@ export const PayoutsInner = () => { const { activeEra } = useApi() const { inSetup } = useStaking() const { syncing } = useSyncing() - const { plugins } = usePlugins() const { network } = useNetwork() const { containerRefs } = useUi() + const { pluginEnabled } = usePlugins() const { poolClaims } = useSubscanData() const { getPoolMembership } = useBalances() const { activeAccount } = useActiveAccounts() @@ -122,11 +122,11 @@ export const PayoutsInner = () => {
- {!plugins.includes('subscan') ? ( + {!pluginEnabled('staking_api') ? ( ) : ( @@ -145,7 +145,7 @@ export const PayoutsInner = () => { transition: 'opacity 0.5s', }} > - {staking ? ( + {staking && pluginEnabled('staking_api') ? ( { const { i18n, t } = useTranslation() const { openHelp } = useHelp() - const { plugins } = usePlugins() const { inSetup } = useStaking() const { syncing } = useSyncing() const { containerRefs } = useUi() + const { pluginEnabled } = usePlugins() const { getPoolMembership } = useBalances() const { activeAccount } = useActiveAccounts() @@ -60,6 +60,12 @@ export const Payouts = ({ page: { key } }: PageProps) => { locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) + useEffect(() => { + if (!pluginEnabled('staking_api')) { + setPayoutLists([]) + } + }, [pluginEnabled('staking_api')]) + return ( <> @@ -91,11 +97,11 @@ export const Payouts = ({ page: { key } }: PageProps) => {
- {!plugins.includes('subscan') ? ( + {!pluginEnabled('staking_api') ? ( ) : ( @@ -115,7 +121,7 @@ export const Payouts = ({ page: { key } }: PageProps) => { transition: 'opacity 0.5s', }} > - {staking ? ( + {staking && pluginEnabled('staking_api') ? ( Date: Thu, 12 Dec 2024 12:34:19 +0700 Subject: [PATCH 07/33] mv Subcan methods to graph utils --- packages/app/src/controllers/Subscan/index.ts | 148 +--------------- packages/app/src/library/Graphs/Utils.ts | 163 ++++++++++++------ packages/app/src/pages/Overview/index.tsx | 2 - .../app/src/pages/Payouts/ActiveGraph.tsx | 4 +- packages/app/src/pages/Payouts/index.tsx | 11 +- 5 files changed, 117 insertions(+), 211 deletions(-) diff --git a/packages/app/src/controllers/Subscan/index.ts b/packages/app/src/controllers/Subscan/index.ts index d227228e39..22716c5ef9 100644 --- a/packages/app/src/controllers/Subscan/index.ts +++ b/packages/app/src/controllers/Subscan/index.ts @@ -1,16 +1,11 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { Locale } from 'date-fns' -import { format, fromUnixTime, getUnixTime, subDays } from 'date-fns' import { poolMembersPerPage } from 'library/List/defaults' -import type { NominatorReward } from 'plugin-staking-api/src/types' import type { PoolMember } from 'types' import type { - PayoutsAndClaims, SubscanData, SubscanEraPoints, - SubscanPayout, SubscanPoolClaim, SubscanPoolClaimRaw, SubscanPoolMember, @@ -29,9 +24,6 @@ export class Subscan { // Total amount of requests that can be made in 1 second. static TOTAL_REQUESTS_PER_SECOND = 5 - // Maximum amount of payout days supported. - static MAX_PAYOUT_DAYS = 60 - // The network to use for Subscan API calls. static network: string @@ -55,24 +47,15 @@ export class Subscan { static handleFetchPayouts = async (address: string): Promise => { try { if (!this.payoutData[address]) { - const results = await Promise.all([ - this.fetchNominatorPayouts(address), - this.fetchPoolClaims(address), - ]) - const { payouts, unclaimedPayouts } = results[0] - const poolClaims = results[1] - - // Persist results to class. + const poolClaims = await this.fetchPoolClaims(address) this.payoutData[address] = { - payouts, - unclaimedPayouts, poolClaims, } document.dispatchEvent( new CustomEvent('subscan-data-updated', { detail: { - keys: ['payouts', 'unclaimedPayouts', 'poolClaims'], + keys: ['poolClaims'], }, }) ) @@ -82,69 +65,6 @@ export class Subscan { } } - // Fetch nominator payouts from Subscan. NOTE: Payouts with a `block_timestamp` of 0 are - // unclaimed. - static fetchNominatorPayouts = async ( - address: string - ): Promise<{ - payouts: NominatorReward[] - unclaimedPayouts: NominatorReward[] - }> => { - try { - const result = await this.makeRequest(this.ENDPOINTS.rewardSlash, { - address, - is_stash: true, - row: 100, - page: 0, - }) - - let payouts = - result?.list?.filter( - ({ block_timestamp }: SubscanPayout) => block_timestamp !== 0 - ) || [] - - let unclaimedPayouts = - result?.list?.filter((l: SubscanPayout) => l.block_timestamp === 0) || - [] - // Further filter unclaimed payouts to ensure that payout records of `stash` and - // `validator_stash` are not repeated for an era. NOTE: This was introduced to remove errornous - // data where there were duplicated payout records (with different amounts) for a stash - - // validator - era record. from Subscan. - unclaimedPayouts = unclaimedPayouts.filter( - (u: SubscanPayout) => - !payouts - .find( - (p: SubscanPayout) => - p.stash === u.stash && - p.validator_stash === u.validator_stash && - p.era === u.era - ) - .map((p: SubscanPayout) => ({ - era: p.era, - reward: p.amount, - claimed: false, - timestamp: p.block_timestamp, - validator: p.validator_stash, - type: 'nominator', - })) - ) - - payouts = payouts.map((p: SubscanPayout) => ({ - era: p.era, - reward: p.amount, - claimed: true, - timestamp: p.block_timestamp, - validator: p.validator_stash, - type: 'nominator', - })) - - return { payouts, unclaimedPayouts } - } catch (e) { - // Silently fail request and return empty records. - return { payouts: [], unclaimedPayouts: [] } - } - } - // Fetch pool claims from Subscan, ensuring no payouts have block_timestamp of 0. static fetchPoolClaims = async ( address: string @@ -261,70 +181,6 @@ export class Subscan { this.payoutData = {} } - // Remove unclaimed payouts and dispatch update event. - static removeUnclaimedPayouts = (address: string, eraPayouts: string[]) => { - const newUnclaimedPayouts = (this.payoutData[address]?.unclaimedPayouts || - []) as NominatorReward[] - - eraPayouts.forEach(([era]) => { - newUnclaimedPayouts.filter((u) => String(u.era) !== era) - }) - this.payoutData[address].unclaimedPayouts = newUnclaimedPayouts - - document.dispatchEvent( - new CustomEvent('subscan-data-updated', { - detail: { - keys: ['unclaimedPayouts'], - }, - }) - ) - } - - // Take non-zero rewards in most-recent order. - static removeNonZeroAmountAndSort = (payouts: PayoutsAndClaims) => { - const list = payouts - .filter((p) => Number(p.reward) > 0) - .sort((a, b) => b.timestamp - a.timestamp) - - // Calculates from the current date. - const fromTimestamp = getUnixTime(subDays(new Date(), this.MAX_PAYOUT_DAYS)) - // Ensure payouts not older than `MAX_PAYOUT_DAYS` are returned. - return list.filter(({ timestamp }) => timestamp >= fromTimestamp) - } - - // Calculate the earliest date of a payout list. - static payoutsFromDate = (payouts: PayoutsAndClaims, locale: Locale) => { - if (!payouts.length) { - return undefined - } - const filtered = this.removeNonZeroAmountAndSort(payouts) - if (!filtered.length) { - return undefined - } - return format( - fromUnixTime(filtered[filtered.length - 1].timestamp), - 'do MMM', - { - locale, - } - ) - } - - // Calculate the latest date of a payout list. - static payoutsToDate = (payouts: PayoutsAndClaims, locale: Locale) => { - if (!payouts.length) { - return undefined - } - const filtered = this.removeNonZeroAmountAndSort(payouts || []) - if (!filtered.length) { - return undefined - } - - return format(fromUnixTime(filtered[0].timestamp), 'do MMM', { - locale, - }) - } - // Get the public Subscan endpoint. static getEndpoint = () => `https://${this.network}.api.subscan.io` diff --git a/packages/app/src/library/Graphs/Utils.ts b/packages/app/src/library/Graphs/Utils.ts index ee1d59ff27..c742f825c3 100644 --- a/packages/app/src/library/Graphs/Utils.ts +++ b/packages/app/src/library/Graphs/Utils.ts @@ -5,9 +5,12 @@ import type { AnyJson } from '@w3ux/types' import BigNumber from 'bignumber.js' import type { AnyApi } from 'common-types' import { MaxPayoutDays } from 'consts' +import type { PayoutsAndClaims } from 'controllers/Subscan/types' +import type { Locale } from 'date-fns' import { addDays, differenceInDays, + format, fromUnixTime, getUnixTime, isSameDay, @@ -18,7 +21,7 @@ import type { NominatorReward } from 'plugin-staking-api/src/types' import { planckToUnitBn } from 'utils' import type { PayoutDayCursor } from './types' -// Given payouts, calculate daily income and fill missing days with zero rewards. +// Given payouts, calculate daily income and fill missing days with zero rewards export const calculateDailyPayouts = ( payouts: AnyApi, fromDate: Date, @@ -42,28 +45,28 @@ export const calculateDailyPayouts = ( // post-fill any missing days. [current day -> last payout] dailyPayouts = postFillMissingDays(payouts, fromDate, maxDays) - // start iterating payouts, most recent first. + // start iterating payouts, most recent first // - // payouts passed. + // payouts passed let p = 0 - // current day cursor. + // current day cursor let curDay: Date = fromDate - // current payout cursor. + // current payout cursor let curPayout: PayoutDayCursor = { reward: new BigNumber(0), } for (const payout of payouts) { p++ - // extract day from current payout. + // extract day from current payout const thisDay = startOfDay(fromUnixTime(payout.timestamp)) - // initialise current day if first payout. + // initialise current day if first payout if (p === 1) { curDay = thisDay } - // handle surpassed maximum days. + // handle surpassed maximum days if (daysPassed(thisDay, fromDate) >= maxDays) { dailyPayouts.push({ reward: planckToUnitBn(curPayout.reward, units), @@ -72,29 +75,29 @@ export const calculateDailyPayouts = ( break } - // get day difference between cursor and current payout. + // get day difference between cursor and current payout const daysDiff = daysPassed(thisDay, curDay) // handle new day. if (daysDiff > 0) { - // add current payout cursor to dailyPayouts. + // add current payout cursor to dailyPayouts dailyPayouts.push({ reward: planckToUnitBn(curPayout.reward, units), timestamp: getUnixTime(curDay), }) - // update day cursor to the new day. + // update day cursor to the new day curDay = thisDay - // reset current payout cursor for the new day. + // reset current payout cursor for the new day curPayout = { reward: new BigNumber(payout.reward), } } else { - // in same day. Aadd payout reward to current payout cursor. + // in same day. Aadd payout reward to current payout cursor curPayout.reward = curPayout.reward.plus(payout.reward) } - // if only 1 payout exists, or at the last unresolved payout, exit here. + // if only 1 payout exists, or at the last unresolved payout, exit here if ( payouts.length === 1 || (p === payouts.length && !curPayout.reward.isZero()) @@ -107,34 +110,34 @@ export const calculateDailyPayouts = ( } } - // return payout rewards as plain numbers. + // return payout rewards as plain numbers return dailyPayouts.map((q: AnyApi) => ({ ...q, reward: Number(q.reward.toString()), })) } -// Calculate average payouts per day. +// Calculate average payouts per day export const calculatePayoutAverages = ( payouts: AnyApi, fromDate: Date, days: number, avgDays: number ) => { - // if we don't need to take an average, just return `payouts`. + // if we don't need to take an average, just return `payouts` if (avgDays <= 1) { return payouts } - // create moving average value over `avgDays` past days, if any. + // create moving average value over `avgDays` past days, if any let payoutsAverages = [] for (let i = 0; i < payouts.length; i++) { // average period end. const end = Math.max(0, i - avgDays) - // the total reward earned in period. + // the total reward earned in period let total = 0 - // period length to be determined. + // period length to be determined let num = 0 for (let j = i; j >= end; j--) { @@ -163,9 +166,9 @@ export const calculatePayoutAverages = ( return payoutsAverages } -// Fetch rewards and graph meta data. +// Fetch rewards and graph meta data // -// Format provided payouts and returns the last payment. +// Format provided payouts and returns the last payment export const formatRewardsForGraphs = ( fromDate: Date, days: number, @@ -174,7 +177,7 @@ export const formatRewardsForGraphs = ( poolClaims: AnyApi, unclaimedPayouts: NominatorReward[] ) => { - // process nominator payouts. + // process nominator payouts const allPayouts = processPayouts(payouts, fromDate, days, units, 'nominate') // process unclaimed nominator payouts. @@ -186,7 +189,7 @@ export const formatRewardsForGraphs = ( 'nominate' ) - // process pool claims. + // process pool claims const allPoolClaims = processPayouts( poolClaims, fromDate, @@ -203,10 +206,9 @@ export const formatRewardsForGraphs = ( lastReward: getLatestReward(payouts, poolClaims), } } - -// Process payouts. +// Process payouts // -// calls the relevant functions on raw payouts to format them correctly. +// calls the relevant functions on raw payouts to format them correctly const processPayouts = ( payouts: AnyApi, fromDate: Date, @@ -216,16 +218,16 @@ const processPayouts = ( ) => { // normalise payout timestamps. const normalised = normalisePayouts(payouts) - // calculate payouts per day from the current day. + // calculate payouts per day from the current day let p = calculateDailyPayouts(normalised, fromDate, days, units, subject) - // pre-fill payouts if max days have not been reached. + // pre-fill payouts if max days have not been reached p = p.concat(prefillMissingDays(p, fromDate, days)) - // fill in gap days between payouts with zero values. + // fill in gap days between payouts with zero values p = fillGapDays(p, fromDate) - // reverse payouts: most recent last. + // reverse payouts: most recent last p = p.reverse() - // use normalised payouts for calculating the 10-day average prior to the start of the payout graph. + // use normalised payouts for calculating the 10-day average prior to the start of the payout graph const avgDays = 10 const preNormalised = getPreMaxDaysPayouts( normalised, @@ -233,7 +235,7 @@ const processPayouts = ( days, avgDays ) - // start of average calculation should be the earliest date. + // start of average calculation should be the earliest date const averageFromDate = subDays(fromDate, MaxPayoutDays) let a = calculateDailyPayouts( @@ -243,11 +245,11 @@ const processPayouts = ( units, subject ) - // prefill payouts if we are missing the earlier dates. + // prefill payouts if we are missing the earlier dates a = a.concat(prefillMissingDays(a, averageFromDate, avgDays)) - // fill in gap days between payouts with zero values. + // fill in gap days between payouts with zero values a = fillGapDays(a, averageFromDate) - // reverse payouts: most recent last. + // reverse payouts: most recent last a = a.reverse() return { p, a } @@ -256,14 +258,14 @@ const processPayouts = ( // Get payout average in `avgDays` day period after to `days` threshold // // These payouts are used for calculating the `avgDays`-day average prior to the start of the payout -// graph. +// graph const getPreMaxDaysPayouts = ( payouts: AnyApi, fromDate: Date, days: number, avgDays: number ) => - // remove payouts that are not within `avgDays` `days` pre-graph window. + // remove payouts that are not within `avgDays` `days` pre-graph window payouts.filter( (p: AnyApi) => daysPassed(fromUnixTime(p.timestamp), fromDate) > days && @@ -271,7 +273,7 @@ const getPreMaxDaysPayouts = ( ) // Combine payouts and pool claims. // -// combines payouts and pool claims into daily records. +// combines payouts and pool claims into daily records export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { // we first check if actual payouts exist, e.g. there are non-zero payout rewards present in // either payouts or pool claims. @@ -279,7 +281,7 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { const payoutExists = payouts.find((p: AnyApi) => p.reward > 0) || null // if no pool claims exist but payouts do, return payouts. Also do this if there are no payouts - // period. + // period if ( (!poolClaimExists && payoutExists) || (!payoutExists && !poolClaimExists) @@ -298,9 +300,9 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { })) } - // We now know pool claims *and* payouts exist. + // We now know pool claims *and* payouts exist // - // Now determine which dates to display. + // Now determine which dates to display let payoutDays: AnyJson[] = [] // prefill `dates` with all pool claim and payout days poolClaims.forEach((p: AnyApi) => { @@ -316,12 +318,12 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { } }) - // sort payoutDays by `timestamp`; + // sort payoutDays by `timestamp` payoutDays = payoutDays.sort((a: AnyApi, b: AnyApi) => a - b) // Iterate payout days. // - // Combine payouts into one unified `rewards` array. + // Combine payouts into one unified `rewards` array const rewards: AnyApi = [] // loop pool claims and consume / combine payouts @@ -352,7 +354,7 @@ export const combineRewards = (payouts: AnyApi, poolClaims: AnyApi) => { // Get latest reward. // -// Gets the latest reward from pool claims and nominator payouts. +// Gets the latest reward from pool claims and nominator payouts export const getLatestReward = (payouts: AnyApi, poolClaims: AnyApi) => { // get most recent payout const payoutExists = @@ -381,9 +383,9 @@ export const getLatestReward = (payouts: AnyApi, poolClaims: AnyApi) => { return lastReward } -// Fill in the days from the earliest payout day to `maxDays`. +// Fill in the days from the earliest payout day to `maxDays` // -// Takes the last (earliest) payout and fills the missing days from that payout day to `maxDays`. +// Takes the last (earliest) payout and fills the missing days from that payout day to `maxDays` export const prefillMissingDays = ( payouts: AnyApi, fromDate: Date, @@ -408,9 +410,9 @@ export const prefillMissingDays = ( return newPayouts } -// Fill in the days from the current day to the last payout. +// Fill in the days from the current day to the last payout // -// Takes the first payout (most recent) and fills the missing days from current day. +// Takes the first payout (most recent) and fills the missing days from current day export const postFillMissingDays = ( payouts: AnyApi, fromDate: Date, @@ -432,11 +434,11 @@ export const postFillMissingDays = ( return newPayouts } -// Fill gap days within payouts with zero rewards. +// Fill gap days within payouts with zero rewards export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { const finalPayouts: AnyApi = [] - // current day cursor. + // current day cursor let curDay = fromDate for (const p of payouts) { @@ -444,7 +446,7 @@ export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { const gapDays = Math.max(0, daysPassed(thisDay, curDay) - 1) if (gapDays > 0) { - // add any gap days. + // add any gap days if (gapDays > 0) { for (let j = 1; j <= gapDays; j++) { finalPayouts.push({ @@ -455,27 +457,27 @@ export const fillGapDays = (payouts: AnyApi, fromDate: Date) => { } } - // add the current day. + // add the current day finalPayouts.push(p) - // day cursor is now the new day. + // day cursor is now the new day curDay = thisDay } return finalPayouts } -// Utiltiy: normalise payout timestamps to start of day. +// Utiltiy: normalise payout timestamps to start of day export const normalisePayouts = (payouts: AnyApi) => payouts.map((p: AnyApi) => ({ ...p, timestamp: getUnixTime(startOfDay(fromUnixTime(p.timestamp))), })) -// Utility: days passed since 2 dates. +// Utility: days passed since 2 dates export const daysPassed = (from: Date, to: Date) => differenceInDays(startOfDay(to), startOfDay(from)) -// Utility: Formats a width and height pair. +// Utility: Formats a width and height pair export const formatSize = ( { width, @@ -490,3 +492,50 @@ export const formatSize = ( height: height || minHeight, minHeight, }) + +// Take non-zero rewards in most-recent order +export const removeNonZeroAmountAndSort = (payouts: PayoutsAndClaims) => { + const list = payouts + .filter((p) => Number(p.reward) > 0) + .sort((a, b) => b.timestamp - a.timestamp) + + // Calculates from the current date. + const fromTimestamp = getUnixTime(subDays(new Date(), MaxPayoutDays)) + // Ensure payouts not older than `MaxPayoutDays` are returned. + return list.filter(({ timestamp }) => timestamp >= fromTimestamp) +} + +// Calculate the earliest date of a payout list +export const getPayoutsFromDate = ( + payouts: PayoutsAndClaims, + locale: Locale +) => { + if (!payouts.length) { + return undefined + } + const filtered = removeNonZeroAmountAndSort(payouts) + if (!filtered.length) { + return undefined + } + return format( + fromUnixTime(filtered[filtered.length - 1].timestamp), + 'do MMM', + { + locale, + } + ) +} + +// Calculate the latest date of a payout list +export const getPayoutsToDate = (payouts: PayoutsAndClaims, locale: Locale) => { + if (!payouts.length) { + return undefined + } + const filtered = removeNonZeroAmountAndSort(payouts || []) + if (!filtered.length) { + return undefined + } + return format(fromUnixTime(filtered[0].timestamp), 'do MMM', { + locale, + }) +} diff --git a/packages/app/src/pages/Overview/index.tsx b/packages/app/src/pages/Overview/index.tsx index 079b02074b..e2b2c18371 100644 --- a/packages/app/src/pages/Overview/index.tsx +++ b/packages/app/src/pages/Overview/index.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only import { CardWrapper } from 'library/Card/Wrappers' -import { PluginLabel } from 'library/PluginLabel' import { StatBoxList } from 'library/StatBoxList' import { useTranslation } from 'react-i18next' import { PageHeading, PageRow, PageTitle, RowSection } from 'ui-structure' @@ -45,7 +44,6 @@ export const Overview = () => { - diff --git a/packages/app/src/pages/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Payouts/ActiveGraph.tsx index 5850ed9dd3..da456fadb4 100644 --- a/packages/app/src/pages/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Payouts/ActiveGraph.tsx @@ -6,11 +6,11 @@ import { MaxPayoutDays } from 'consts' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { useNetwork } from 'contexts/Network' -import { Subscan } from 'controllers/Subscan' import type { PayoutsAndClaims } from 'controllers/Subscan/types' import { useSubscanData } from 'hooks/useSubscanData' import { PayoutBar } from 'library/Graphs/PayoutBar' import { PayoutLine } from 'library/Graphs/PayoutLine' +import { removeNonZeroAmountAndSort } from 'library/Graphs/Utils' import { ApolloProvider, client, useRewards } from 'plugin-staking-api' import type { NominatorReward } from 'plugin-staking-api/src/types' import { useEffect } from 'react' @@ -48,7 +48,7 @@ export const ActiveGraphInner = ({ const payoutsList = (allRewards as PayoutsAndClaims).concat( poolClaims ) as PayoutsAndClaims - setPayoutLists(Subscan.removeNonZeroAmountAndSort(payoutsList)) + setPayoutLists(removeNonZeroAmountAndSort(payoutsList)) }, [JSON.stringify(payouts), JSON.stringify(poolClaims)]) return ( diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index 4588a5e853..a04fe2dcf3 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -9,10 +9,13 @@ import { useHelp } from 'contexts/Help' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' import { useUi } from 'contexts/UI' -import { Subscan } from 'controllers/Subscan' import { useSyncing } from 'hooks/useSyncing' import { CardHeaderWrapper, CardWrapper } from 'library/Card/Wrappers' -import { formatSize } from 'library/Graphs/Utils' +import { + formatSize, + getPayoutsFromDate, + getPayoutsToDate, +} from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' import { PluginLabel } from 'library/PluginLabel' import { StatBoxList } from 'library/StatBoxList' @@ -51,11 +54,11 @@ export const Payouts = ({ page: { key } }: PageProps) => { }) const { width, height, minHeight } = formatSize(size, 280) - const payoutsFromDate = Subscan.payoutsFromDate( + const payoutsFromDate = getPayoutsFromDate( payoutsList, locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) - const payoutsToDate = Subscan.payoutsToDate( + const payoutsToDate = getPayoutsToDate( payoutsList, locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat ) From 188f1a6a85a8f9a18ba80a806d6e93607e3a7bd9 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 12:40:35 +0700 Subject: [PATCH 08/33] set lastReward --- .../pages/Overview/Payouts/ActiveGraph.tsx | 5 ++ .../app/src/pages/Overview/Payouts/index.tsx | 57 +++++-------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx index e9e3271fdd..2872b53936 100644 --- a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx @@ -14,10 +14,12 @@ export const ActiveGraphInner = ({ nominating, inPool, lineMarginTop, + setLastReward, }: { nominating: boolean inPool: boolean lineMarginTop: string + setLastReward: (reward: NominatorReward | undefined) => void }) => { const { activeEra } = useApi() const { network } = useNetwork() @@ -37,6 +39,8 @@ export const ActiveGraphInner = ({ allRewards.filter((reward: NominatorReward) => reward.claimed === false) ?? [] + setLastReward(payouts[0]) + return ( <> void }) => ( diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index 51f696c72b..2e00e4cea5 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -3,32 +3,29 @@ import { useSize } from '@w3ux/hooks' import { Odometer } from '@w3ux/react-odometer' -import type { AnyJson } from '@w3ux/types' import { minDecimalPlaces } from '@w3ux/utils' import BigNumber from 'bignumber.js' import { useActiveAccounts } from 'contexts/ActiveAccounts' -import { useApi } from 'contexts/Api' import { useBalances } from 'contexts/Balances' import { useNetwork } from 'contexts/Network' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' import { useUi } from 'contexts/UI' import { formatDistance, fromUnixTime, getUnixTime } from 'date-fns' -import { useSubscanData } from 'hooks/useSubscanData' import { useSyncing } from 'hooks/useSyncing' import { CardHeaderWrapper } from 'library/Card/Wrappers' -import { formatRewardsForGraphs, formatSize } from 'library/Graphs/Utils' +import { formatSize } from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' import { StatusLabel } from 'library/StatusLabel' import { DefaultLocale, locales } from 'locales' -import { ApolloProvider, client, useRewards } from 'plugin-staking-api' -import { useRef } from 'react' +import type { NominatorReward } from 'plugin-staking-api/src/types' +import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { planckToUnitBn } from 'utils' import { ActiveGraph } from './ActiveGraph' import { InactiveGraph } from './InactiveGraph' -export const PayoutsInner = () => { +export const Payouts = () => { const { i18n, t } = useTranslation('pages') const { networkData: { @@ -36,13 +33,10 @@ export const PayoutsInner = () => { brand: { token: Token }, }, } = useNetwork() - const { activeEra } = useApi() const { inSetup } = useStaking() const { syncing } = useSyncing() - const { network } = useNetwork() const { containerRefs } = useUi() const { pluginEnabled } = usePlugins() - const { poolClaims } = useSubscanData() const { getPoolMembership } = useBalances() const { activeAccount } = useActiveAccounts() @@ -52,18 +46,7 @@ export const PayoutsInner = () => { const staking = nominating || inPool const notStaking = !syncing && !staking - const { data } = useRewards({ - chain: network, - who: activeAccount || '', - fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), - }) - - const allRewards = data?.allRewards ?? [] - const payouts = - allRewards.filter((reward: AnyJson) => reward.claimed === true) ?? [] - - const unclaimedPayouts = - allRewards.filter((reward: AnyJson) => reward.claimed === false) ?? [] + const [lastReward, setLastReward] = useState() // Ref to the graph container. const graphInnerRef = useRef(null) @@ -74,20 +57,11 @@ export const PayoutsInner = () => { }) const { width, height, minHeight } = formatSize(size, 260) - // Get the last reward with its timestmap. - const { lastReward } = formatRewardsForGraphs( - new Date(), - 14, - units, - payouts, - poolClaims, - unclaimedPayouts - ) let formatFrom = new Date() let formatTo = new Date() let formatOpts = {} - if (lastReward !== null) { - formatFrom = fromUnixTime(lastReward?.timestamp ?? getUnixTime(new Date())) + if (lastReward !== undefined) { + formatFrom = fromUnixTime(lastReward.timestamp ?? getUnixTime(new Date())) formatTo = new Date() formatOpts = { addSuffix: true, @@ -95,6 +69,10 @@ export const PayoutsInner = () => { } } + useEffect(() => { + setLastReward(undefined) + }, [activeAccount]) + return ( <> @@ -103,17 +81,17 @@ export const PayoutsInner = () => { - {lastReward === null ? ( + {lastReward === undefined ? ( '' ) : ( <> {formatDistance(formatFrom, formatTo, formatOpts)} @@ -150,6 +128,7 @@ export const PayoutsInner = () => { nominating={nominating} inPool={inPool} lineMarginTop="3rem" + setLastReward={setLastReward} /> ) : ( @@ -159,9 +138,3 @@ export const PayoutsInner = () => { ) } - -export const Payouts = () => ( - - - -) From 418922df44a6927d243a8466752adc86f3801d7a Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 12:56:21 +0700 Subject: [PATCH 09/33] remove plugin label --- packages/app/src/pages/Payouts/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index a04fe2dcf3..aaa9bb6771 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -17,7 +17,6 @@ import { getPayoutsToDate, } from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' -import { PluginLabel } from 'library/PluginLabel' import { StatBoxList } from 'library/StatBoxList' import { StatusLabel } from 'library/StatusLabel' import { DefaultLocale, locales } from 'locales' @@ -77,7 +76,6 @@ export const Payouts = ({ page: { key } }: PageProps) => { -

{t('payouts.payoutHistory', { ns: 'pages' })} From beb47a23afdf927d6011f303c3fa35c87cf2e8cb Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 22:35:07 +0700 Subject: [PATCH 10/33] fetch unclaimed rewards from staking api --- packages/app/src/Router.tsx | 14 +++++-- packages/app/src/StakingApi.tsx | 37 +++++++++++++++++++ packages/app/src/contexts/Payouts/defaults.ts | 11 +++++- packages/app/src/contexts/Payouts/index.tsx | 20 +++++++--- packages/app/src/contexts/Payouts/types.ts | 4 +- packages/app/src/library/Headers/Sync.tsx | 14 ------- .../src/overlay/modals/ClaimPayouts/Forms.tsx | 35 +++++++++++------- .../src/overlay/modals/ClaimPayouts/Item.tsx | 10 ++--- .../overlay/modals/ClaimPayouts/Overview.tsx | 29 +++++++-------- .../src/overlay/modals/ClaimPayouts/Utils.ts | 9 ++--- .../src/overlay/modals/ClaimPayouts/index.tsx | 4 +- .../src/overlay/modals/ClaimPayouts/types.ts | 4 +- .../Active/Status/UnclaimedPayoutsStatus.tsx | 27 +++++--------- .../pages/Overview/Payouts/ActiveGraph.tsx | 5 ++- packages/app/src/pages/Payouts/index.tsx | 2 +- packages/plugin-staking-api/src/index.tsx | 1 + .../src/queries/useUnclaimedRewards.tsx | 37 +++++++++++++++++++ packages/plugin-staking-api/src/types.ts | 22 +++++++++++ 18 files changed, 198 insertions(+), 87 deletions(-) create mode 100644 packages/app/src/StakingApi.tsx create mode 100644 packages/plugin-staking-api/src/queries/useUnclaimedRewards.tsx diff --git a/packages/app/src/Router.tsx b/packages/app/src/Router.tsx index cb139e817b..86ba7ad97a 100644 --- a/packages/app/src/Router.tsx +++ b/packages/app/src/Router.tsx @@ -7,6 +7,8 @@ import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts' import { useOtherAccounts } from 'contexts/Connect/OtherAccounts' import { useNetwork } from 'contexts/Network' +import { usePlugins } from 'contexts/Plugins' +import { useStaking } from 'contexts/Staking' import { useUi } from 'contexts/UI' import { Notifications } from 'controllers/Notifications' import { ErrorFallbackApp, ErrorFallbackRoutes } from 'library/ErrorBoundary' @@ -31,33 +33,38 @@ import { Routes, useLocation, } from 'react-router-dom' +import { StakingApi } from 'StakingApi' import { Body, Main } from 'ui-structure' const RouterInner = () => { const { t } = useTranslation() const { network } = useNetwork() + const { inSetup } = useStaking() const { pathname } = useLocation() const { setContainerRefs } = useUi() + const { pluginEnabled } = usePlugins() const { accounts } = useImportedAccounts() const { accountsInitialised } = useOtherAccounts() const { activeAccount, setActiveAccount } = useActiveAccounts() + const nominating = !inSetup() + const stakingApiEnabled = pluginEnabled('staking_api') // References to outer container. const mainInterfaceRef = useRef(null) - // Scroll to top of the window on every page change or network change. + // Scroll to top of the window on every page change or network change useEffect(() => { window.scrollTo(0, 0) }, [pathname, network]) - // Set container references to UI context and make available throughout app. + // Set container references to UI context and make available throughout app useEffect(() => { setContainerRefs({ mainInterface: mainInterfaceRef, }) }, []) - // Open default account modal if url var present and accounts initialised. + // Open default account modal if url var present and accounts initialised useEffect(() => { if (accountsInitialised) { const aUrl = extractUrlValue('a') @@ -79,6 +86,7 @@ const RouterInner = () => { return ( + {stakingApiEnabled && nominating && } diff --git a/packages/app/src/StakingApi.tsx b/packages/app/src/StakingApi.tsx new file mode 100644 index 0000000000..e247280e22 --- /dev/null +++ b/packages/app/src/StakingApi.tsx @@ -0,0 +1,37 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useActiveAccounts } from 'contexts/ActiveAccounts' +import { useApi } from 'contexts/Api' +import { useNetwork } from 'contexts/Network' +import { usePayouts } from 'contexts/Payouts' +import { ApolloProvider, client, useUnclaimedRewards } from 'plugin-staking-api' +import { useEffect } from 'react' + +export const StakingApiInner = () => { + const { activeEra } = useApi() + const { network } = useNetwork() + const { setUnclaimedRewards } = usePayouts() + const { activeAccount } = useActiveAccounts() + + // Fetch and store unclaimed rewards + const { data, loading, error } = useUnclaimedRewards({ + chain: network, + who: activeAccount || '', + fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), + }) + + useEffect(() => { + if (!loading && !error && data?.unclaimedRewards) { + setUnclaimedRewards(data?.unclaimedRewards) + } + }, [data?.unclaimedRewards.total]) + + return null +} + +export const StakingApi = () => ( + + + +) diff --git a/packages/app/src/contexts/Payouts/defaults.ts b/packages/app/src/contexts/Payouts/defaults.ts index 6b458f6fab..0339fc6ee9 100644 --- a/packages/app/src/contexts/Payouts/defaults.ts +++ b/packages/app/src/contexts/Payouts/defaults.ts @@ -8,6 +8,15 @@ export const MaxSupportedPayoutEras = 7 export const defaultPayoutsContext: PayoutsContextInterface = { payoutsSynced: 'unsynced', - unclaimedPayouts: null, removeEraPayout: (era, validator) => {}, + unclaimedRewards: { + total: '0', + entries: [], + }, + setUnclaimedRewards: (unclaimedRewards) => {}, +} + +export const defaultUnclaimedRewards = { + total: '0', + entries: [], } diff --git a/packages/app/src/contexts/Payouts/index.tsx b/packages/app/src/contexts/Payouts/index.tsx index f32cd98563..57100326d9 100644 --- a/packages/app/src/contexts/Payouts/index.tsx +++ b/packages/app/src/contexts/Payouts/index.tsx @@ -14,6 +14,7 @@ import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { useNetwork } from 'contexts/Network' import { useStaking } from 'contexts/Staking' +import type { UnclaimedRewards } from 'plugin-staking-api/src/types' import type { ReactNode } from 'react' import { createContext, useContext, useEffect, useRef, useState } from 'react' import { perbillToPercent } from 'utils' @@ -46,6 +47,12 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { const { isNominating, fetchEraStakers } = useStaking() const { maxExposurePageSize } = consts + // Store pending nominator reward total & individual entries. + const [unclaimedRewards, setUnclaimedRewards] = useState({ + total: '0', + entries: [], + }) + // Store active accont's payout state. const [unclaimedPayouts, setUnclaimedPayouts] = useState(null) @@ -184,7 +191,7 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { } // Unclaimed rewards by validator. Record. - const unclaimedRewards: Record = {} + const newUnclaimedRewards: Record = {} // Refer to new `ClaimedRewards` storage item and calculate unclaimed rewards from that and // `exposedPage` stored locally in exposure data. @@ -213,10 +220,10 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { // Add to `unclaimedRewards` if payout page has not yet been claimed. if (exposedPage) { if (!pages.includes(exposedPage)) { - if (unclaimedRewards?.[validator]) { - unclaimedRewards[validator].push(era) + if (newUnclaimedRewards?.[validator]) { + newUnclaimedRewards[validator].push(era) } else { - unclaimedRewards[validator] = [era] + newUnclaimedRewards[validator] = [era] } } } @@ -226,7 +233,7 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { const unclaimedByEra: Record = {} erasToCheck.forEach((era) => { const eraValidators: string[] = [] - Object.entries(unclaimedRewards).forEach(([validator, eras]) => { + Object.entries(newUnclaimedRewards).forEach(([validator, eras]) => { if (eras.includes(era)) { eraValidators.push(validator) } @@ -401,9 +408,10 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { return ( {children} diff --git a/packages/app/src/contexts/Payouts/types.ts b/packages/app/src/contexts/Payouts/types.ts index b10eeb8271..e0e2a4edbf 100644 --- a/packages/app/src/contexts/Payouts/types.ts +++ b/packages/app/src/contexts/Payouts/types.ts @@ -2,11 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only import type { Sync } from '@w3ux/types' +import type { UnclaimedRewards } from 'plugin-staking-api/src/types' export interface PayoutsContextInterface { payoutsSynced: Sync - unclaimedPayouts: UnclaimedPayouts removeEraPayout: (era: string, validator: string) => void + unclaimedRewards: UnclaimedRewards + setUnclaimedRewards: (unclaimedRewards: UnclaimedRewards) => void } // Record diff --git a/packages/app/src/library/Headers/Sync.tsx b/packages/app/src/library/Headers/Sync.tsx index 6eb7fe1a25..5eaec48275 100644 --- a/packages/app/src/library/Headers/Sync.tsx +++ b/packages/app/src/library/Headers/Sync.tsx @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only import { pageFromUri } from '@w3ux/utils' -import { usePayouts } from 'contexts/Payouts' import { useBondedPools } from 'contexts/Pools/BondedPools' import { useTxMeta } from 'contexts/TxMeta' import { useValidators } from 'contexts/Validators/ValidatorEntries' @@ -14,21 +13,9 @@ export const Sync = () => { const { uids } = useTxMeta() const { syncing } = useSyncing() const { pathname } = useLocation() - const { payoutsSynced } = usePayouts() const { validators } = useValidators() const { bondedPools } = useBondedPools() - // Keep syncing if on nominate page and still fetching payouts. - const onNominateSyncing = () => { - if ( - pageFromUri(pathname, 'overview') === 'nominate' && - payoutsSynced !== 'synced' - ) { - return true - } - return false - } - // Keep syncing if on pools page and still fetching bonded pools or pool members. Ignore pool // member sync if Subscan is enabled. const onPoolsSyncing = () => { @@ -54,7 +41,6 @@ export const Sync = () => { const isSyncing = syncing || onPoolsSyncing() || - onNominateSyncing() || onValidatorsSyncing() || uids.filter(({ processing }) => processing === true).length > 0 diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx b/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx index 6d66ea27ec..da4d4430e6 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx @@ -5,10 +5,10 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons' import { planckToUnit } from '@w3ux/utils' import { PayoutStakersByPage } from 'api/tx/payoutStakersByPage' import BigNumber from 'bignumber.js' +import type { AnyApi } from 'common-types' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useNetwork } from 'contexts/Network' import { usePayouts } from 'contexts/Payouts' -import { Subscan } from 'controllers/Subscan' import { useBatchCall } from 'hooks/useBatchCall' import { useSignerWarnings } from 'hooks/useSignerWarnings' import { useSubmitExtrinsic } from 'hooks/useSubmitExtrinsic' @@ -58,19 +58,27 @@ export const Forms = forwardRef( ) || 0 const getCalls = () => { - const calls = payouts?.reduce((acc, { era, paginatedValidators }) => { - if (!paginatedValidators) { + const calls = payouts?.reduce( + (acc: AnyApi[], { era, paginatedValidators }) => { + if (!paginatedValidators.length) { + return acc + } + paginatedValidators.forEach(([page, v]) => { + const tx = new PayoutStakersByPage( + network, + v, + Number(era), + page + ).tx() + if (tx) { + acc.push(tx) + } + }) return acc - } - paginatedValidators.forEach(([page, v]) => { - const tx = new PayoutStakersByPage(network, v, Number(era), page).tx() + }, + [] + ) - if (tx) { - acc.push() - } - }) - return acc - }, []) return calls || [] } @@ -111,7 +119,8 @@ export const Forms = forwardRef( payouts.forEach(({ era }) => { eraPayouts.push(String(era)) }) - Subscan.removeUnclaimedPayouts(activeAccount, eraPayouts) + // TODO: Deduct unclaimed payout value from state value + // Subscan.removeUnclaimedPayouts(activeAccount, eraPayouts) // Deduct from `unclaimedPayouts` in Payouts context. payouts.forEach(({ era, paginatedValidators }) => { diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx b/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx index d4267f5aed..8f4f92c641 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx @@ -11,7 +11,7 @@ import { ItemWrapper } from './Wrappers' export const Item = ({ era, - unclaimedPayout, + validators, setPayouts, setSection, }: ItemProps) => { @@ -20,8 +20,8 @@ export const Item = ({ networkData: { units, unit }, } = useNetwork() - const totalPayout = getTotalPayout(unclaimedPayout) - const numPayouts = Object.values(unclaimedPayout).length + const totalPayout = getTotalPayout(validators) + const numPayouts = validators.length return ( @@ -49,8 +49,8 @@ export const Item = ({ { era, payout: totalPayout.toString(), - paginatedValidators: Object.entries(unclaimedPayout).map( - ([v, [page]]) => [page, v] + paginatedValidators: validators.map( + ({ page, validator }) => [page || 0, validator] ), }, ]) diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Overview.tsx b/packages/app/src/overlay/modals/ClaimPayouts/Overview.tsx index ca9b253da8..8bdfb2ec3f 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Overview.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/Overview.tsx @@ -1,6 +1,7 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only +import BigNumber from 'bignumber.js' import { usePayouts } from 'contexts/Payouts' import { ModalNotes } from 'kits/Overlay/structure/ModalNotes' import { ModalPadding } from 'kits/Overlay/structure/ModalPadding' @@ -9,30 +10,28 @@ import { Fragment, forwardRef } from 'react' import { useTranslation } from 'react-i18next' import { Item } from './Item' import type { OverviewProps } from './types' -import { getTotalPayout } from './Utils' import { ContentWrapper } from './Wrappers' export const Overview = forwardRef( ({ setSection, setPayouts }: OverviewProps, ref: Ref) => { const { t } = useTranslation('modals') - const { unclaimedPayouts } = usePayouts() + const { unclaimedRewards } = usePayouts() return ( - {Object.entries(unclaimedPayouts || {}).map( - ([era, unclaimedPayout], i) => - getTotalPayout(unclaimedPayout).isZero() ? ( - - ) : ( - - ) + {unclaimedRewards.entries.map(({ era, reward, validators }, i) => + new BigNumber(reward).isZero() ? ( + + ) : ( + + ) )}

{t('claimsOnBehalf')}

diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts b/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts index 92b9f1e69a..96202e03d3 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts +++ b/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts @@ -2,13 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only import BigNumber from 'bignumber.js' -import type { EraUnclaimedPayouts } from 'contexts/Payouts/types' +import type { ValidatorUnclaimedReward } from 'plugin-staking-api/src/types' export const getTotalPayout = ( - unclaimedPayout: EraUnclaimedPayouts + validators: ValidatorUnclaimedReward[] ): BigNumber => - Object.values(unclaimedPayout).reduce( - (acc: BigNumber, paginatedValidator: [number, string]) => - acc.plus(paginatedValidator[1]), + validators.reduce( + (acc: BigNumber, { reward }: ValidatorUnclaimedReward) => acc.plus(reward), new BigNumber(0) ) diff --git a/packages/app/src/overlay/modals/ClaimPayouts/index.tsx b/packages/app/src/overlay/modals/ClaimPayouts/index.tsx index 6381d584f2..fd2b2eae42 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/index.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/index.tsx @@ -16,7 +16,7 @@ import type { ActivePayout } from './types' export const ClaimPayouts = () => { const { t } = useTranslation('modals') - const { unclaimedPayouts } = usePayouts() + const { unclaimedRewards } = usePayouts() const { setModalHeight, modalMaxHeight } = useOverlay().modal // Active modal section. @@ -51,7 +51,7 @@ export const ClaimPayouts = () => { // Resize modal on state change. useEffect(() => { onResize() - }, [unclaimedPayouts, section]) + }, [unclaimedRewards.total, section]) // Resize this modal on window resize. useEffect(() => { diff --git a/packages/app/src/overlay/modals/ClaimPayouts/types.ts b/packages/app/src/overlay/modals/ClaimPayouts/types.ts index 7fdfb2816a..91f95f4190 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/types.ts +++ b/packages/app/src/overlay/modals/ClaimPayouts/types.ts @@ -1,11 +1,11 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { EraUnclaimedPayouts } from 'contexts/Payouts/types' +import type { ValidatorUnclaimedReward } from 'plugin-staking-api/src/types' export interface ItemProps { era: string - unclaimedPayout: EraUnclaimedPayouts + validators: ValidatorUnclaimedReward[] setSection: (v: number) => void setPayouts: (payout: ActivePayout[] | null) => void } diff --git a/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx b/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx index fc0e54826e..a49c14c696 100644 --- a/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx +++ b/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only import { faCircleDown } from '@fortawesome/free-solid-svg-icons' -import { minDecimalPlaces } from '@w3ux/utils' -import BigNumber from 'bignumber.js' +import { minDecimalPlaces, planckToUnit } from '@w3ux/utils' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts' @@ -12,7 +11,6 @@ import { usePayouts } from 'contexts/Payouts' import { useOverlay } from 'kits/Overlay/Provider' import { Stat } from 'library/Stat' import { useTranslation } from 'react-i18next' -import { planckToUnitBn } from 'utils' export const UnclaimedPayoutsStatus = ({ dimmed }: { dimmed: boolean }) => { const { t } = useTranslation() @@ -21,33 +19,26 @@ export const UnclaimedPayoutsStatus = ({ dimmed }: { dimmed: boolean }) => { } = useNetwork() const { isReady } = useApi() const { openModal } = useOverlay().modal - const { unclaimedPayouts } = usePayouts() + const { + unclaimedRewards: { total }, + } = usePayouts() const { activeAccount } = useActiveAccounts() const { isReadOnlyAccount } = useImportedAccounts() - const totalUnclaimed = Object.values(unclaimedPayouts || {}).reduce( - (total, paginatedValidators) => - Object.values(paginatedValidators) - .reduce((amount, [, value]) => amount.plus(value), new BigNumber(0)) - .plus(total), - new BigNumber(0) - ) - return ( 0 && - !totalUnclaimed.isZero() + total !== '0' ? [ { title: t('claim', { ns: 'modals' }), diff --git a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx index 2872b53936..e703bdce9a 100644 --- a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx @@ -9,6 +9,7 @@ import { PayoutBar } from 'library/Graphs/PayoutBar' import { PayoutLine } from 'library/Graphs/PayoutLine' import { ApolloProvider, client, useRewards } from 'plugin-staking-api' import type { NominatorReward } from 'plugin-staking-api/src/types' +import { useEffect } from 'react' export const ActiveGraphInner = ({ nominating, @@ -39,7 +40,9 @@ export const ActiveGraphInner = ({ allRewards.filter((reward: NominatorReward) => reward.claimed === false) ?? [] - setLastReward(payouts[0]) + useEffect(() => { + setLastReward(payouts[0]) + }, [JSON.stringify(payouts[0])]) return ( <> diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index aaa9bb6771..39640a5980 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -39,11 +39,11 @@ export const Payouts = ({ page: { key } }: PageProps) => { const { getPoolMembership } = useBalances() const { activeAccount } = useActiveAccounts() - const notStaking = !syncing && inSetup() const membership = getPoolMembership(activeAccount) const nominating = !inSetup() const inPool = membership !== null const staking = nominating || inPool + const notStaking = !syncing && !staking const [payoutsList, setPayoutLists] = useState([]) diff --git a/packages/plugin-staking-api/src/index.tsx b/packages/plugin-staking-api/src/index.tsx index 09defe18d3..12eff909c0 100644 --- a/packages/plugin-staking-api/src/index.tsx +++ b/packages/plugin-staking-api/src/index.tsx @@ -6,5 +6,6 @@ import { ApolloProvider } from '@apollo/client' export * from './Client' export * from './queries/useRewards' export * from './queries/useTokenPrice' +export * from './queries/useUnclaimedRewards' export { ApolloProvider } diff --git a/packages/plugin-staking-api/src/queries/useUnclaimedRewards.tsx b/packages/plugin-staking-api/src/queries/useUnclaimedRewards.tsx new file mode 100644 index 0000000000..acc99e4a87 --- /dev/null +++ b/packages/plugin-staking-api/src/queries/useUnclaimedRewards.tsx @@ -0,0 +1,37 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { gql, useQuery } from '@apollo/client' +import type { UnclaimedRewardsResult } from '../types' + +const QUERY = gql` + query UnclaimedRewards($chain: String!, $who: String!, $fromEra: Int!) { + unclaimedRewards(chain: $chain, who: $who, fromEra: $fromEra) { + total + entries { + era + reward + validators { + page + reward + validator + } + } + } + } +` + +export const useUnclaimedRewards = ({ + chain, + who, + fromEra, +}: { + chain: string + who: string + fromEra: number +}): UnclaimedRewardsResult => { + const { loading, error, data, refetch } = useQuery(QUERY, { + variables: { chain, who, fromEra }, + }) + return { loading, error, data, refetch } +} diff --git a/packages/plugin-staking-api/src/types.ts b/packages/plugin-staking-api/src/types.ts index 58f72d4ea7..05e842ba2f 100644 --- a/packages/plugin-staking-api/src/types.ts +++ b/packages/plugin-staking-api/src/types.ts @@ -34,6 +34,12 @@ export type AllRewardsResult = Query & { } } +export type UnclaimedRewardsResult = Query & { + data: { + unclaimedRewards: UnclaimedRewards + } +} + export interface NominatorReward { era: number reward: number @@ -42,3 +48,19 @@ export interface NominatorReward { validator: string type: string } + +export interface UnclaimedRewards { + total: string + entries: EraUnclaimedReward[] +} +export interface EraUnclaimedReward { + era: number + reward: string + validators: ValidatorUnclaimedReward[] +} + +export interface ValidatorUnclaimedReward { + validator: string + reward: string + page: number | null +} From 59d971cadb10b6eeb9ec9fd0a6663abb303815ce Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 22:39:41 +0700 Subject: [PATCH 11/33] deprecate removeEraPayout --- packages/app/src/contexts/Payouts/defaults.ts | 1 - packages/app/src/contexts/Payouts/index.tsx | 38 ------------------- packages/app/src/contexts/Payouts/types.ts | 1 - .../src/overlay/modals/ClaimPayouts/Forms.tsx | 22 +++++------ 4 files changed, 11 insertions(+), 51 deletions(-) diff --git a/packages/app/src/contexts/Payouts/defaults.ts b/packages/app/src/contexts/Payouts/defaults.ts index 0339fc6ee9..47c44c960d 100644 --- a/packages/app/src/contexts/Payouts/defaults.ts +++ b/packages/app/src/contexts/Payouts/defaults.ts @@ -8,7 +8,6 @@ export const MaxSupportedPayoutEras = 7 export const defaultPayoutsContext: PayoutsContextInterface = { payoutsSynced: 'unsynced', - removeEraPayout: (era, validator) => {}, unclaimedRewards: { total: '0', entries: [], diff --git a/packages/app/src/contexts/Payouts/index.tsx b/packages/app/src/contexts/Payouts/index.tsx index 57100326d9..1af4f41c30 100644 --- a/packages/app/src/contexts/Payouts/index.tsx +++ b/packages/app/src/contexts/Payouts/index.tsx @@ -346,43 +346,6 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { }) } - // Removes a payout from `unclaimedPayouts` based on an era and validator record. - const removeEraPayout = (era: string, validator: string) => { - if (!unclaimedPayouts) { - return - } - - // Delete the payout from local storage. - const localPayouts = localStorage.getItem(`${network}_unclaimed_payouts`) - if (localPayouts && activeAccount) { - const parsed = JSON.parse(localPayouts) - - if (parsed?.[activeAccount]?.[era]?.[validator]) { - delete parsed[activeAccount][era][validator] - - // Delete the era if it has no more payouts. - if (Object.keys(parsed[activeAccount][era]).length === 0) { - delete parsed[activeAccount][era] - } - - // Delete the active account if it has no more eras. - if (Object.keys(parsed[activeAccount]).length === 0) { - delete parsed[activeAccount] - } - } - localStorage.setItem( - `${network}_unclaimed_payouts`, - JSON.stringify(parsed) - ) - } - - // Remove the payout from state. - const newUnclaimedPayouts = { ...unclaimedPayouts } - delete newUnclaimedPayouts[era][validator] - - setUnclaimedPayouts(newUnclaimedPayouts) - } - // Fetch payouts if active account is nominating. useEffect(() => { if (!activeEra.index.isZero()) { @@ -409,7 +372,6 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { void unclaimedRewards: UnclaimedRewards setUnclaimedRewards: (unclaimedRewards: UnclaimedRewards) => void } diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx b/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx index da4d4430e6..1f9ee2e2d3 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx @@ -36,10 +36,10 @@ export const Forms = forwardRef( networkData: { units, unit }, } = useNetwork() const { newBatchCall } = useBatchCall() - const { removeEraPayout } = usePayouts() const { setModalStatus } = useOverlay().modal const { activeAccount } = useActiveAccounts() const { getSignerWarnings } = useSignerWarnings() + const { unclaimedRewards, setUnclaimedRewards } = usePayouts() // Get the total payout amount. const totalPayout = @@ -114,20 +114,20 @@ export const Forms = forwardRef( }, callbackInBlock: () => { if (payouts && activeAccount) { - // Remove Subscan unclaimed payout record(s) if they exist. + // Deduct unclaimed payout value from state value const eraPayouts: string[] = [] payouts.forEach(({ era }) => { eraPayouts.push(String(era)) }) - // TODO: Deduct unclaimed payout value from state value - // Subscan.removeUnclaimedPayouts(activeAccount, eraPayouts) - - // Deduct from `unclaimedPayouts` in Payouts context. - payouts.forEach(({ era, paginatedValidators }) => { - for (const v of paginatedValidators || []) { - removeEraPayout(era, v[1]) - } - }) + const newUnclaimedRewards = { + total: new BigNumber(unclaimedRewards.total) + .minus(totalPayout) + .toString(), + entries: unclaimedRewards.entries.filter( + (entry) => !eraPayouts.includes(String(entry.era)) + ), + } + setUnclaimedRewards(newUnclaimedRewards) } // Reset active form payouts for this modal. setPayouts([]) From 6513c24cb2fd31bf328d8e0e9030be065511442d Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 22:40:24 +0700 Subject: [PATCH 12/33] stop exposing payoutsSynced --- packages/app/src/contexts/Payouts/defaults.ts | 1 - packages/app/src/contexts/Payouts/index.tsx | 1 - packages/app/src/contexts/Payouts/types.ts | 2 -- 3 files changed, 4 deletions(-) diff --git a/packages/app/src/contexts/Payouts/defaults.ts b/packages/app/src/contexts/Payouts/defaults.ts index 47c44c960d..24eb91ea8b 100644 --- a/packages/app/src/contexts/Payouts/defaults.ts +++ b/packages/app/src/contexts/Payouts/defaults.ts @@ -7,7 +7,6 @@ import type { PayoutsContextInterface } from './types' export const MaxSupportedPayoutEras = 7 export const defaultPayoutsContext: PayoutsContextInterface = { - payoutsSynced: 'unsynced', unclaimedRewards: { total: '0', entries: [], diff --git a/packages/app/src/contexts/Payouts/index.tsx b/packages/app/src/contexts/Payouts/index.tsx index 1af4f41c30..0282f53424 100644 --- a/packages/app/src/contexts/Payouts/index.tsx +++ b/packages/app/src/contexts/Payouts/index.tsx @@ -371,7 +371,6 @@ export const PayoutsProvider = ({ children }: { children: ReactNode }) => { return ( void } From 5332be3a17702605adb76509e438fb6165aa4ca5 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 22:55:01 +0700 Subject: [PATCH 13/33] remove legacy reward calculations --- packages/app/src/contexts/Payouts/Utils.ts | 99 ----- packages/app/src/contexts/Payouts/defaults.ts | 2 - packages/app/src/contexts/Payouts/index.tsx | 354 +----------------- packages/app/src/contexts/Payouts/types.ts | 14 - packages/app/src/contexts/Validators/types.ts | 8 + packages/app/src/workers/stakers.ts | 2 +- 6 files changed, 12 insertions(+), 467 deletions(-) delete mode 100644 packages/app/src/contexts/Payouts/Utils.ts diff --git a/packages/app/src/contexts/Payouts/Utils.ts b/packages/app/src/contexts/Payouts/Utils.ts deleted file mode 100644 index 4decdb6fb5..0000000000 --- a/packages/app/src/contexts/Payouts/Utils.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: GPL-3.0-only - -import type { AnyJson } from '@w3ux/types' -import BigNumber from 'bignumber.js' -import type { NetworkId } from 'common-types' -import type { LocalValidatorExposure } from './types' - -// Check if local exposure entry exists for an era. -export const hasLocalEraExposure = ( - network: NetworkId, - era: string, - who: string -) => { - const current = JSON.parse( - localStorage.getItem(`${network}_era_exposures`) || '{}' - ) - return !!current?.[who]?.[era] -} - -// Get local exposure entry for an era. -export const getLocalEraExposure = ( - network: NetworkId, - era: string, - who: string -) => { - const current = JSON.parse( - localStorage.getItem(`${network}_era_exposures`) || '{}' - ) - return current?.[who]?.[era] || [] -} - -// Set local exposure entry for an era. -export const setLocalEraExposure = ( - network: NetworkId, - era: string, - who: string, - exposedValidators: Record | null, - endEra: string -) => { - const current = JSON.parse( - localStorage.getItem(`${network}_era_exposures`) || '{}' - ) - - const whoRemoveStaleEras = Object.fromEntries( - Object.entries(current[who] || {}).filter(([k]: AnyJson) => - new BigNumber(k).isGreaterThanOrEqualTo(endEra) - ) - ) - - localStorage.setItem( - `${network}_era_exposures`, - JSON.stringify({ - ...current, - [who]: { - ...whoRemoveStaleEras, - [era]: exposedValidators, - }, - }) - ) -} - -// Get unclaimed payouts for an account. -export const getLocalUnclaimedPayouts = (network: NetworkId, who: string) => { - const current = JSON.parse( - localStorage.getItem(`${network}_unclaimed_payouts`) || '{}' - ) - return current?.[who] || {} -} - -// Set local unclaimed payouts for an account. -export const setLocalUnclaimedPayouts = ( - network: NetworkId, - era: string, - who: string, - unclaimdPayouts: Record, - endEra: string -) => { - const current = JSON.parse( - localStorage.getItem(`${network}_unclaimed_payouts`) || '{}' - ) - - const whoRemoveStaleEras = Object.fromEntries( - Object.entries(current[who] || {}).filter(([k]: AnyJson) => - new BigNumber(k).isGreaterThanOrEqualTo(endEra) - ) - ) - - localStorage.setItem( - `${network}_unclaimed_payouts`, - JSON.stringify({ - ...current, - [who]: { - ...whoRemoveStaleEras, - [era]: unclaimdPayouts, - }, - }) - ) -} diff --git a/packages/app/src/contexts/Payouts/defaults.ts b/packages/app/src/contexts/Payouts/defaults.ts index 24eb91ea8b..537b4df51c 100644 --- a/packages/app/src/contexts/Payouts/defaults.ts +++ b/packages/app/src/contexts/Payouts/defaults.ts @@ -4,8 +4,6 @@ import type { PayoutsContextInterface } from './types' -export const MaxSupportedPayoutEras = 7 - export const defaultPayoutsContext: PayoutsContextInterface = { unclaimedRewards: { total: '0', diff --git a/packages/app/src/contexts/Payouts/index.tsx b/packages/app/src/contexts/Payouts/index.tsx index 0282f53424..41a337b472 100644 --- a/packages/app/src/contexts/Payouts/index.tsx +++ b/packages/app/src/contexts/Payouts/index.tsx @@ -1,38 +1,11 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { AnyJson, Sync } from '@w3ux/types' -import { setStateWithRef } from '@w3ux/utils' -import { ClaimedRewards } from 'api/query/claimedRewards' -import { ErasRewardPoints } from 'api/query/erasRewardPoints' -import { ErasValidatorReward } from 'api/query/erasValidatorReward' -import { ValidatorPrefs } from 'api/query/validatorPrefs' -import { BondedMulti } from 'api/queryMulti/bondedMulti' -import BigNumber from 'bignumber.js' -import type { AnyApi } from 'common-types' -import { useActiveAccounts } from 'contexts/ActiveAccounts' -import { useApi } from 'contexts/Api' -import { useNetwork } from 'contexts/Network' -import { useStaking } from 'contexts/Staking' import type { UnclaimedRewards } from 'plugin-staking-api/src/types' import type { ReactNode } from 'react' -import { createContext, useContext, useEffect, useRef, useState } from 'react' -import { perbillToPercent } from 'utils' -import Worker from 'workers/stakers?worker' -import { MaxSupportedPayoutEras, defaultPayoutsContext } from './defaults' -import type { - LocalValidatorExposure, - PayoutsContextInterface, - UnclaimedPayouts, -} from './types' -import { - getLocalEraExposure, - hasLocalEraExposure, - setLocalEraExposure, - setLocalUnclaimedPayouts, -} from './Utils' - -const worker = new Worker() +import { createContext, useContext, useState } from 'react' +import { defaultPayoutsContext } from './defaults' +import type { PayoutsContextInterface } from './types' export const PayoutsContext = createContext( defaultPayoutsContext @@ -41,333 +14,12 @@ export const PayoutsContext = createContext( export const usePayouts = () => useContext(PayoutsContext) export const PayoutsProvider = ({ children }: { children: ReactNode }) => { - const { network } = useNetwork() - const { consts, activeEra } = useApi() - const { activeAccount } = useActiveAccounts() - const { isNominating, fetchEraStakers } = useStaking() - const { maxExposurePageSize } = consts - // Store pending nominator reward total & individual entries. const [unclaimedRewards, setUnclaimedRewards] = useState({ total: '0', entries: [], }) - // Store active accont's payout state. - const [unclaimedPayouts, setUnclaimedPayouts] = - useState(null) - - // Track whether payouts have been fetched. - const [payoutsSynced, setPayoutsSynced] = useState('unsynced') - const payoutsSyncedRef = useRef(payoutsSynced) - - // Calculate eras to check for pending payouts. - const getErasInterval = () => { - const startEra = activeEra?.index.minus(1) || new BigNumber(1) - const endEra = BigNumber.max( - startEra.minus(MaxSupportedPayoutEras).plus(1), - 1 - ) - return { - startEra, - endEra, - } - } - - // Determine whether to keep processing a next era, or move onto checking for pending payouts. - const shouldContinueProcessing = async ( - era: BigNumber, - endEra: BigNumber - ) => { - // If there are more exposures to process, check next era. - if (new BigNumber(era).isGreaterThan(endEra)) { - checkEra(new BigNumber(era).minus(1)) - } - // If all exposures have been processed, check for pending payouts. - else if (new BigNumber(era).isEqualTo(endEra)) { - await getUnclaimedPayouts() - setStateWithRef('synced', setPayoutsSynced, payoutsSyncedRef) - } - } - - // Fetch exposure data for an era, and pass the data to the worker to determine the validator the - // active account was backing in that era. - const checkEra = async (era: BigNumber) => { - if (!activeAccount) { - return - } - - // Bypass worker if local exposure data is available. - if (hasLocalEraExposure(network, era.toString(), activeAccount)) { - // Continue processing eras, or move onto reward processing. - shouldContinueProcessing(era, getErasInterval().endEra) - } else { - const exposures = await fetchEraStakers(era.toString()) - worker.postMessage({ - task: 'processEraForExposure', - era: String(era), - who: activeAccount, - networkName: network, - maxExposurePageSize: maxExposurePageSize.toString(), - exitOnExposed: false, - exposures, - }) - } - } - - // Handle worker message on completed exposure check. - worker.onmessage = (message: MessageEvent) => { - if (message) { - // ensure correct task received. - const { data } = message - const { task } = data - if (task !== 'processEraForExposure') { - return - } - - // Exit early if network or account conditions have changed. - const { networkName, who } = data - if (networkName !== network || who !== activeAccount) { - return - } - const { era, exposedValidators } = data - const { endEra } = getErasInterval() - - // Store received era exposure data results in local storage. - setLocalEraExposure( - networkName, - era, - who, - exposedValidators, - endEra.toString() - ) - - // Continue processing eras, or move onto reward processing. - shouldContinueProcessing(era, endEra) - } - } - - // Start pending payout process once exposure data is fetched. - const getUnclaimedPayouts = async () => { - if (!activeAccount) { - return - } - - // Accumulate eras to check, and determine all validator ledgers to fetch from exposures. - const erasValidators = [] - const { startEra, endEra } = getErasInterval() - let erasToCheck: string[] = [] - let currentEra = startEra - while (currentEra.isGreaterThanOrEqualTo(endEra)) { - const validators = Object.keys( - getLocalEraExposure(network, currentEra.toString(), activeAccount) - ) - erasValidators.push(...validators) - erasToCheck.push(currentEra.toString()) - currentEra = currentEra.minus(1) - } - - // Ensure no validator duplicates. - const uniqueValidators = [...new Set(erasValidators)] - - // Ensure `erasToCheck` is in order, highest first. - erasToCheck = erasToCheck.sort((a: string, b: string) => - new BigNumber(b).minus(a).toNumber() - ) - - // Fetch controllers in order to query ledgers. - const uniqueValidatorsMulti: [string][] = uniqueValidators.map((v) => [v]) - const bondedResultsMulti = await new BondedMulti( - network, - uniqueValidatorsMulti - ).fetch() - - const validatorControllers: Record = {} - for (let i = 0; i < bondedResultsMulti.length; i++) { - const ctlr = bondedResultsMulti[i] || null - if (ctlr) { - validatorControllers[uniqueValidators[i]] = ctlr - } - } - - // Unclaimed rewards by validator. Record. - const newUnclaimedRewards: Record = {} - - // Refer to new `ClaimedRewards` storage item and calculate unclaimed rewards from that and - // `exposedPage` stored locally in exposure data. - - // Accumulate calls to fetch unclaimed rewards for each era for all validators. - const unclaimedRewardsEntries = erasToCheck - .map((era) => uniqueValidators.map((v) => [era, v])) - .flat() - - const results = await Promise.all( - unclaimedRewardsEntries.map(([era, v]) => - new ClaimedRewards(network, Number(era), v).fetch() - ) - ) - - for (let i = 0; i < results.length; i++) { - const pages = results[i] || [] - const era = unclaimedRewardsEntries[i][0] - const validator = unclaimedRewardsEntries[i][1] - const exposure = getLocalEraExposure(network, era, activeAccount) - const exposedPage = - exposure?.[validator]?.exposedPage !== undefined - ? Number(exposure[validator].exposedPage) - : undefined - - // Add to `unclaimedRewards` if payout page has not yet been claimed. - if (exposedPage) { - if (!pages.includes(exposedPage)) { - if (newUnclaimedRewards?.[validator]) { - newUnclaimedRewards[validator].push(era) - } else { - newUnclaimedRewards[validator] = [era] - } - } - } - } - - // Reformat unclaimed rewards to be { era: validators[] }. - const unclaimedByEra: Record = {} - erasToCheck.forEach((era) => { - const eraValidators: string[] = [] - Object.entries(newUnclaimedRewards).forEach(([validator, eras]) => { - if (eras.includes(era)) { - eraValidators.push(validator) - } - }) - if (eraValidators.length > 0) { - unclaimedByEra[era] = eraValidators - } - }) - - // Accumulate calls needed to fetch data to calculate rewards. - const calls: AnyApi[] = [] - Object.entries(unclaimedByEra).forEach(([era, validators]) => { - if (validators.length > 0) { - calls.push( - Promise.all([ - new ErasValidatorReward(network, Number(era)).fetch(), - new ErasRewardPoints(network, Number(era)).fetch(), - ...validators.map((validator: AnyJson) => - new ValidatorPrefs(network, Number(era), validator).fetch() - ), - ]) - ) - } - }) - - // Iterate calls and determine unclaimed payouts. - // `unclaimed`: Record>. - const unclaimed: UnclaimedPayouts = {} - let i = 0 - for (const [reward, eraRewardPoints, ...prefs] of await Promise.all( - calls - )) { - const era = Object.keys(unclaimedByEra)[i] - const eraTotalPayout = new BigNumber(reward.toString()) - const unclaimedValidators = unclaimedByEra[era] - - let j = 0 - for (const pref of prefs) { - const eraValidatorPrefs = { - commission: pref.commission, - blocked: pref.blocked, - } - const commission = new BigNumber( - perbillToPercent(eraValidatorPrefs.commission) - ) - - // Get validator from era exposure data. Falls back no null if it cannot be found. - const validator = unclaimedValidators?.[j] || '' - - const localExposed: LocalValidatorExposure | null = getLocalEraExposure( - network, - era, - activeAccount - )?.[validator] - - const staked = new BigNumber(localExposed?.staked || '0') - const total = new BigNumber(localExposed?.total || '0') - const isValidator = localExposed?.isValidator || false - const exposedPage = localExposed?.exposedPage || 0 - - // Calculate the validator's share of total era payout. - const totalRewardPoints = new BigNumber( - eraRewardPoints.total.toString() - ) - const validatorRewardPoints = new BigNumber( - eraRewardPoints.individual.find( - ([v]: [string]) => v === validator - )?.[1] || '0' - ) - - const avail = eraTotalPayout - .multipliedBy(validatorRewardPoints) - .dividedBy(totalRewardPoints) - - const valCut = commission.multipliedBy(0.01).multipliedBy(avail) - - const unclaimedPayout = total.isZero() - ? new BigNumber(0) - : avail - .minus(valCut) - .multipliedBy(staked) - .dividedBy(total) - .plus(isValidator ? valCut : 0) - .integerValue(BigNumber.ROUND_DOWN) - - if (!unclaimedPayout.isZero()) { - unclaimed[era] = { - ...unclaimed[era], - [validator]: [exposedPage, unclaimedPayout.toString()], - } - j++ - } - } - - // This is not currently useful for preventing re-syncing. Need to know the eras that have - // been claimed already and remove them from `erasToCheck`. - setLocalUnclaimedPayouts( - network, - era, - activeAccount, - unclaimed[era], - endEra.toString() - ) - i++ - } - - setUnclaimedPayouts({ - ...unclaimedPayouts, - ...unclaimed, - }) - } - - // Fetch payouts if active account is nominating. - useEffect(() => { - if (!activeEra.index.isZero()) { - if (!isNominating()) { - setStateWithRef('synced', setPayoutsSynced, payoutsSyncedRef) - } else if ( - unclaimedPayouts === null && - payoutsSyncedRef.current !== 'syncing' - ) { - setStateWithRef('syncing', setPayoutsSynced, payoutsSyncedRef) - // Start checking eras for exposures, starting with the previous one. - checkEra(activeEra.index.minus(1)) - } - } - }, [unclaimedPayouts, isNominating(), activeEra, payoutsSynced]) - - // Clear payout state on network / active account change. - useEffect(() => { - setUnclaimedPayouts(null) - setStateWithRef('unsynced', setPayoutsSynced, payoutsSyncedRef) - }, [network, activeAccount]) - return ( void } - -// Record -export type UnclaimedPayouts = Record | null - -// Record -export type EraUnclaimedPayouts = Record - -export interface LocalValidatorExposure { - staked: string - total: string - share: string - isValidator: boolean - exposedPage: number -} diff --git a/packages/app/src/contexts/Validators/types.ts b/packages/app/src/contexts/Validators/types.ts index 1212a6bae8..77c1711869 100644 --- a/packages/app/src/contexts/Validators/types.ts +++ b/packages/app/src/contexts/Validators/types.ts @@ -84,3 +84,11 @@ export interface ValidatorEraPointHistory { rank?: number quartile?: number } + +export interface LocalValidatorExposure { + staked: string + total: string + share: string + isValidator: boolean + exposedPage: number +} diff --git a/packages/app/src/workers/stakers.ts b/packages/app/src/workers/stakers.ts index 8fbb704e12..761b8164fc 100644 --- a/packages/app/src/workers/stakers.ts +++ b/packages/app/src/workers/stakers.ts @@ -4,12 +4,12 @@ import type { AnyJson } from '@w3ux/types' import { planckToUnit, rmCommas } from '@w3ux/utils' import BigNumber from 'bignumber.js' -import type { LocalValidatorExposure } from 'contexts/Payouts/types' import type { ActiveAccountStaker, ExposureOther, Staker, } from 'contexts/Staking/types' +import type { LocalValidatorExposure } from 'contexts/Validators/types' import type { ProcessEraForExposureArgs, ProcessExposuresArgs } from './types' // eslint-disable-next-line @typescript-eslint/no-explicit-any From 0fc09e4326d73d5c99e1eb3c12487ac83fa6ab75 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 12 Dec 2024 23:00:20 +0700 Subject: [PATCH 14/33] fix --- .../pages/Overview/Payouts/InactiveGraph.tsx | 44 ++++++++++++------- .../app/src/pages/Overview/Payouts/index.tsx | 8 +--- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx index ac7a0ff9d7..034a806b28 100644 --- a/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx +++ b/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx @@ -3,25 +3,37 @@ import { PayoutBar } from 'library/Graphs/PayoutBar' import { PayoutLine } from 'library/Graphs/PayoutLine' +import type { NominatorReward } from 'plugin-staking-api/src/types' +import { useEffect } from 'react' -export const InactiveGraph = () => ( - <> - -
- void +}) => { + useEffect(() => { + setLastReward(undefined) + }, []) + + return ( + <> + -
- -) +
+ +
+ + ) +} diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index 2e00e4cea5..1ed863162f 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -19,7 +19,7 @@ import { GraphWrapper } from 'library/Graphs/Wrapper' import { StatusLabel } from 'library/StatusLabel' import { DefaultLocale, locales } from 'locales' import type { NominatorReward } from 'plugin-staking-api/src/types' -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { planckToUnitBn } from 'utils' import { ActiveGraph } from './ActiveGraph' @@ -69,10 +69,6 @@ export const Payouts = () => { } } - useEffect(() => { - setLastReward(undefined) - }, [activeAccount]) - return ( <> @@ -131,7 +127,7 @@ export const Payouts = () => { setLastReward={setLastReward} /> ) : ( - + )}

From 3233d1601d6e321cf1bb68c514360fe079720e38 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 17:48:48 +0700 Subject: [PATCH 15/33] use full amount --- packages/app/src/library/Graphs/PayoutBar.tsx | 4 ++-- packages/app/src/library/Graphs/PayoutLine.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/library/Graphs/PayoutBar.tsx b/packages/app/src/library/Graphs/PayoutBar.tsx index df04aa7f9d..ae35f1d41e 100644 --- a/packages/app/src/library/Graphs/PayoutBar.tsx +++ b/packages/app/src/library/Graphs/PayoutBar.tsx @@ -88,7 +88,7 @@ export const PayoutBar = ({ { order: 1, label: t('payout'), - data: graphPayouts.map((item: AnyApi) => item.reward.toFixed(5)), + data: graphPayouts.map((item: AnyApi) => item.reward), borderColor: colorPayouts, backgroundColor: colorPayouts, pointRadius: 0, @@ -97,7 +97,7 @@ export const PayoutBar = ({ { order: 2, label: t('poolClaim'), - data: graphPoolClaims.map((item: AnyApi) => item.reward.toFixed(5)), + data: graphPoolClaims.map((item: AnyApi) => item.reward), borderColor: colorPoolClaims, backgroundColor: colorPoolClaims, pointRadius: 0, diff --git a/packages/app/src/library/Graphs/PayoutLine.tsx b/packages/app/src/library/Graphs/PayoutLine.tsx index f257cf2e41..a0dccba8c1 100644 --- a/packages/app/src/library/Graphs/PayoutLine.tsx +++ b/packages/app/src/library/Graphs/PayoutLine.tsx @@ -145,7 +145,7 @@ export const PayoutLine = ({ datasets: [ { label: t('payout'), - data: combinedPayouts.map((item: AnyApi) => item.reward.toFixed(5)), + data: combinedPayouts.map((item: AnyApi) => item.reward), borderColor: color, pointStyle: undefined, pointRadius: 0, From 004f6f54e70f612eda817197376abe2c0923c7c6 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 17:53:04 +0700 Subject: [PATCH 16/33] fetch payouts --- packages/app/src/contexts/Plugins/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/app/src/contexts/Plugins/index.tsx b/packages/app/src/contexts/Plugins/index.tsx index 275a6692d0..8edc2f330d 100644 --- a/packages/app/src/contexts/Plugins/index.tsx +++ b/packages/app/src/contexts/Plugins/index.tsx @@ -53,6 +53,9 @@ export const PluginsProvider = ({ children }: { children: ReactNode }) => { Subscan.resetData() } else if (isReady && !activeEra.index.isZero()) { Subscan.network = network + if (activeAccount) { + Subscan.handleFetchPayouts(activeAccount) + } } }, [plugins.includes('subscan'), isReady, network, activeAccount, activeEra]) From c379788e9bec26eb68ba68db6490f17dbff13d38 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:13:42 +0700 Subject: [PATCH 17/33] tidy up router --- packages/app/src/Router.tsx | 38 +++---------------- .../app/src/hooks/useAccountFromUrl/index.tsx | 37 ++++++++++++++++++ 2 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 packages/app/src/hooks/useAccountFromUrl/index.tsx diff --git a/packages/app/src/Router.tsx b/packages/app/src/Router.tsx index 86ba7ad97a..47cbff3a6e 100644 --- a/packages/app/src/Router.tsx +++ b/packages/app/src/Router.tsx @@ -1,16 +1,12 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { extractUrlValue } from '@w3ux/utils' import { PagesConfig } from 'config/pages' -import { useActiveAccounts } from 'contexts/ActiveAccounts' -import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts' -import { useOtherAccounts } from 'contexts/Connect/OtherAccounts' import { useNetwork } from 'contexts/Network' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' import { useUi } from 'contexts/UI' -import { Notifications } from 'controllers/Notifications' +import { useAccountFromUrl } from 'hooks/useAccountFromUrl' import { ErrorFallbackApp, ErrorFallbackRoutes } from 'library/ErrorBoundary' import { Headers } from 'library/Headers' import { Help } from 'library/Help' @@ -25,7 +21,6 @@ import { Tooltip } from 'library/Tooltip' import { Overlays } from 'overlay' import { useEffect, useRef } from 'react' import { ErrorBoundary } from 'react-error-boundary' -import { useTranslation } from 'react-i18next' import { HashRouter, Navigate, @@ -37,19 +32,13 @@ import { StakingApi } from 'StakingApi' import { Body, Main } from 'ui-structure' const RouterInner = () => { - const { t } = useTranslation() const { network } = useNetwork() const { inSetup } = useStaking() const { pathname } = useLocation() const { setContainerRefs } = useUi() const { pluginEnabled } = usePlugins() - const { accounts } = useImportedAccounts() - const { accountsInitialised } = useOtherAccounts() - const { activeAccount, setActiveAccount } = useActiveAccounts() - const nominating = !inSetup() - const stakingApiEnabled = pluginEnabled('staking_api') - // References to outer container. + // References to outer container const mainInterfaceRef = useRef(null) // Scroll to top of the window on every page change or network change @@ -64,29 +53,12 @@ const RouterInner = () => { }) }, []) - // Open default account modal if url var present and accounts initialised - useEffect(() => { - if (accountsInitialised) { - const aUrl = extractUrlValue('a') - if (aUrl) { - const account = accounts.find((a) => a.address === aUrl) - if (account && aUrl !== activeAccount) { - setActiveAccount(account.address || null) - - Notifications.emit({ - title: t('accountConnected', { ns: 'library' }), - subtitle: `${t('connectedTo', { ns: 'library' })} ${ - account.name || aUrl - }.`, - }) - } - } - } - }, [accountsInitialised]) + // Support active account from url + useAccountFromUrl() return ( - {stakingApiEnabled && nominating && } + {pluginEnabled('staking_api') && !inSetup() && } diff --git a/packages/app/src/hooks/useAccountFromUrl/index.tsx b/packages/app/src/hooks/useAccountFromUrl/index.tsx new file mode 100644 index 0000000000..a16ed8863b --- /dev/null +++ b/packages/app/src/hooks/useAccountFromUrl/index.tsx @@ -0,0 +1,37 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { extractUrlValue } from '@w3ux/utils' +import { useActiveAccounts } from 'contexts/ActiveAccounts' +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts' +import { useOtherAccounts } from 'contexts/Connect/OtherAccounts' +import { Notifications } from 'controllers/Notifications' +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' + +export const useAccountFromUrl = () => { + const { t } = useTranslation() + const { accounts } = useImportedAccounts() + const { accountsInitialised } = useOtherAccounts() + const { activeAccount, setActiveAccount } = useActiveAccounts() + + // Set active account if url var present and accounts initialised + useEffect(() => { + if (accountsInitialised) { + const aUrl = extractUrlValue('a') + if (aUrl) { + const account = accounts.find((a) => a.address === aUrl) + if (account && aUrl !== activeAccount) { + setActiveAccount(account.address || null) + + Notifications.emit({ + title: t('accountConnected', { ns: 'library' }), + subtitle: `${t('connectedTo', { ns: 'library' })} ${ + account.name || aUrl + }.`, + }) + } + } + } + }, [accountsInitialised]) +} From 62108792ea795411479684af82b06054968dfdbd Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:17:31 +0700 Subject: [PATCH 18/33] StakingApi polish --- packages/app/src/StakingApi.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/app/src/StakingApi.tsx b/packages/app/src/StakingApi.tsx index e247280e22..178ed02a8a 100644 --- a/packages/app/src/StakingApi.tsx +++ b/packages/app/src/StakingApi.tsx @@ -8,13 +8,12 @@ import { usePayouts } from 'contexts/Payouts' import { ApolloProvider, client, useUnclaimedRewards } from 'plugin-staking-api' import { useEffect } from 'react' -export const StakingApiInner = () => { +const Inner = () => { const { activeEra } = useApi() const { network } = useNetwork() const { setUnclaimedRewards } = usePayouts() const { activeAccount } = useActiveAccounts() - // Fetch and store unclaimed rewards const { data, loading, error } = useUnclaimedRewards({ chain: network, who: activeAccount || '', @@ -32,6 +31,6 @@ export const StakingApiInner = () => { export const StakingApi = () => ( - + ) From f17dfa018c4db806c2ca53cbc368089b758ec438 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:19:21 +0700 Subject: [PATCH 19/33] active account never null --- packages/app/src/Router.tsx | 6 +++++- packages/app/src/StakingApi.tsx | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/app/src/Router.tsx b/packages/app/src/Router.tsx index 47cbff3a6e..4ddb988a83 100644 --- a/packages/app/src/Router.tsx +++ b/packages/app/src/Router.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only import { PagesConfig } from 'config/pages' +import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useNetwork } from 'contexts/Network' import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' @@ -37,6 +38,7 @@ const RouterInner = () => { const { pathname } = useLocation() const { setContainerRefs } = useUi() const { pluginEnabled } = usePlugins() + const { activeAccount } = useActiveAccounts() // References to outer container const mainInterfaceRef = useRef(null) @@ -58,7 +60,9 @@ const RouterInner = () => { return ( - {pluginEnabled('staking_api') && !inSetup() && } + {pluginEnabled('staking_api') && !inSetup() && activeAccount && ( + + )} diff --git a/packages/app/src/StakingApi.tsx b/packages/app/src/StakingApi.tsx index 178ed02a8a..fde0b032ac 100644 --- a/packages/app/src/StakingApi.tsx +++ b/packages/app/src/StakingApi.tsx @@ -1,22 +1,24 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { useNetwork } from 'contexts/Network' import { usePayouts } from 'contexts/Payouts' import { ApolloProvider, client, useUnclaimedRewards } from 'plugin-staking-api' import { useEffect } from 'react' -const Inner = () => { +interface Props { + activeAccount: string +} + +const Inner = ({ activeAccount }: Props) => { const { activeEra } = useApi() const { network } = useNetwork() const { setUnclaimedRewards } = usePayouts() - const { activeAccount } = useActiveAccounts() const { data, loading, error } = useUnclaimedRewards({ chain: network, - who: activeAccount || '', + who: activeAccount, fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), }) @@ -29,8 +31,8 @@ const Inner = () => { return null } -export const StakingApi = () => ( +export const StakingApi = (props: Props) => ( - + ) From 34150bbf73d6156e4010c6182611b5777965ea56 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:20:29 +0700 Subject: [PATCH 20/33] use defaults --- packages/app/src/contexts/Payouts/defaults.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/app/src/contexts/Payouts/defaults.ts b/packages/app/src/contexts/Payouts/defaults.ts index 537b4df51c..6d44d53df5 100644 --- a/packages/app/src/contexts/Payouts/defaults.ts +++ b/packages/app/src/contexts/Payouts/defaults.ts @@ -4,15 +4,12 @@ import type { PayoutsContextInterface } from './types' -export const defaultPayoutsContext: PayoutsContextInterface = { - unclaimedRewards: { - total: '0', - entries: [], - }, - setUnclaimedRewards: (unclaimedRewards) => {}, -} - export const defaultUnclaimedRewards = { total: '0', entries: [], } + +export const defaultPayoutsContext: PayoutsContextInterface = { + unclaimedRewards: defaultUnclaimedRewards, + setUnclaimedRewards: (unclaimedRewards) => {}, +} From 8592b64bdc534dd6560fa1cb7cf464ad39080e86 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:27:00 +0700 Subject: [PATCH 21/33] improve staking apii exports --- packages/app/src/contexts/Payouts/index.tsx | 2 +- packages/app/src/contexts/Payouts/types.ts | 2 +- packages/app/src/controllers/Subscan/types.ts | 2 +- packages/app/src/hooks/useSubscanData/index.tsx | 2 +- packages/app/src/library/Graphs/Utils.ts | 2 +- packages/app/src/library/Graphs/types.ts | 2 +- packages/app/src/overlay/modals/ClaimPayouts/Utils.ts | 2 +- packages/app/src/overlay/modals/ClaimPayouts/types.ts | 2 +- packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx | 2 +- packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx | 2 +- packages/app/src/pages/Overview/Payouts/index.tsx | 2 +- packages/app/src/pages/Payouts/ActiveGraph.tsx | 2 +- packages/plugin-staking-api/package.json | 4 ++++ 13 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/app/src/contexts/Payouts/index.tsx b/packages/app/src/contexts/Payouts/index.tsx index 41a337b472..973bf416be 100644 --- a/packages/app/src/contexts/Payouts/index.tsx +++ b/packages/app/src/contexts/Payouts/index.tsx @@ -1,7 +1,7 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { UnclaimedRewards } from 'plugin-staking-api/src/types' +import type { UnclaimedRewards } from 'plugin-staking-api/types' import type { ReactNode } from 'react' import { createContext, useContext, useState } from 'react' import { defaultPayoutsContext } from './defaults' diff --git a/packages/app/src/contexts/Payouts/types.ts b/packages/app/src/contexts/Payouts/types.ts index b0fa46cd0c..9543d84938 100644 --- a/packages/app/src/contexts/Payouts/types.ts +++ b/packages/app/src/contexts/Payouts/types.ts @@ -1,7 +1,7 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { UnclaimedRewards } from 'plugin-staking-api/src/types' +import type { UnclaimedRewards } from 'plugin-staking-api/types' export interface PayoutsContextInterface { unclaimedRewards: UnclaimedRewards diff --git a/packages/app/src/controllers/Subscan/types.ts b/packages/app/src/controllers/Subscan/types.ts index 44c7fd9093..026c1a2795 100644 --- a/packages/app/src/controllers/Subscan/types.ts +++ b/packages/app/src/controllers/Subscan/types.ts @@ -1,7 +1,7 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' export type PayoutType = 'payouts' | 'unclaimedPayouts' | 'poolClaims' diff --git a/packages/app/src/hooks/useSubscanData/index.tsx b/packages/app/src/hooks/useSubscanData/index.tsx index 09f570f952..dafba2e24d 100644 --- a/packages/app/src/hooks/useSubscanData/index.tsx +++ b/packages/app/src/hooks/useSubscanData/index.tsx @@ -7,7 +7,7 @@ import { usePlugins } from 'contexts/Plugins' import { Subscan } from 'controllers/Subscan' import type { PayoutType, SubscanPoolClaim } from 'controllers/Subscan/types' import { isCustomEvent } from 'controllers/utils' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' import { useEffect, useRef, useState } from 'react' import { useEventListener } from 'usehooks-ts' import { useErasToTimeLeft } from '../useErasToTimeLeft' diff --git a/packages/app/src/library/Graphs/Utils.ts b/packages/app/src/library/Graphs/Utils.ts index c742f825c3..c1d8c75a07 100644 --- a/packages/app/src/library/Graphs/Utils.ts +++ b/packages/app/src/library/Graphs/Utils.ts @@ -17,7 +17,7 @@ import { startOfDay, subDays, } from 'date-fns' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' import { planckToUnitBn } from 'utils' import type { PayoutDayCursor } from './types' diff --git a/packages/app/src/library/Graphs/types.ts b/packages/app/src/library/Graphs/types.ts index f88c6cbcb3..503a76066d 100644 --- a/packages/app/src/library/Graphs/types.ts +++ b/packages/app/src/library/Graphs/types.ts @@ -4,7 +4,7 @@ import type BigNumber from 'bignumber.js' import type { AnyApi } from 'common-types' import type { SubscanPoolClaim } from 'controllers/Subscan/types' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' export interface BondedProps { active: BigNumber diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts b/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts index 96202e03d3..b757144e87 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts +++ b/packages/app/src/overlay/modals/ClaimPayouts/Utils.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only import BigNumber from 'bignumber.js' -import type { ValidatorUnclaimedReward } from 'plugin-staking-api/src/types' +import type { ValidatorUnclaimedReward } from 'plugin-staking-api/types' export const getTotalPayout = ( validators: ValidatorUnclaimedReward[] diff --git a/packages/app/src/overlay/modals/ClaimPayouts/types.ts b/packages/app/src/overlay/modals/ClaimPayouts/types.ts index 91f95f4190..d665b7f9a6 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/types.ts +++ b/packages/app/src/overlay/modals/ClaimPayouts/types.ts @@ -1,7 +1,7 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { ValidatorUnclaimedReward } from 'plugin-staking-api/src/types' +import type { ValidatorUnclaimedReward } from 'plugin-staking-api/types' export interface ItemProps { era: string diff --git a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx index e703bdce9a..77db9a06e7 100644 --- a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx @@ -8,7 +8,7 @@ import { useSubscanData } from 'hooks/useSubscanData' import { PayoutBar } from 'library/Graphs/PayoutBar' import { PayoutLine } from 'library/Graphs/PayoutLine' import { ApolloProvider, client, useRewards } from 'plugin-staking-api' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' import { useEffect } from 'react' export const ActiveGraphInner = ({ diff --git a/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx index 034a806b28..f6cf38b401 100644 --- a/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx +++ b/packages/app/src/pages/Overview/Payouts/InactiveGraph.tsx @@ -3,7 +3,7 @@ import { PayoutBar } from 'library/Graphs/PayoutBar' import { PayoutLine } from 'library/Graphs/PayoutLine' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' import { useEffect } from 'react' export const InactiveGraph = ({ diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index 1ed863162f..f86cde4337 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -18,7 +18,7 @@ import { formatSize } from 'library/Graphs/Utils' import { GraphWrapper } from 'library/Graphs/Wrapper' import { StatusLabel } from 'library/StatusLabel' import { DefaultLocale, locales } from 'locales' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { planckToUnitBn } from 'utils' diff --git a/packages/app/src/pages/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Payouts/ActiveGraph.tsx index da456fadb4..d169048104 100644 --- a/packages/app/src/pages/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Payouts/ActiveGraph.tsx @@ -12,7 +12,7 @@ import { PayoutBar } from 'library/Graphs/PayoutBar' import { PayoutLine } from 'library/Graphs/PayoutLine' import { removeNonZeroAmountAndSort } from 'library/Graphs/Utils' import { ApolloProvider, client, useRewards } from 'plugin-staking-api' -import type { NominatorReward } from 'plugin-staking-api/src/types' +import type { NominatorReward } from 'plugin-staking-api/types' import { useEffect } from 'react' export const ActiveGraphInner = ({ diff --git a/packages/plugin-staking-api/package.json b/packages/plugin-staking-api/package.json index 913cb5ee61..190b0bad2e 100644 --- a/packages/plugin-staking-api/package.json +++ b/packages/plugin-staking-api/package.json @@ -8,6 +8,10 @@ "clear": "rm -rf build tsconfig.tsbuildinfo dist", "reset": "yarn run clear && rm -rf node_modules yarn.lock && yarn" }, + "exports": { + ".": "./src/index.tsx", + "./types": "./src/types.ts" + }, "dependencies": { "@apollo/client": "^3.11.10", "graphql": "^16.9.0" From 32414ee37a92b794cc5310f924860b9ce9dba792 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:35:27 +0700 Subject: [PATCH 22/33] remove deprecated subscan boilerplate --- packages/app/src/controllers/Subscan/index.ts | 29 +++++---------- packages/app/src/controllers/Subscan/types.ts | 37 +++---------------- .../app/src/hooks/useSubscanData/index.tsx | 35 +----------------- 3 files changed, 18 insertions(+), 83 deletions(-) diff --git a/packages/app/src/controllers/Subscan/index.ts b/packages/app/src/controllers/Subscan/index.ts index 22716c5ef9..5845dc0ea0 100644 --- a/packages/app/src/controllers/Subscan/index.ts +++ b/packages/app/src/controllers/Subscan/index.ts @@ -13,37 +13,31 @@ import type { } from './types' export class Subscan { - // List of endpoints to be used for Subscan API calls. + // List of endpoints to be used for Subscan API calls static ENDPOINTS = { eraStat: '/api/scan/staking/era_stat', poolMembers: '/api/scan/nomination_pool/pool/members', poolRewards: '/api/scan/nomination_pool/rewards', - rewardSlash: '/api/v2/scan/account/reward_slash', } - // Total amount of requests that can be made in 1 second. - static TOTAL_REQUESTS_PER_SECOND = 5 - - // The network to use for Subscan API calls. + // The network to use for Subscan API calls static network: string - // Subscan payout data, keyed by address. + // Subscan payout data, keyed by address static payoutData: Record = {} - // Subscan pool data, keyed by `---...`. + // Subscan pool data, keyed by `---...` static poolData: Record = {} - // Subscan era points data, keyed by `-
-`. + // Subscan era points data, keyed by `-
-` static eraPointsData: Record = {} - // Set the network to use for Subscan API calls. - // - // Effects the endpoint being used. Should be updated on network change in the UI. + // Set the network to use for Subscan API calls set network(network: string) { Subscan.network = network } - // Handle fetching the various types of payout and set state in one render. + // Handle fetching pool claims and set state in one render static handleFetchPayouts = async (address: string): Promise => { try { if (!this.payoutData[address]) { @@ -51,7 +45,6 @@ export class Subscan { this.payoutData[address] = { poolClaims, } - document.dispatchEvent( new CustomEvent('subscan-data-updated', { detail: { @@ -61,7 +54,7 @@ export class Subscan { ) } } catch (e) { - // Silently fail request. + // Silently fail request } } @@ -78,7 +71,7 @@ export class Subscan { if (!result?.list) { return [] } - // Remove claims with a `block_timestamp`. + // Remove claims with a `block_timestamp` const poolClaims = result.list .filter((l: SubscanPoolClaimRaw) => l.block_timestamp !== 0) .map((l: SubscanPoolClaimRaw) => ({ @@ -87,15 +80,13 @@ export class Subscan { timestamp: l.block_timestamp, type: 'pool', })) - return poolClaims } catch (e) { - // Silently fail request and return empty record. return [] } } - // Fetch a page of pool members from Subscan. + // Fetch a page of pool members from Subscan static fetchPoolMembers = async ( poolId: number, page: number diff --git a/packages/app/src/controllers/Subscan/types.ts b/packages/app/src/controllers/Subscan/types.ts index 026c1a2795..455889aadf 100644 --- a/packages/app/src/controllers/Subscan/types.ts +++ b/packages/app/src/controllers/Subscan/types.ts @@ -3,35 +3,27 @@ import type { NominatorReward } from 'plugin-staking-api/types' -export type PayoutType = 'payouts' | 'unclaimedPayouts' | 'poolClaims' +export type PayoutType = 'poolClaims' export type SubscanData = Partial> export interface SubscanPayoutData { - payouts: SubscanPayout[] - unclaimedPayouts: SubscanPayout[] poolClaims: SubscanPoolClaim[] } export type PayoutsAndClaims = (NominatorReward | SubscanPoolClaim)[] export type SubscanRequestBody = - | RewardSlashRequestBody - | RewardRewardsRequestBody - | RewardMembersRequestBody + | PoolRewardsRequestBody + | PoolMembersRequestBody | PoolDetailsRequestBody -export type RewardSlashRequestBody = SubscanRequestPagination & { - address: string - is_stash: boolean -} - -export type RewardRewardsRequestBody = SubscanRequestPagination & { +export type PoolRewardsRequestBody = SubscanRequestPagination & { address: string claimed_filter?: 'claimed' | 'unclaimed' } -export type RewardMembersRequestBody = SubscanRequestPagination & { +export type PoolMembersRequestBody = SubscanRequestPagination & { pool_id: number } @@ -44,10 +36,7 @@ export interface SubscanRequestPagination { page: number } -export type SubscanResult = - | NominatorReward[] - | SubscanPoolClaim[] - | SubscanPoolMember[] +export type SubscanResult = SubscanPoolClaim[] | SubscanPoolMember[] export interface SubscanPoolClaimBase { account_display: { @@ -75,20 +64,6 @@ export type SubscanPoolClaim = SubscanPoolClaimBase & { timestamp: number } -export interface SubscanPayout { - era: number - stash: string - account: string - validator_stash: string - amount: string - block_timestamp: number - event_index: string - module_id: string - event_id: string - extrinsic_index: string - invalid_era: boolean -} - export interface SubscanPoolMember { pool_id: number bonded: string diff --git a/packages/app/src/hooks/useSubscanData/index.tsx b/packages/app/src/hooks/useSubscanData/index.tsx index dafba2e24d..eee533903f 100644 --- a/packages/app/src/hooks/useSubscanData/index.tsx +++ b/packages/app/src/hooks/useSubscanData/index.tsx @@ -18,14 +18,6 @@ export const useSubscanData = () => { const { erasToSeconds } = useErasToTimeLeft() const { activeAccount } = useActiveAccounts() - // Store payouts data for the active account. - const [payouts, setPayouts] = useState([]) - - // Store unclaimed payouts data for the active account. - const [unclaimedPayouts, setUnclaimedPayouts] = useState( - [] - ) - // Store pool claims data for the active account. const [poolClaims, setPoolClaims] = useState([]) @@ -36,20 +28,6 @@ export const useSubscanData = () => { if (isCustomEvent(e) && pluginEnabled('subscan') && activeAccount) { const { keys: receivedKeys }: { keys: PayoutType[] } = e.detail - if (receivedKeys.includes('payouts')) { - setPayouts( - (Subscan.payoutData[activeAccount]?.['payouts'] || - []) as NominatorReward[] - ) - } - - if (receivedKeys.includes('unclaimedPayouts')) { - setUnclaimedPayouts( - (Subscan.payoutData[activeAccount]?.['unclaimedPayouts'] || - []) as NominatorReward[] - ) - } - if (receivedKeys.includes('poolClaims')) { setPoolClaims( (Subscan.payoutData[activeAccount]?.['poolClaims'] || @@ -68,7 +46,8 @@ export const useSubscanData = () => { ) // Inject timestamp for unclaimed payouts. We take the timestamp of the start of the - // following payout era - this is the time payouts become available to claim by validators. + // following payout era - this is the time payouts become available to claim by validators + // NOTE: Not currently being used const injectBlockTimestamp = (entries: NominatorReward[]) => { if (!entries) { return entries @@ -85,14 +64,6 @@ export const useSubscanData = () => { // Populate state on initial render if data is already available. useEffect(() => { if (activeAccount) { - setPayouts( - (Subscan.payoutData[activeAccount]?.['payouts'] || - []) as NominatorReward[] - ) - setUnclaimedPayouts( - (Subscan.payoutData[activeAccount]?.['unclaimedPayouts'] || - []) as NominatorReward[] - ) setPoolClaims( (Subscan.payoutData[activeAccount]?.['poolClaims'] || []) as SubscanPoolClaim[] @@ -101,8 +72,6 @@ export const useSubscanData = () => { }, [activeAccount]) return { - payouts, - unclaimedPayouts, poolClaims, injectBlockTimestamp, } From 0bc2713f0dee7fa8899bebf44c8c8ab9fcc0905f Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:37:31 +0700 Subject: [PATCH 23/33] polish hook --- .../app/src/hooks/useAccountFromUrl/index.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/app/src/hooks/useAccountFromUrl/index.tsx b/packages/app/src/hooks/useAccountFromUrl/index.tsx index a16ed8863b..6446cc6451 100644 --- a/packages/app/src/hooks/useAccountFromUrl/index.tsx +++ b/packages/app/src/hooks/useAccountFromUrl/index.tsx @@ -10,7 +10,7 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' export const useAccountFromUrl = () => { - const { t } = useTranslation() + const { t } = useTranslation('library') const { accounts } = useImportedAccounts() const { accountsInitialised } = useOtherAccounts() const { activeAccount, setActiveAccount } = useActiveAccounts() @@ -18,17 +18,15 @@ export const useAccountFromUrl = () => { // Set active account if url var present and accounts initialised useEffect(() => { if (accountsInitialised) { - const aUrl = extractUrlValue('a') - if (aUrl) { - const account = accounts.find((a) => a.address === aUrl) - if (account && aUrl !== activeAccount) { - setActiveAccount(account.address || null) + const val = extractUrlValue('a') + if (val) { + const account = accounts.find((a) => a.address === val) + if (account && activeAccount !== val) { + setActiveAccount(account.address) Notifications.emit({ - title: t('accountConnected', { ns: 'library' }), - subtitle: `${t('connectedTo', { ns: 'library' })} ${ - account.name || aUrl - }.`, + title: t('accountConnected'), + subtitle: `${t('connectedTo')} ${account.name}.`, }) } } From 1be14ec8a4eeba1f318645aaa2cb24633b43e806 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:39:25 +0700 Subject: [PATCH 24/33] polish useSubscanData --- packages/app/src/hooks/useSubscanData/index.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/app/src/hooks/useSubscanData/index.tsx b/packages/app/src/hooks/useSubscanData/index.tsx index eee533903f..7765121dd7 100644 --- a/packages/app/src/hooks/useSubscanData/index.tsx +++ b/packages/app/src/hooks/useSubscanData/index.tsx @@ -18,7 +18,7 @@ export const useSubscanData = () => { const { erasToSeconds } = useErasToTimeLeft() const { activeAccount } = useActiveAccounts() - // Store pool claims data for the active account. + // Store pool claims data for the active account const [poolClaims, setPoolClaims] = useState([]) // Listen for updated data callback. When there are new data, fetch the updated values directly @@ -27,7 +27,6 @@ export const useSubscanData = () => { // NOTE: Subscan has to be enabled to continue. if (isCustomEvent(e) && pluginEnabled('subscan') && activeAccount) { const { keys: receivedKeys }: { keys: PayoutType[] } = e.detail - if (receivedKeys.includes('poolClaims')) { setPoolClaims( (Subscan.payoutData[activeAccount]?.['poolClaims'] || @@ -37,7 +36,7 @@ export const useSubscanData = () => { } } - // Listen for new subscan data updates. + // Listen for new subscan data updates const documentRef = useRef(document) useEventListener( 'subscan-data-updated', @@ -49,9 +48,6 @@ export const useSubscanData = () => { // following payout era - this is the time payouts become available to claim by validators // NOTE: Not currently being used const injectBlockTimestamp = (entries: NominatorReward[]) => { - if (!entries) { - return entries - } entries.forEach((p) => { p.timestamp = activeEra.start .multipliedBy(0.001) @@ -61,7 +57,7 @@ export const useSubscanData = () => { return entries } - // Populate state on initial render if data is already available. + // Populate state on initial render if data is already available useEffect(() => { if (activeAccount) { setPoolClaims( From 6e5c8e1531db54a779939cb148abbc2497c692cb Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:46:51 +0700 Subject: [PATCH 25/33] polish PayoutBar --- packages/app/src/library/Graphs/PayoutBar.tsx | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/app/src/library/Graphs/PayoutBar.tsx b/packages/app/src/library/Graphs/PayoutBar.tsx index ae35f1d41e..5a68abf0d9 100644 --- a/packages/app/src/library/Graphs/PayoutBar.tsx +++ b/packages/app/src/library/Graphs/PayoutBar.tsx @@ -1,8 +1,8 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { AnyJson } from '@w3ux/types' import BigNumber from 'bignumber.js' +import type { TooltipItem } from 'chart.js' import { BarElement, CategoryScale, @@ -14,7 +14,6 @@ import { Title, Tooltip, } from 'chart.js' -import type { AnyApi } from 'common-types' import { useNetwork } from 'contexts/Network' import { useTheme } from 'contexts/Themes' import { format, fromUnixTime } from 'date-fns' @@ -48,7 +47,7 @@ export const PayoutBar = ({ const { unit, units, colors } = useNetwork().networkData const staking = nominating || inPool - // get formatted rewards data for graph. + // Get formatted rewards data const { allPayouts, allPoolClaims, allUnclaimedPayouts } = formatRewardsForGraphs( new Date(), @@ -58,27 +57,25 @@ export const PayoutBar = ({ poolClaims, unclaimedPayouts ) - const { p: graphPayouts } = allPayouts const { p: graphUnclaimedPayouts } = allUnclaimedPayouts const { p: graphPoolClaims } = allPoolClaims - // determine color for payouts + // Determine color for payouts const colorPayouts = !staking ? colors.transparent[mode] : colors.primary[mode] - // determine color for poolClaims + // Determine color for poolClaims const colorPoolClaims = !staking ? colors.transparent[mode] : colors.secondary[mode] - // Bar border radius const borderRadius = 4 - + const pointRadius = 0 const data = { - labels: graphPayouts.map((item: AnyApi) => { - const dateObj = format(fromUnixTime(item.timestamp), 'do MMM', { + labels: graphPayouts.map(({ timestamp }: { timestamp: number }) => { + const dateObj = format(fromUnixTime(timestamp), 'do MMM', { locale: locales[i18n.resolvedLanguage ?? DefaultLocale].dateFormat, }) return `${dateObj}` @@ -88,30 +85,30 @@ export const PayoutBar = ({ { order: 1, label: t('payout'), - data: graphPayouts.map((item: AnyApi) => item.reward), + data: graphPayouts.map(({ reward }: { reward: string }) => reward), borderColor: colorPayouts, backgroundColor: colorPayouts, - pointRadius: 0, + pointRadius, borderRadius, }, { order: 2, label: t('poolClaim'), - data: graphPoolClaims.map((item: AnyApi) => item.reward), + data: graphPoolClaims.map(({ reward }: { reward: string }) => reward), borderColor: colorPoolClaims, backgroundColor: colorPoolClaims, - pointRadius: 0, + pointRadius, borderRadius, }, { order: 3, - data: graphUnclaimedPayouts.map((item: AnyApi) => - item.reward.toFixed(5) + data: graphUnclaimedPayouts.map( + ({ reward }: { reward: string }) => reward ), label: t('unclaimedPayouts'), borderColor: colorPayouts, backgroundColor: colors.pending[mode], - pointRadius: 0, + pointRadius, borderRadius, }, ], @@ -167,10 +164,10 @@ export const PayoutBar = ({ }, callbacks: { title: () => [], - label: (context: AnyJson) => - `${ - context.dataset.order === 3 ? `${t('pending')}: ` : '' - }${new BigNumber(context.parsed.y) + label: ({ dataset, parsed }: TooltipItem<'bar'>) => + `${dataset.order === 3 ? `${t('pending')}: ` : ''}${new BigNumber( + parsed.y + ) .decimalPlaces(units) .toFormat()} ${unit}`, }, From 3d4fd9ded29fd30a2299bbc5bdb49f4583dc2d9e Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:49:51 +0700 Subject: [PATCH 26/33] polish PayoutlLine --- .../app/src/library/Graphs/PayoutLine.tsx | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/app/src/library/Graphs/PayoutLine.tsx b/packages/app/src/library/Graphs/PayoutLine.tsx index a0dccba8c1..78529407d9 100644 --- a/packages/app/src/library/Graphs/PayoutLine.tsx +++ b/packages/app/src/library/Graphs/PayoutLine.tsx @@ -1,8 +1,8 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { AnyJson } from '@w3ux/types' import BigNumber from 'bignumber.js' +import type { TooltipItem } from 'chart.js' import { CategoryScale, Chart as ChartJS, @@ -13,7 +13,6 @@ import { Title, Tooltip, } from 'chart.js' -import type { AnyApi } from 'common-types' import { useNetwork } from 'contexts/Network' import { useTheme } from 'contexts/Themes' import { Line } from 'react-chartjs-2' @@ -47,12 +46,11 @@ export const PayoutLine = ({ }: PayoutLineProps) => { const { t } = useTranslation('library') const { mode } = useTheme() - const staking = nominating || inPool - const { unit, units, colors } = useNetwork().networkData - const inPoolOnly = !nominating && inPool - // define the most recent date that we will show on the graph. + const staking = nominating || inPool + const inPoolOnly = !nominating && inPool + // Define the most recent date that we will show on the graph const fromDate = new Date() const { allPayouts, allPoolClaims } = formatRewardsForGraphs( @@ -61,13 +59,12 @@ export const PayoutLine = ({ units, payouts, poolClaims, - [] // Note: we are not using `unclaimedPayouts` here. + [] // Note: we are not using `unclaimedPayouts` here ) - const { p: graphPayouts, a: graphPrePayouts } = allPayouts const { p: graphPoolClaims, a: graphPrePoolClaims } = allPoolClaims - // combine payouts and pool claims into one dataset and calculate averages. + // Combine payouts and pool claims into one dataset and calculate averages const combined = combineRewards(graphPayouts, graphPoolClaims) const preCombined = combineRewards(graphPrePayouts, graphPrePoolClaims) @@ -78,14 +75,13 @@ export const PayoutLine = ({ 10 ) - // determine color for payouts + // Determine color for payouts const color = !staking ? colors.primary[mode] : !inPoolOnly ? colors.primary[mode] : colors.secondary[mode] - // configure graph options const options = { responsive: true, maintainAspectRatio: false, @@ -127,8 +123,8 @@ export const PayoutLine = ({ }, callbacks: { title: () => [], - label: (context: AnyJson) => - ` ${new BigNumber(context.parsed.y) + label: ({ parsed }: TooltipItem<'line'>) => + ` ${new BigNumber(parsed.y) .decimalPlaces(units) .toFormat()} ${unit}`, }, @@ -145,7 +141,7 @@ export const PayoutLine = ({ datasets: [ { label: t('payout'), - data: combinedPayouts.map((item: AnyApi) => item.reward), + data: combinedPayouts.map(({ reward }: { reward: string }) => reward), borderColor: color, pointStyle: undefined, pointRadius: 0, From c090799a1b9bd1c158ea4de0fb6eac79ecdc00d7 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:53:51 +0700 Subject: [PATCH 27/33] polish --- packages/app/src/library/Headers/Sync.tsx | 4 +- .../src/overlay/modals/ClaimPayouts/Forms.tsx | 40 ++++++++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/packages/app/src/library/Headers/Sync.tsx b/packages/app/src/library/Headers/Sync.tsx index 5eaec48275..0cf095866f 100644 --- a/packages/app/src/library/Headers/Sync.tsx +++ b/packages/app/src/library/Headers/Sync.tsx @@ -17,7 +17,7 @@ export const Sync = () => { const { bondedPools } = useBondedPools() // Keep syncing if on pools page and still fetching bonded pools or pool members. Ignore pool - // member sync if Subscan is enabled. + // member sync if Subscan is enabled const onPoolsSyncing = () => { if (pageFromUri(pathname, 'overview') === 'pools') { if (!bondedPools.length) { @@ -27,7 +27,7 @@ export const Sync = () => { return false } - // Keep syncing if on validators page and still fetching. + // Keep syncing if on validators page and still fetching const onValidatorsSyncing = () => { if ( pageFromUri(pathname, 'overview') === 'validators' && diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx b/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx index 1f9ee2e2d3..5e7d5b80db 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/Forms.tsx @@ -41,15 +41,14 @@ export const Forms = forwardRef( const { getSignerWarnings } = useSignerWarnings() const { unclaimedRewards, setUnclaimedRewards } = usePayouts() - // Get the total payout amount. + // Get the total payout amount const totalPayout = payouts?.reduce( (total: BigNumber, cur: ActivePayout) => total.plus(cur.payout), new BigNumber(0) ) || new BigNumber(0) - // Get the total number of validators to payout (the same validator can repeat for separate - // eras). + // Get the total number of validators per payout per era const totalPayoutValidators = payouts?.reduce( (prev, { paginatedValidators }) => @@ -57,9 +56,13 @@ export const Forms = forwardRef( 0 ) || 0 + const [valid, setValid] = useState( + totalPayout.isGreaterThan(0) && totalPayoutValidators > 0 + ) + const getCalls = () => { - const calls = payouts?.reduce( - (acc: AnyApi[], { era, paginatedValidators }) => { + const calls = + payouts?.reduce((acc: AnyApi[], { era, paginatedValidators }) => { if (!paginatedValidators.length) { return acc } @@ -75,31 +78,16 @@ export const Forms = forwardRef( } }) return acc - }, - [] - ) - - return calls || [] + }, []) || [] + return calls } - // Store whether form is valid to submit transaction. - const [valid, setValid] = useState( - totalPayout.isGreaterThan(0) && totalPayoutValidators > 0 - ) - - // Ensure payouts value is valid. - useEffect( - () => setValid(totalPayout.isGreaterThan(0) && totalPayoutValidators > 0), - [payouts] - ) - const getTx = () => { const tx = null const calls = getCalls() if (!valid || !calls.length) { return tx } - return calls.length === 1 ? calls.pop() : newBatchCall(calls, activeAccount) @@ -129,7 +117,7 @@ export const Forms = forwardRef( } setUnclaimedRewards(newUnclaimedRewards) } - // Reset active form payouts for this modal. + // Reset active form payouts for this modal setPayouts([]) }, }) @@ -140,6 +128,12 @@ export const Forms = forwardRef( submitExtrinsic.proxySupported ) + // Ensure payouts value is valid + useEffect( + () => setValid(totalPayout.isGreaterThan(0) && totalPayoutValidators > 0), + [payouts] + ) + return (
From 0f34dd61d5d0e5dfcfcc0cebe4d772153680b77c Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:55:29 +0700 Subject: [PATCH 28/33] lint --- packages/app/src/overlay/modals/ClaimPayouts/Item.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx b/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx index 8f4f92c641..f07ca582e5 100644 --- a/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx +++ b/packages/app/src/overlay/modals/ClaimPayouts/Item.tsx @@ -19,7 +19,6 @@ export const Item = ({ const { networkData: { units, unit }, } = useNetwork() - const totalPayout = getTotalPayout(validators) const numPayouts = validators.length @@ -39,7 +38,6 @@ export const Item = ({ {planckToUnitBn(totalPayout, units).toString()} {unit} -
Date: Sat, 14 Dec 2024 20:56:43 +0700 Subject: [PATCH 29/33] add Props --- .../pages/Overview/Payouts/ActiveGraph.tsx | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx index 77db9a06e7..04cc4f2a5f 100644 --- a/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Overview/Payouts/ActiveGraph.tsx @@ -11,17 +11,18 @@ import { ApolloProvider, client, useRewards } from 'plugin-staking-api' import type { NominatorReward } from 'plugin-staking-api/types' import { useEffect } from 'react' +interface Props { + nominating: boolean + inPool: boolean + lineMarginTop: string + setLastReward: (reward: NominatorReward | undefined) => void +} export const ActiveGraphInner = ({ nominating, inPool, lineMarginTop, setLastReward, -}: { - nominating: boolean - inPool: boolean - lineMarginTop: string - setLastReward: (reward: NominatorReward | undefined) => void -}) => { +}: Props) => { const { activeEra } = useApi() const { network } = useNetwork() const { poolClaims } = useSubscanData() @@ -67,12 +68,7 @@ export const ActiveGraphInner = ({ ) } -export const ActiveGraph = (props: { - nominating: boolean - inPool: boolean - lineMarginTop: string - setLastReward: (reward: NominatorReward | undefined) => void -}) => ( +export const ActiveGraph = (props: Props) => ( From 633ee27913cff6bc198121219133943c26ec1afd Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 20:59:58 +0700 Subject: [PATCH 30/33] polish --- .../app/src/pages/Overview/Payouts/index.tsx | 4 +-- .../app/src/pages/Payouts/ActiveGraph.tsx | 20 +++++++-------- .../src/pages/Payouts/PayoutList/index.tsx | 25 ++++++------------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/packages/app/src/pages/Overview/Payouts/index.tsx b/packages/app/src/pages/Overview/Payouts/index.tsx index f86cde4337..de8fa613e1 100644 --- a/packages/app/src/pages/Overview/Payouts/index.tsx +++ b/packages/app/src/pages/Overview/Payouts/index.tsx @@ -48,10 +48,10 @@ export const Payouts = () => { const [lastReward, setLastReward] = useState() - // Ref to the graph container. + // Ref to the graph container const graphInnerRef = useRef(null) - // Get the size of the graph container. + // Get the size of the graph container const size = useSize(graphInnerRef, { outerElement: containerRefs?.mainInterface, }) diff --git a/packages/app/src/pages/Payouts/ActiveGraph.tsx b/packages/app/src/pages/Payouts/ActiveGraph.tsx index d169048104..4a9c8b483b 100644 --- a/packages/app/src/pages/Payouts/ActiveGraph.tsx +++ b/packages/app/src/pages/Payouts/ActiveGraph.tsx @@ -15,15 +15,17 @@ import { ApolloProvider, client, useRewards } from 'plugin-staking-api' import type { NominatorReward } from 'plugin-staking-api/types' import { useEffect } from 'react' +interface Props { + nominating: boolean + inPool: boolean + setPayoutLists: (payouts: AnyApi[]) => void +} + export const ActiveGraphInner = ({ nominating, inPool, setPayoutLists, -}: { - nominating: boolean - inPool: boolean - setPayoutLists: (payouts: AnyApi[]) => void -}) => { +}: Props) => { const { activeEra } = useApi() const { network } = useNetwork() const { poolClaims } = useSubscanData() @@ -44,7 +46,7 @@ export const ActiveGraphInner = ({ [] useEffect(() => { - // filter zero rewards and order via timestamp, most recent first. + // filter zero rewards and order via timestamp, most recent first const payoutsList = (allRewards as PayoutsAndClaims).concat( poolClaims ) as PayoutsAndClaims @@ -72,11 +74,7 @@ export const ActiveGraphInner = ({ ) } -export const ActiveGraph = (props: { - nominating: boolean - inPool: boolean - setPayoutLists: (payouts: AnyApi[]) => void -}) => ( +export const ActiveGraph = (props: Props) => ( diff --git a/packages/app/src/pages/Payouts/PayoutList/index.tsx b/packages/app/src/pages/Payouts/PayoutList/index.tsx index c50c456bce..5748e3f7db 100644 --- a/packages/app/src/pages/Payouts/PayoutList/index.tsx +++ b/packages/app/src/pages/Payouts/PayoutList/index.tsx @@ -40,30 +40,28 @@ export const PayoutListInner = ({ const { networkData: { units, unit, colors }, } = useNetwork() - const { listFormat, setListFormat } = usePayoutList() const { validators } = useValidators() const { bondedPools } = useBondedPools() + const { listFormat, setListFormat } = usePayoutList() - // current page const [page, setPage] = useState(1) - // manipulated list (ordering, filtering) of payouts + // Manipulated list (ordering, filtering) of payouts const [payouts, setPayouts] = useState(initialPayouts) - // is this the initial fetch + // Whether still in initial fetch const [fetched, setFetched] = useState(false) - // pagination const totalPages = Math.ceil(payouts.length / payoutsPerPage) const pageEnd = page * payoutsPerPage - 1 const pageStart = pageEnd - (payoutsPerPage - 1) - // refetch list when list changes + // Refetch list when list changes useEffect(() => { setFetched(false) }, [initialPayouts]) - // configure list when network is ready to fetch + // Configure list when network is ready to fetch useEffect(() => { if (isReady && !activeEra.index.isZero() && !fetched) { setPayouts(initialPayouts) @@ -71,13 +69,8 @@ export const PayoutListInner = ({ } }, [isReady, fetched, activeEra.index]) - // get list items to render - let listPayouts = [] - - // get throttled subset or entire list - listPayouts = payouts.slice(pageStart).slice(0, payoutsPerPage) - - if (!payouts.length) { + const listPayouts = payouts.slice(pageStart).slice(0, payoutsPerPage) + if (!listPayouts.length) { return null } @@ -111,11 +104,7 @@ export const PayoutListInner = ({ const label = p.type === 'pool' ? t('payouts.poolClaim') : t('payouts.payout') const labelClass = p.type === 'pool' ? 'claim' : 'reward' - - // get validator if it exists const validator = validators.find((v) => v.address === p.validator) - - // get pool if it exists const pool = bondedPools.find(({ id }) => id === p.pool_id) const batchIndex = validator From c8e31e9d14603cd0c416104a8f15cd377b34eb7e Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sat, 14 Dec 2024 21:01:35 +0700 Subject: [PATCH 31/33] polish --- packages/app/src/pages/Payouts/index.tsx | 1 - packages/plugin-staking-api/src/types.ts | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/app/src/pages/Payouts/index.tsx b/packages/app/src/pages/Payouts/index.tsx index 39640a5980..57847c3df1 100644 --- a/packages/app/src/pages/Payouts/index.tsx +++ b/packages/app/src/pages/Payouts/index.tsx @@ -112,7 +112,6 @@ export const Payouts = ({ page: { key } }: PageProps) => { topOffset="30%" /> )} - Date: Sun, 15 Dec 2024 09:20:21 +0700 Subject: [PATCH 32/33] prevent dip on unfinished era --- packages/app/src/library/Graphs/Utils.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/app/src/library/Graphs/Utils.ts b/packages/app/src/library/Graphs/Utils.ts index c1d8c75a07..2021c975d7 100644 --- a/packages/app/src/library/Graphs/Utils.ts +++ b/packages/app/src/library/Graphs/Utils.ts @@ -130,7 +130,7 @@ export const calculatePayoutAverages = ( } // create moving average value over `avgDays` past days, if any - let payoutsAverages = [] + let payoutsAverages: { reward: number; timestamp: number }[] = [] for (let i = 0; i < payouts.length; i++) { // average period end. const end = Math.max(0, i - avgDays) @@ -152,8 +152,15 @@ export const calculatePayoutAverages = ( total = payouts[i].reward } + // If on last reward and is a zero (current era still processing), use previous reward to + // prevent misleading dip + const reward = + i === payouts.length - 1 && payouts[i].reward === 0 + ? payoutsAverages[i - 1].reward + : total / num + payoutsAverages.push({ - reward: total / num, + reward, timestamp: payouts[i].timestamp, }) } From 84bc8f9559de7b98faee5d813697eb121959010e Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Sun, 15 Dec 2024 09:36:16 +0700 Subject: [PATCH 33/33] disable payouts on staking api disabled --- .../pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx | 4 +++- packages/app/src/pages/Nominate/Active/Status/index.tsx | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx b/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx index a49c14c696..b26e85f52a 100644 --- a/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx +++ b/packages/app/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx @@ -8,6 +8,7 @@ import { useApi } from 'contexts/Api' import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts' import { useNetwork } from 'contexts/Network' import { usePayouts } from 'contexts/Payouts' +import { usePlugins } from 'contexts/Plugins' import { useOverlay } from 'kits/Overlay/Provider' import { Stat } from 'library/Stat' import { useTranslation } from 'react-i18next' @@ -22,6 +23,7 @@ export const UnclaimedPayoutsStatus = ({ dimmed }: { dimmed: boolean }) => { const { unclaimedRewards: { total }, } = usePayouts() + const { pluginEnabled } = usePlugins() const { activeAccount } = useActiveAccounts() const { isReadOnlyAccount } = useImportedAccounts() @@ -38,7 +40,7 @@ export const UnclaimedPayoutsStatus = ({ dimmed }: { dimmed: boolean }) => { }} dimmed={dimmed} buttons={ - total !== '0' + total !== '0' && pluginEnabled('staking_api') ? [ { title: t('claim', { ns: 'modals' }), diff --git a/packages/app/src/pages/Nominate/Active/Status/index.tsx b/packages/app/src/pages/Nominate/Active/Status/index.tsx index 60ea4e26f6..12c6bf3ac3 100644 --- a/packages/app/src/pages/Nominate/Active/Status/index.tsx +++ b/packages/app/src/pages/Nominate/Active/Status/index.tsx @@ -3,6 +3,7 @@ import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts' +import { usePlugins } from 'contexts/Plugins' import { useStaking } from 'contexts/Staking' import { useSyncing } from 'hooks/useSyncing' import { CardWrapper } from 'library/Card/Wrappers' @@ -15,6 +16,7 @@ import { UnclaimedPayoutsStatus } from './UnclaimedPayoutsStatus' export const Status = ({ height }: { height: number }) => { const { syncing } = useSyncing() const { inSetup } = useStaking() + const { pluginEnabled } = usePlugins() const { activeAccount } = useActiveAccounts() const { isReadOnlyAccount } = useImportedAccounts() @@ -25,7 +27,9 @@ export const Status = ({ height }: { height: number }) => { > - + {!syncing ? ( !inSetup() ? (