mirror of
https://github.com/pezkuwichain/pezkuwi-sdk-ui.git
synced 2026-06-17 10:11:09 +00:00
Initial commit: Pezkuwi SDK UI
Comprehensive web interface for interacting with Pezkuwi blockchain. Features: - Blockchain explorer - Wallet management - Staking interface - Governance participation - Developer tools Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
// Copyright 2017-2026 @pezkuwi/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { CombinatorFunction } from '@pezkuwi/api/promise/Combinator';
|
||||
import type { DeriveStakingAccount } from '@pezkuwi/api-derive/types';
|
||||
import type { AccountId, ValidatorPrefs } from '@pezkuwi/types/interfaces';
|
||||
import type { Codec, ITuple } from '@pezkuwi/types/types';
|
||||
import type { StakerState } from './types.js';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { u8aConcat, u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { isEmpty } from './utils/isEmpty.js';
|
||||
import { createNamedHook } from './createNamedHook.js';
|
||||
import { useAccounts } from './useAccounts.js';
|
||||
import { useApi } from './useApi.js';
|
||||
import { useIsMountedRef } from './useIsMountedRef.js';
|
||||
import { useOwnStashes } from './useOwnStashes.js';
|
||||
|
||||
type ValidatorInfo = ITuple<[ValidatorPrefs, Codec]> | ValidatorPrefs;
|
||||
type Queried = Record<string, [boolean, DeriveStakingAccount, ValidatorInfo]>;
|
||||
|
||||
function toIdString (id?: AccountId | null): string | null {
|
||||
return id
|
||||
? id.toString()
|
||||
: null;
|
||||
}
|
||||
|
||||
const QUERY_OPTS = {
|
||||
withClaimedRewardsEras: true,
|
||||
withDestination: true,
|
||||
withLedger: true,
|
||||
withNominations: true,
|
||||
withPrefs: true
|
||||
};
|
||||
|
||||
function getStakerState (stashId: string, allAccounts: string[], [isOwnStash, { claimedRewardsEras, controllerId: _controllerId, exposureMeta, exposurePaged, nextSessionIds: _nextSessionIds, nominators, rewardDestination, sessionIds: _sessionIds, stakingLedger, validatorPrefs }, validateInfo]: [boolean, DeriveStakingAccount, ValidatorInfo]): StakerState {
|
||||
const isStashNominating = !!(nominators?.length);
|
||||
const isStashValidating = !(Array.isArray(validateInfo) ? isEmpty(validateInfo[1] as ValidatorPrefs) : isEmpty(validateInfo));
|
||||
const nextSessionIds = _nextSessionIds instanceof Map
|
||||
? [..._nextSessionIds.values()]
|
||||
: _nextSessionIds;
|
||||
const nextConcat = u8aConcat(...nextSessionIds.map((id) => id.toU8a()));
|
||||
const sessionIds = _sessionIds instanceof Map
|
||||
? [..._sessionIds.values()]
|
||||
: _sessionIds;
|
||||
const currConcat = u8aConcat(...sessionIds.map((id) => id.toU8a()));
|
||||
const controllerId = toIdString(_controllerId);
|
||||
|
||||
return {
|
||||
claimedRewardsEras,
|
||||
controllerId,
|
||||
destination: rewardDestination,
|
||||
exposureMeta,
|
||||
exposurePaged,
|
||||
hexSessionIdNext: u8aToHex(nextConcat, 48),
|
||||
hexSessionIdQueue: u8aToHex(currConcat.length ? currConcat : nextConcat, 48),
|
||||
isLoading: false,
|
||||
isOwnController: allAccounts.includes(controllerId || ''),
|
||||
isOwnStash,
|
||||
isStashNominating,
|
||||
isStashValidating,
|
||||
// we assume that all ids are non-null
|
||||
nominating: nominators?.map(toIdString) as string[],
|
||||
sessionIds: (
|
||||
nextSessionIds.length
|
||||
? nextSessionIds
|
||||
: sessionIds
|
||||
).map(toIdString) as string[],
|
||||
stakingLedger,
|
||||
stashId,
|
||||
validatorPrefs
|
||||
};
|
||||
}
|
||||
|
||||
function useOwnStashInfosImpl (apiOverride?: ApiPromise): StakerState[] | undefined {
|
||||
const { api: connectedApi } = useApi();
|
||||
const api = useMemo(() => apiOverride ?? connectedApi, [apiOverride, connectedApi]);
|
||||
const { allAccounts } = useAccounts();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const ownStashes = useOwnStashes(undefined, api);
|
||||
const [queried, setQueried] = useState<Queried | undefined>();
|
||||
|
||||
useEffect((): () => void => {
|
||||
let unsub: (() => void) | undefined;
|
||||
|
||||
if (ownStashes) {
|
||||
if (ownStashes.length) {
|
||||
const stashIds = ownStashes.map(([stashId]) => stashId);
|
||||
const fns = [
|
||||
[api.derive.staking.accounts, stashIds, QUERY_OPTS],
|
||||
[api.query.staking.validators.multi, stashIds]
|
||||
] as unknown as CombinatorFunction[];
|
||||
|
||||
api.combineLatest<[DeriveStakingAccount[], ValidatorInfo[]]>(fns, ([accounts, validators]): void => {
|
||||
mountedRef.current && ownStashes.length === accounts.length && ownStashes.length === validators.length && setQueried(
|
||||
ownStashes.reduce((queried: Queried, [stashId, isOwnStash], index): Queried => ({
|
||||
...queried,
|
||||
[stashId]: [isOwnStash, accounts[index], validators[index]]
|
||||
}), {})
|
||||
);
|
||||
}).then((u): void => {
|
||||
unsub = u;
|
||||
}).catch(console.error);
|
||||
} else {
|
||||
mountedRef.current && setQueried({});
|
||||
}
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
unsub && unsub();
|
||||
};
|
||||
}, [api, mountedRef, ownStashes]);
|
||||
|
||||
return useMemo(
|
||||
() => ownStashes && queried && ownStashes.length === Object.keys(queried).length
|
||||
? ownStashes
|
||||
.filter(([stashId]) => queried[stashId])
|
||||
.map(([stashId]) => getStakerState(stashId, allAccounts, queried[stashId]))
|
||||
: undefined,
|
||||
[allAccounts, ownStashes, queried]
|
||||
);
|
||||
}
|
||||
|
||||
export const useOwnStashInfos = createNamedHook('useOwnStashInfos', useOwnStashInfosImpl);
|
||||
Reference in New Issue
Block a user