Expansion to pools logic (#7384)

* Expansion to pools logic

* Add filtering

* Pass through own pools

* Allow pool nominate tx

* Start with account actions

* Asho pool nominees

* Nomination operational

* Adjust table rows

* BondExtra for pools

* use availableBalance in calcs (w/ existential)

* Re-use isAmountError check

* Pass through actual pool info

* s/id/poolId/

* Display payout stashes

* Display points balance

* OCD cleanups

* claimPayout

* Calculate pool claimable

* Unbond

* Fix Unbond max

* Display remaining witldrawal eras

* Stashed/Pooled selection

* Withdraw unbonded

* StakingRedeemable display

* Renames as per metadata

* Additional

* s/pool/pools/

* PoolInfo display hints

* Adjust usePoolInfo hook

* Ordering adjustments

* Additional dedupes

* Combine queries

* Combinations & renames

* Flatten transform

* Non-existent reward

* bnMax sub

* lint fixes

* Bump API
This commit is contained in:
Jaco 2022-04-26 17:21:38 +02:00 committed by GitHub
parent 28bbe4e986
commit 47913892cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1471 additions and 425 deletions

View File

@ -91,24 +91,24 @@
"webpack-cli": "^4.9.2"
},
"resolutions": {
"@polkadot/api": "^8.2.2-1",
"@polkadot/api-augment": "^8.2.2-1",
"@polkadot/api-base": "^8.2.2-1",
"@polkadot/api-contract": "^8.2.2-1",
"@polkadot/api-derive": "^8.2.2-1",
"@polkadot/api": "^8.2.2-2",
"@polkadot/api-augment": "^8.2.2-2",
"@polkadot/api-base": "^8.2.2-2",
"@polkadot/api-contract": "^8.2.2-2",
"@polkadot/api-derive": "^8.2.2-2",
"@polkadot/hw-ledger": "^9.0.1",
"@polkadot/keyring": "^9.0.1",
"@polkadot/networks": "^9.0.1",
"@polkadot/phishing": "^0.11.91",
"@polkadot/rpc-augment": "^8.2.2-1",
"@polkadot/rpc-core": "^8.2.2-1",
"@polkadot/rpc-provider": "^8.2.2-1",
"@polkadot/types": "^8.2.2-1",
"@polkadot/types-augment": "^8.2.2-1",
"@polkadot/types-codec": "^8.2.2-1",
"@polkadot/types-create": "^8.2.2-1",
"@polkadot/types-known": "^8.2.2-1",
"@polkadot/types-support": "^8.2.2-1",
"@polkadot/rpc-augment": "^8.2.2-2",
"@polkadot/rpc-core": "^8.2.2-2",
"@polkadot/rpc-provider": "^8.2.2-2",
"@polkadot/types": "^8.2.2-2",
"@polkadot/types-augment": "^8.2.2-2",
"@polkadot/types-codec": "^8.2.2-2",
"@polkadot/types-create": "^8.2.2-2",
"@polkadot/types-known": "^8.2.2-2",
"@polkadot/types-support": "^8.2.2-2",
"@polkadot/util": "^9.0.1",
"@polkadot/util-crypto": "^9.0.1",
"@polkadot/wasm-crypto": "^6.0.1",

View File

@ -29,10 +29,10 @@
"@metaverse-network-sdk/type-definitions": "^0.0.1-13",
"@parallel-finance/type-definitions": "1.6.5",
"@phala/typedefs": "0.2.30",
"@polkadot/api": "^8.2.2-1",
"@polkadot/api-derive": "^8.2.2-1",
"@polkadot/api": "^8.2.2-2",
"@polkadot/api-derive": "^8.2.2-2",
"@polkadot/networks": "^9.0.1",
"@polkadot/types": "^8.2.2-1",
"@polkadot/types": "^8.2.2-2",
"@polkadot/util": "^9.0.1",
"@polkadot/x-fetch": "^9.0.1",
"@polymathnetwork/polymesh-types": "0.0.2",

View File

@ -14,6 +14,6 @@
"version": "0.113.2-1",
"dependencies": {
"@babel/runtime": "^7.17.9",
"@polkadot/api-contract": "^8.2.2-1"
"@polkadot/api-contract": "^8.2.2-2"
}
}

View File

@ -72,7 +72,7 @@ function BondExtra ({ controllerId, onClose, stakingInfo, stashId }: Props): Rea
autoFocus
defaultValue={startBalance}
help={t<string>('Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.')}
isError={!!amountError?.error || !maxAdditional || maxAdditional.eqn(0)}
isError={!!amountError?.error || !maxAdditional || maxAdditional.isZero()}
label={t<string>('additional funds to bond')}
labelExtra={
<BalanceFree

View File

@ -1,6 +1,7 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { SortedTargets } from '../../types';
import type { NominateInfo } from '../partials/types';
@ -17,11 +18,12 @@ interface Props {
controllerId: string;
nominating?: string[];
onClose: () => void;
poolId?: BN;
stashId: string;
targets: SortedTargets;
}
function Nominate ({ className = '', controllerId, nominating, onClose, stashId, targets }: Props): React.ReactElement<Props> | null {
function Nominate ({ className = '', controllerId, nominating, onClose, poolId, stashId, targets }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const [{ nominateTx }, setTx] = useState<NominateInfo>({});
@ -38,6 +40,7 @@ function Nominate ({ className = '', controllerId, nominating, onClose, stashId,
controllerId={controllerId}
nominating={nominating}
onChange={setTx}
poolId={poolId}
stashId={stashId}
targets={targets}
withSenders

View File

@ -3,8 +3,7 @@
import type { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types';
import type { StakerState } from '@polkadot/react-hooks/types';
import type { Option } from '@polkadot/types';
import type { SlashingSpans, UnappliedSlash } from '@polkadot/types/interfaces';
import type { PalletStakingUnappliedSlash } from '@polkadot/types/lookup';
import type { SortedTargets } from '../../types';
import type { Slash } from '../types';
@ -12,11 +11,12 @@ import React, { useCallback, useContext, useMemo } from 'react';
import styled from 'styled-components';
import { ApiPromise } from '@polkadot/api';
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, Checkbox, Menu, Popup, StakingBonded, StakingRedeemable, StakingUnbonding, StatusContext, TxButton } from '@polkadot/react-components';
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, Menu, Popup, StakingBonded, StakingRedeemable, StakingUnbonding, StatusContext, TxButton } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { BN, formatNumber, isFunction } from '@polkadot/util';
import { useTranslation } from '../../translate';
import useSlashingSpans from '../useSlashingSpans';
import BondExtra from './BondExtra';
import InjectKeys from './InjectKeys';
import KickNominees from './KickNominees';
@ -31,20 +31,18 @@ import Validate from './Validate';
import WarnBond from './WarnBond';
interface Props {
allSlashes?: [BN, UnappliedSlash[]][];
allSlashes?: [BN, PalletStakingUnappliedSlash[]][];
className?: string;
isDisabled?: boolean;
isSelectable: boolean;
info: StakerState;
minCommission?: BN;
next?: string[];
onSelect: (stashId: string, controllerId: string, isSelected: boolean) => void;
stashId: string;
targets: SortedTargets;
validators?: string[];
}
function extractSlashes (stashId: string, allSlashes: [BN, UnappliedSlash[]][] = []): Slash[] {
function extractSlashes (stashId: string, allSlashes: [BN, PalletStakingUnappliedSlash[]][] = []): Slash[] {
return allSlashes
.map(([era, all]) => ({
era,
@ -55,23 +53,16 @@ function extractSlashes (stashId: string, allSlashes: [BN, UnappliedSlash[]][] =
.filter(({ slashes }) => slashes.length);
}
const transformSpan = {
transform: (optSpans: Option<SlashingSpans>): number =>
optSpans.isNone
? 0
: optSpans.unwrap().prior.length + 1
};
function useStashCalls (api: ApiPromise, stashId: string) {
const params = useMemo(() => [stashId], [stashId]);
const balancesAll = useCall<DeriveBalancesAll>(api.derive.balances?.all, params);
const spanCount = useCall<number>(api.query.staking.slashingSpans, params, transformSpan);
const stakingAccount = useCall<DeriveStakingAccount>(api.derive.staking.account, params);
const spanCount = useSlashingSpans(stashId);
return { balancesAll, spanCount, stakingAccount };
}
function Account ({ allSlashes, className = '', info: { controllerId, destination, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, isSelectable, minCommission, onSelect, targets }: Props): React.ReactElement<Props> {
function Account ({ allSlashes, className = '', info: { controllerId, destination, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, minCommission, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { queueExtrinsic } = useContext(StatusContext);
@ -87,29 +78,20 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
const [isValidateOpen, toggleValidate] = useToggle();
const { balancesAll, spanCount, stakingAccount } = useStashCalls(api, stashId);
const _onSelect = useCallback(
(isSelected: boolean) => controllerId && onSelect(stashId, controllerId, isSelected),
[controllerId, stashId, onSelect]
);
const [isSelected, , setSelected] = useToggle(false, _onSelect);
const slashes = useMemo(
() => extractSlashes(stashId, allSlashes),
[allSlashes, stashId]
);
const withdrawFunds = useCallback(
() => {
queueExtrinsic({
accountId: controllerId,
extrinsic: api.tx.staking.withdrawUnbonded.meta.args.length === 1
? api.tx.staking.withdrawUnbonded(spanCount || 0)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore (We are doing toHex here since we have a Vec<u8> input)
: api.tx.staking.withdrawUnbonded()
});
},
() => queueExtrinsic({
accountId: controllerId,
extrinsic: api.tx.staking.withdrawUnbonded.meta.args.length === 1
? api.tx.staking.withdrawUnbonded(spanCount)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore (We are doing toHex here since we have a Vec<u8> input)
: api.tx.staking.withdrawUnbonded()
}),
[api, controllerId, queueExtrinsic, spanCount]
);
@ -377,14 +359,6 @@ function Account ({ allSlashes, className = '', info: { controllerId, destinatio
</>
)}
</td>
<td className='--hidden'>
{isSelectable && controllerId && (
<Checkbox
onChange={setSelected}
value={isSelected}
/>
)}
</td>
</tr>
);
}

View File

@ -0,0 +1,58 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { StakerState } from '@polkadot/react-hooks/types';
import type { PalletStakingUnappliedSlash } from '@polkadot/types/lookup';
import type { SortedTargets } from '../types';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { BN } from '@polkadot/util';
import { useTranslation } from '../translate';
import Account from './Account';
interface Props {
allSlashes: [BN, PalletStakingUnappliedSlash[]][];
className?: string;
footer: React.ReactNode;
isInElection?: boolean;
list?: StakerState[];
minCommission?: BN;
targets: SortedTargets;
}
function Accounts ({ allSlashes, className, footer, isInElection, list, minCommission, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const hdrRef = useRef([
[t('stashes'), 'start', 2],
[t('controller'), 'address'],
[t('rewards'), 'start media--1200'],
[t('bonded'), 'number'],
[]
]);
return (
<Table
className={className}
empty={list && t<string>('No funds staked yet. Bond funds to validate or nominate a validator')}
footer={footer}
header={hdrRef.current}
>
{list?.map((info): React.ReactNode => (
<Account
allSlashes={allSlashes}
info={info}
isDisabled={isInElection}
key={info.stashId}
minCommission={minCommission}
targets={targets}
/>
))}
</Table>
);
}
export default React.memo(Accounts);

View File

@ -0,0 +1,197 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveSessionProgress, DeriveUnlocking } from '@polkadot/api-derive/types';
import type { PalletNominationPoolsPoolMember, PalletNominationPoolsPoolRoles } from '@polkadot/types/lookup';
import type { PoolInfo } from '../../Pools/types';
import type { SortedTargets } from '../../types';
import React, { useCallback, useContext, useMemo } from 'react';
import { AddressSmall, Menu, Popup, StakingRedeemable, StakingUnbonding, StatusContext } from '@polkadot/react-components';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { BN, formatNumber } from '@polkadot/util';
import { useTranslation } from '../../translate';
import ListNominees from '../Account/ListNominees';
import Nominate from '../Account/Nominate';
import useSlashingSpans from '../useSlashingSpans';
import BondExtra from './BondExtra';
import Unbond from './Unbond';
import useAccountInfo from './useAccountInfo';
interface Props {
accountId: string;
className?: string;
info: PoolInfo;
isFirst: boolean;
poolId: BN;
sessionProgress?: DeriveSessionProgress;
targets: SortedTargets;
}
interface Roles {
isNominator: boolean;
}
function extractRoles (accountId: string, { nominator, root }: PalletNominationPoolsPoolRoles): Roles {
return {
isNominator: nominator.eq(accountId) || root.eq(accountId)
};
}
function calcUnbonding (accountId: string, stashId: string, { activeEra }: DeriveSessionProgress, { unbondingEras }: PalletNominationPoolsPoolMember): { accountId: string, controllerId: string, redeemable: BN, stashId: string, unlocking: DeriveUnlocking[] } {
const unlocking: DeriveUnlocking[] = [];
const redeemable = new BN(0);
for (const [era, value] of unbondingEras.entries()) {
if (era.lte(activeEra)) {
redeemable.iadd(value);
} else {
unlocking.push({ remainingEras: era.sub(activeEra), value });
}
}
return {
accountId,
controllerId: accountId,
redeemable,
stashId,
unlocking
};
}
function Pool ({ accountId, className, info: { bonded: { points, roles }, metadata, nominating, reward, rewardClaimable, stashId }, isFirst, poolId, sessionProgress, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const spanCount = useSlashingSpans(stashId);
const { queueExtrinsic } = useContext(StatusContext);
const [isBondOpen, toggleBond] = useToggle();
const [isNominateOpen, toggleNominate] = useToggle();
const [isUnbondOpen, toggleUnbond] = useToggle();
const accInfo = useAccountInfo(accountId, reward, points, rewardClaimable);
const stakingInfo = useMemo(
() => sessionProgress && accInfo && accInfo.member.unbondingEras && !accInfo.member.unbondingEras.isEmpty
? calcUnbonding(accountId, stashId, sessionProgress, accInfo.member)
: null,
[accInfo, accountId, stashId, sessionProgress]
);
const claimPayout = useCallback(
() => queueExtrinsic({
accountId,
extrinsic: api.tx.nominationPools.claimPayout()
}),
[api, accountId, queueExtrinsic]
);
const withdrawUnbonded = useCallback(
() => queueExtrinsic({
accountId,
extrinsic: api.tx.nominationPools.withdrawUnbonded(accountId, spanCount)
}),
[api, accountId, spanCount, queueExtrinsic]
);
const { isNominator } = useMemo(
() => extractRoles(accountId, roles),
[accountId, roles]
);
return (
<tr className={className}>
<td className='number'><h1>{isFirst && formatNumber(poolId)}</h1></td>
<td className='start'>{isFirst && metadata}</td>
<td className='address'><AddressSmall value={accountId} /></td>
<td className='number'>
{accInfo && (
<>
{!accInfo.member.points.isZero() && <FormatBalance value={accInfo.member.points} />}
{stakingInfo && (
<>
<StakingUnbonding stakingInfo={stakingInfo} />
<StakingRedeemable
isPool
stakingInfo={stakingInfo}
/>
</>
)}
</>
)}
</td>
<td className='number'>{accInfo && !accInfo.claimable.isZero() && <FormatBalance value={accInfo.claimable} />}</td>
<td className='number'>
{isFirst && nominating && (
<ListNominees
nominating={nominating}
stashId={stashId}
/>
)}
</td>
<td className='button'>
{isBondOpen && (
<BondExtra
controllerId={accountId}
onClose={toggleBond}
poolId={poolId}
/>
)}
{isNominateOpen && (
<Nominate
controllerId={accountId}
nominating={nominating}
onClose={toggleNominate}
poolId={poolId}
stashId={accountId}
targets={targets}
/>
)}
{accInfo && isUnbondOpen && (
<Unbond
controllerId={accountId}
maxUnbond={accInfo.member.points}
onClose={toggleUnbond}
poolId={poolId}
/>
)}
<Popup
key='settings'
value={
<Menu>
<Menu.Item
label={t<string>('Bond more funds')}
onClick={toggleBond}
/>
<Menu.Item
isDisabled={!accInfo || accInfo.member.points.isZero()}
label={t<string>('Unbond funds')}
onClick={toggleUnbond}
/>
<Menu.Divider />
<Menu.Item
isDisabled={!accInfo || accInfo.claimable.isZero()}
label={t<string>('Withdraw claimable')}
onClick={claimPayout}
/>
<Menu.Item
isDisabled={!stakingInfo || stakingInfo.redeemable.isZero()}
label={t<string>('Withdraw unbonded')}
onClick={withdrawUnbonded}
/>
<Menu.Divider />
<Menu.Item
isDisabled={!isNominator}
label={t<string>('Set nominees')}
onClick={toggleNominate}
/>
</Menu>
}
/>
</td>
</tr>
);
}
export default React.memo(Pool);

View File

@ -0,0 +1,93 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import React, { useRef, useState } from 'react';
import { Dropdown, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { BalanceFree } from '@polkadot/react-query';
import { BN_ZERO } from '@polkadot/util';
import useAmountError from '../../Pools/useAmountError';
import { useTranslation } from '../../translate';
import PoolInfo from '../partials/PoolInfo';
interface Props {
className?: string;
controllerId: string;
onClose: () => void;
poolId: BN;
}
const DEFAULT_TYPE = 'rewards';
function BondExtra ({ className, controllerId, onClose, poolId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [type, setType] = useState(DEFAULT_TYPE);
const [amount, setAmount] = useState(BN_ZERO);
const isAmountError = useAmountError(controllerId, amount, BN_ZERO);
const typeRef = useRef([
{ text: t<string>('Free balance'), value: 'free' },
{ text: t<string>('Pool rewards'), value: 'rewards' }
]);
return (
<Modal
className={className}
header={t<string>('Bond extra into pool')}
onClose={onClose}
size='large'
>
<Modal.Content>
<PoolInfo
controllerId={controllerId}
poolId={poolId}
/>
<Modal.Columns hint={t<string>('You can either bond a specific amount from your free balance, or all of the accumulated rewards.')}>
<Dropdown
defaultValue={DEFAULT_TYPE}
label={t<string>('type of funds to bond')}
onChange={setType}
options={typeRef.current}
/>
{type === 'free' && (
<InputBalance
autoFocus
help={t<string>('Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.')}
isError={isAmountError}
label={t<string>('additional free funds to bond')}
labelExtra={
<BalanceFree
label={<span className='label'>{t<string>('balance')}</span>}
params={controllerId}
/>
}
onChange={setAmount}
/>
)}
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={controllerId}
icon='sign-in-alt'
isDisabled={type === 'free' && isAmountError}
label={t<string>('Bond Extra')}
onStart={onClose}
params={[
type === 'free'
? { FreeBalance: amount }
: 'Rewards'
]}
tx={api.tx.nominationPools.bondExtra}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(BondExtra);

View File

@ -0,0 +1,97 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import React, { useState } from 'react';
import { InputBalance, Modal, Static, Toggle, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
import { BN_ZERO } from '@polkadot/util';
import { useTranslation } from '../../translate';
import PoolInfo from '../partials/PoolInfo';
import useUnbondDuration from '../useUnbondDuration';
interface Props {
className?: string;
controllerId: string;
maxUnbond: BN;
onClose: () => void;
poolId: BN;
}
function Unbond ({ className, controllerId, maxUnbond, onClose, poolId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [amount, setAmount] = useState(BN_ZERO);
const [withMax, setWithMax] = useState(false);
const bondedBlocks = useUnbondDuration();
const isAmountError = !amount || !maxUnbond || amount.gt(maxUnbond);
return (
<Modal
className={className}
header={t<string>('Unbond funds from pool')}
onClose={onClose}
size='large'
>
<Modal.Content>
<PoolInfo
controllerId={controllerId}
poolId={poolId}
/>
<Modal.Columns hint={t<string>('The amount to unbond. It should be less or equal to the full bonded amount.')}>
<InputBalance
autoFocus
defaultValue={maxUnbond}
help={t<string>('Amount to unbond from the current bonded amount.')}
isDisabled={withMax}
isError={isAmountError}
key={`unbondAmount-${withMax.toString()}`}
label={t<string>('amount to unbond')}
labelExtra={
<FormatBalance
label={<span className='label'>{t<string>('bonded')}</span>}
value={maxUnbond}
/>
}
maxValue={maxUnbond}
onChange={setAmount}
withMax
>
<Toggle
isOverlay
label={t<string>('all bonded')}
onChange={setWithMax}
value={withMax}
/>
</InputBalance>
{bondedBlocks?.gtn(0) && (
<Static
help={t<string>('The bonding duration for any staked funds. After this period needs to be withdrawn.')}
label={t<string>('on-chain bonding duration')}
>
<BlockToTime value={bondedBlocks} />
</Static>
)}
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={controllerId}
icon='unlock'
isDisabled={!withMax && isAmountError}
label={t<string>('Unbond')}
onStart={onClose}
params={[controllerId, withMax ? maxUnbond : amount]}
tx={api.tx.nominationPools.unbond}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(Unbond);

View File

@ -0,0 +1,48 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveSessionProgress } from '@polkadot/api-derive/types';
import type { u32 } from '@polkadot/types';
import type { PalletNominationPoolsPoolMember } from '@polkadot/types/lookup';
import type { SortedTargets } from '../../types';
import React from 'react';
import usePoolInfo from '../../Pools/usePoolInfo';
import Account from './Account';
interface Props {
count: number;
className?: string;
members: Record<string, PalletNominationPoolsPoolMember>;
poolId: u32;
sessionProgress?: DeriveSessionProgress;
targets: SortedTargets;
}
function Pool ({ className, count, members, poolId, sessionProgress, targets }: Props): React.ReactElement<Props> | null {
const info = usePoolInfo(poolId);
if (!info) {
return null;
}
return (
<>
{Object.keys(members).map((accountId, index) => (
<Account
accountId={accountId}
className={`${className || ''} ${count % 2 ? 'isEven' : 'isOdd'}`}
info={info}
isFirst={index === 0}
key={`${poolId.toString()}:${accountId}`}
poolId={poolId}
sessionProgress={sessionProgress}
targets={targets}
/>
))}
</>
);
}
export default React.memo(Pool);

View File

@ -0,0 +1,10 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletNominationPoolsPoolMember } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
export interface AccountInfo {
claimable: BN;
member: PalletNominationPoolsPoolMember;
}

View File

@ -0,0 +1,46 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
import type { PalletNominationPoolsPoolMember, PalletNominationPoolsRewardPool } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { AccountInfo } from './types';
import { useMemo } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
import { BN_ZERO, bnMax } from '@polkadot/util';
const OPT_DEL = {
transform: (opt: Option<PalletNominationPoolsPoolMember>): PalletNominationPoolsPoolMember | null =>
opt.unwrapOr(null)
};
function createInfo (member: PalletNominationPoolsPoolMember, rewardPool: PalletNominationPoolsRewardPool, poolPoints: BN, currentBalance: BN): AccountInfo {
const lastTotalEarnings = rewardPool.totalEarnings;
const currTotalEarnings = bnMax(BN_ZERO, currentBalance.sub(rewardPool.balance)).add(rewardPool.totalEarnings);
const newEarnings = bnMax(BN_ZERO, currTotalEarnings.sub(lastTotalEarnings));
const newPoints = poolPoints.mul(newEarnings);
const currentPoints = rewardPool.points.add(newPoints);
const newEarningsSinceLastClaim = bnMax(BN_ZERO, currTotalEarnings.sub(member.rewardPoolTotalEarnings));
const delegatorVirtualPoints = member.points.mul(newEarningsSinceLastClaim);
return {
claimable: delegatorVirtualPoints.isZero() || currentPoints.isZero() || currentBalance.isZero()
? BN_ZERO
: delegatorVirtualPoints.mul(currentBalance).div(currentPoints),
member
};
}
function useAccountInfoImpl (accountId: string, rewardPool: PalletNominationPoolsRewardPool, poolPoints: BN, rewardClaimable: BN): AccountInfo | undefined | null {
const { api } = useApi();
const member = useCall(api.query.nominationPools.poolMembers, [accountId], OPT_DEL);
return useMemo(
() => member && createInfo(member, rewardPool, poolPoints, rewardClaimable),
[member, poolPoints, rewardPool, rewardClaimable]
);
}
export default createNamedHook('useAccountInfo', useAccountInfoImpl);

View File

@ -0,0 +1,60 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveSessionProgress } from '@polkadot/api-derive/types';
import type { PalletStakingUnappliedSlash } from '@polkadot/types/lookup';
import type { OwnPool, SortedTargets } from '../types';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { BN } from '@polkadot/util';
import { useTranslation } from '../translate';
import Pool from './Pool';
interface Props {
allSlashes: [BN, PalletStakingUnappliedSlash[]][];
className?: string;
isInElection?: boolean;
list?: OwnPool[];
minCommission?: BN;
targets: SortedTargets;
}
function Pools ({ className, list, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const sessionProgress = useCall<DeriveSessionProgress>(api.derive.session.progress);
const hdrRef = useRef([
[t('pools'), 'start', 2],
[t('account'), 'address'],
[t('bonded')],
[t('claimable')],
[],
[]
]);
return (
<Table
className={className}
empty={list && t<string>('Not participating in any pools. Join a pool first.')}
header={hdrRef.current}
>
{list?.map(({ members, poolId }, count): React.ReactNode => (
<Pool
count={count}
key={poolId.toString()}
members={members}
poolId={poolId}
sessionProgress={sessionProgress}
targets={targets}
/>
))}
</Table>
);
}
export default React.memo(Pools);

View File

@ -4,26 +4,28 @@
import '@polkadot/api-augment';
import type { StakerState } from '@polkadot/react-hooks/types';
import type { SortedTargets } from '../types';
import type { OwnPool, SortedTargets } from '../types';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { Button, Table, ToggleGroup } from '@polkadot/react-components';
import { useAvailableSlashes } from '@polkadot/react-hooks';
import { Button, ToggleGroup } from '@polkadot/react-components';
import { useApi, useAvailableSlashes } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { BN, BN_ZERO } from '@polkadot/util';
import ElectionBanner from '../ElectionBanner';
import { useTranslation } from '../translate';
import Account from './Account';
import Accounts from './Accounts';
import NewNominator from './NewNominator';
import NewStash from './NewStash';
import NewValidator from './NewValidator';
import Pools from './Pools';
interface Props {
className?: string;
isInElection?: boolean;
minCommission?: BN;
ownPools?: OwnPool[];
ownStashes?: StakerState[];
next?: string[];
validators?: string[];
@ -85,9 +87,9 @@ function extractState (ownStashes?: StakerState[]): State {
};
}
function filterStashes (typeIndex: number, stashes: StakerState[]): StakerState[] {
function filterStashes (stashTypeIndex: number, stashes: StakerState[]): StakerState[] {
return stashes.filter(({ isStashNominating, isStashValidating }) => {
switch (typeIndex) {
switch (stashTypeIndex) {
case 1: return isStashNominating;
case 2: return isStashValidating;
case 3: return !isStashNominating && !isStashValidating;
@ -96,8 +98,8 @@ function filterStashes (typeIndex: number, stashes: StakerState[]): StakerState[
});
}
function getValue (typeIndex: number, { bondedNoms, bondedNone, bondedTotal, bondedVals }: State): BN | undefined {
switch (typeIndex) {
function getValue (stashTypeIndex: number, { bondedNoms, bondedNone, bondedTotal, bondedVals }: State): BN | undefined {
switch (stashTypeIndex) {
case 0: return bondedTotal;
case 1: return bondedNoms;
case 2: return bondedVals;
@ -106,27 +108,25 @@ function getValue (typeIndex: number, { bondedNoms, bondedNone, bondedTotal, bon
}
}
function formatTotal (typeIndex: number, state: State): React.ReactNode {
const value = getValue(typeIndex, state);
function formatTotal (stashTypeIndex: number, state: State): React.ReactNode {
const value = getValue(stashTypeIndex, state);
return value && <FormatBalance value={value} />;
}
function Actions ({ className = '', isInElection, minCommission, ownStashes, targets }: Props): React.ReactElement<Props> {
function Actions ({ className = '', isInElection, minCommission, ownPools, ownStashes, targets }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const allSlashes = useAvailableSlashes();
const [typeIndex, setTypeIndex] = useState(0);
const [, setSelected] = useState<[string, string][]>([]);
const [accTypeIndex, setAccTypeIndex] = useState(0);
const [stashTypeIndex, setStashTypeIndex] = useState(0);
const headerRef = useRef([
[t('stashes'), 'start', 2],
[t('controller'), 'address'],
[t('rewards'), 'start media--1200'],
[t('bonded'), 'number'],
[undefined, undefined, 2]
const accTypes = useRef([
{ text: t<string>('Stashed'), value: 'stash' },
{ text: t<string>('Pooled'), value: 'pool' }
]);
const typeRef = useRef([
const stashTypes = useRef([
{ text: t('All stashes'), value: 'all' },
{ text: t('Nominators'), value: 'noms' },
{ text: t('Validators'), value: 'vals' },
@ -138,74 +138,70 @@ function Actions ({ className = '', isInElection, minCommission, ownStashes, tar
[ownStashes]
);
const [isSelectable, filtered, footer] = useMemo(
const [filtered, footer] = useMemo(
() => [
false, // [1, 2].includes(typeIndex)
state.foundStashes && filterStashes(typeIndex, state.foundStashes),
state.foundStashes && filterStashes(stashTypeIndex, state.foundStashes),
(
<tr key='footer'>
<td colSpan={4} />
<td className='number'>{formatTotal(typeIndex, state)}</td>
<td className='number'>{formatTotal(stashTypeIndex, state)}</td>
<td colSpan={2} />
</tr>
)
],
[state, typeIndex]
);
const onSelectStash = useCallback(
(stashId: string, controllerId: string, isSelected: boolean) =>
setSelected((prev) =>
isSelected
? [...prev, [stashId, controllerId]]
: prev.filter(([s]) => s !== stashId)
),
[]
);
useEffect(
() => setSelected([]),
[typeIndex]
[state, stashTypeIndex]
);
return (
<div className={className}>
<Button.Group>
<ToggleGroup
onChange={setTypeIndex}
options={typeRef.current}
value={typeIndex}
/>
<NewNominator
isInElection={isInElection}
targets={targets}
/>
<NewValidator
isInElection={isInElection}
minCommission={minCommission}
targets={targets}
/>
<NewStash />
{api.consts.nominationPools && (
<ToggleGroup
onChange={setAccTypeIndex}
options={accTypes.current}
value={accTypeIndex}
/>
)}
{accTypeIndex === 0 && (
<>
<ToggleGroup
onChange={setStashTypeIndex}
options={stashTypes.current}
value={stashTypeIndex}
/>
<NewNominator
isInElection={isInElection}
targets={targets}
/>
<NewValidator
isInElection={isInElection}
minCommission={minCommission}
targets={targets}
/>
<NewStash />
</>
)}
</Button.Group>
<ElectionBanner isInElection={isInElection} />
<Table
empty={filtered && t<string>('No funds staked yet. Bond funds to validate or nominate a validator')}
footer={footer}
header={headerRef.current}
>
{filtered?.map((info): React.ReactNode => (
<Account
{accTypeIndex === 0
? (
<Accounts
allSlashes={allSlashes}
info={info}
isDisabled={isInElection}
isSelectable={isSelectable}
key={info.stashId}
footer={footer}
isInElection={isInElection}
list={filtered}
minCommission={minCommission}
onSelect={onSelectStash}
targets={targets}
/>
))}
</Table>
)
: (
<Pools
allSlashes={allSlashes}
list={ownPools}
targets={targets}
/>
)
}
</div>
);
}

View File

@ -1,7 +1,7 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { u32 } from '@polkadot/types-codec';
import type { BN } from '@polkadot/util';
import type { SortedTargets } from '../../types';
import type { NominateInfo } from './types';
@ -13,6 +13,7 @@ import { useApi, useFavorites } from '@polkadot/react-hooks';
import { MAX_NOMINATIONS, STORE_FAVS_BASE } from '../../constants';
import { useTranslation } from '../../translate';
import PoolInfo from './PoolInfo';
import SenderInfo from './SenderInfo';
interface Props {
@ -20,12 +21,13 @@ interface Props {
controllerId: string;
nominating?: string[];
onChange: (info: NominateInfo) => void;
poolId?: BN;
stashId: string;
targets: SortedTargets;
withSenders?: boolean;
}
function Nominate ({ className = '', controllerId, nominating, onChange, stashId, targets: { nominateIds = [] }, withSenders }: Props): React.ReactElement<Props> {
function Nominate ({ className = '', controllerId, nominating, onChange, poolId, stashId, targets: { nominateIds = [] }, withSenders }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [favorites] = useFavorites(STORE_FAVS_BASE);
@ -47,25 +49,36 @@ function Nominate ({ className = '', controllerId, nominating, onChange, stashId
try {
onChange({
nominateTx: selected && selected.length
? api.tx.staking.nominate(selected)
? poolId
? api.tx.nominationPools.nominate(poolId, selected)
: api.tx.staking.nominate(selected)
: null
});
} catch {
onChange({ nominateTx: null });
}
}, [api, onChange, selected]);
}, [api, onChange, poolId, selected]);
const maxNominations = api.consts.staking.maxNominations
? (api.consts.staking.maxNominations as u32).toNumber()
? (api.consts.staking.maxNominations as BN).toNumber()
: MAX_NOMINATIONS;
return (
<div className={className}>
{withSenders && (
<SenderInfo
controllerId={controllerId}
stashId={stashId}
/>
poolId
? (
<PoolInfo
controllerId={controllerId}
poolId={poolId}
/>
)
: (
<SenderInfo
controllerId={controllerId}
stashId={stashId}
/>
)
)}
<Modal.Columns
hint={

View File

@ -0,0 +1,44 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import React from 'react';
import { InputAddress, InputNumber, Modal } from '@polkadot/react-components';
import { useTranslation } from '../../translate';
interface Props {
className?: string;
controllerId?: string | null;
poolId?: BN;
}
function PoolInfo ({ className = '', controllerId, poolId }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
if (!poolId || !controllerId) {
return null;
}
return (
<Modal.Columns
className={className}
hint={t<string>('The pool and pool member that is to be affected. The transaction will be sent from the associated pool member account.')}
>
<InputNumber
defaultValue={poolId}
isDisabled
label={t<string>('pool id')}
/>
<InputAddress
defaultValue={controllerId}
isDisabled
label={t<string>('member account')}
/>
</Modal.Columns>
);
}
export default React.memo(PoolInfo);

View File

@ -0,0 +1,22 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
import type { PalletStakingSlashingSlashingSpans } from '@polkadot/types/lookup';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
const OPT_SPAN = {
transform: (optSpans: Option<PalletStakingSlashingSlashingSpans>): number =>
optSpans.isNone
? 0
: optSpans.unwrap().prior.length + 1
};
function useSlashingSpansImpl (stashId: string): number {
const { api } = useApi();
return useCall<number>(api.query.staking.slashingSpans, [stashId], OPT_SPAN) || 0;
}
export default createNamedHook('useSlashingSpans', useSlashingSpansImpl);

View File

@ -4,6 +4,7 @@
import type { TFunction } from 'i18next';
import type { DeriveStakerReward } from '@polkadot/api-derive/types';
import type { StakerState } from '@polkadot/react-hooks/types';
import type { OwnPool } from '../types';
import type { PayoutStash, PayoutValidator } from './types';
import React, { useMemo, useRef, useState } from 'react';
@ -23,6 +24,7 @@ import Validator from './Validator';
interface Props {
className?: string;
isInElection?: boolean;
ownPools?: OwnPool[];
ownValidators: StakerState[];
}
@ -156,7 +158,7 @@ function getOptions (blockTime: BN, eraLength: BN | undefined, historyDepth: BN
return eraSelection;
}
function Payouts ({ className = '', isInElection, ownValidators }: Props): React.ReactElement<Props> {
function Payouts ({ className = '', isInElection, ownPools, ownValidators }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [hasOwnValidators] = useState(() => ownValidators.length !== 0);
@ -166,12 +168,17 @@ function Payouts ({ className = '', isInElection, ownValidators }: Props): React
const historyDepth = useCall<BN>(api.query.staking.historyDepth);
const blockTime = useBlockInterval();
const poolStashes = useMemo(
() => ownPools && ownPools.map(({ stashId }) => stashId),
[ownPools]
);
const eraSelection = useMemo(
() => getOptions(blockTime, eraLength, historyDepth, t),
[blockTime, eraLength, historyDepth, t]
);
const { allRewards, isLoadingRewards } = useOwnEraRewards(eraSelection[eraSelectionIndex].value, myStashesIndex ? undefined : ownValidators);
const { allRewards, isLoadingRewards } = useOwnEraRewards(eraSelection[eraSelectionIndex].value, myStashesIndex ? undefined : ownValidators, poolStashes);
const { stashAvail, stashes, valAvail, validators } = useMemo(
() => getAvailable(allRewards),

View File

@ -11,14 +11,19 @@ import { useApi, useToggle } from '@polkadot/react-hooks';
import { BN_ZERO, bnMax } from '@polkadot/util';
import { useTranslation } from '../translate';
import useAmountError from './useAmountError';
interface Props {
className?: string;
isDisabled?: boolean;
ownAccounts?: string[];
params: Params;
}
function Create ({ className, isDisabled, params: { minCreateBond, minNominatorBond, nextPoolId } }: Props): React.ReactElement<Props> {
const MAX_META_LEN = 32;
const MIN_META_LEN = 3;
function Create ({ className, isDisabled, ownAccounts, params: { minCreateBond, minNominatorBond, nextPoolId } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [isOpen, toggleOpen] = useToggle();
@ -32,13 +37,15 @@ function Create ({ className, isDisabled, params: { minCreateBond, minNominatorB
[api, minCreateBond, minNominatorBond]
);
const isAmountError = useMemo(
() => !amount || !minValue || amount.lt(minValue),
[amount, minValue]
const isAmountError = useAmountError(accountId, amount, minValue);
const isMetaError = useMemo(
() => !metadata || (metadata.length < MIN_META_LEN) || (metadata.length > MAX_META_LEN),
[metadata]
);
const extrinsic = useMemo(
() => accountId && !isAmountError
() => amount && accountId && !isAmountError && nextPoolId
? api.tx.utility.batch([
api.tx.nominationPools.create(amount, accountId, accountId, accountId),
api.tx.nominationPools.setMetadata(nextPoolId, metadata)
@ -65,13 +72,15 @@ function Create ({ className, isDisabled, params: { minCreateBond, minNominatorB
<Modal.Content>
<Modal.Columns hint={t<string>('The origin account will also be set as the pool admin, nominator and state toggler.')}>
<InputAddress
filter={ownAccounts}
label={t<string>('create pool from')}
onChange={setAccount}
type='account'
value={accountId}
withExclude
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The initial value to assign to the pool. It is set the the maximum of the minimum bond and the minium nomination value.')}>
<Modal.Columns hint={t<string>('The initial value to assign to the pool. It is set to the maximum of the minimum bond and the minium nomination value.')}>
<InputBalance
autoFocus
defaultValue={minValue}
@ -82,8 +91,9 @@ function Create ({ className, isDisabled, params: { minCreateBond, minNominatorB
</Modal.Columns>
<Modal.Columns hint={t<string>('The metadata description to set for this pool')}>
<Input
isError={isMetaError}
label={t<string>('description')}
maxLength={32}
maxLength={MAX_META_LEN}
onChange={setMetadata}
/>
</Modal.Columns>
@ -100,7 +110,7 @@ function Create ({ className, isDisabled, params: { minCreateBond, minNominatorB
accountId={accountId}
extrinsic={extrinsic}
icon='plus'
isDisabled={!accountId || isAmountError}
isDisabled={!accountId || isAmountError || isMetaError}
label={t<string>('Create')}
onStart={toggleOpen}
/>

View File

@ -4,37 +4,29 @@
import type { BN } from '@polkadot/util';
import type { Params } from './types';
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { bnMax } from '@polkadot/util';
import { useTranslation } from '../translate';
import useAmountError from './useAmountError';
interface Props {
className?: string;
id: BN;
isDisabled?: boolean;
ownAccounts?: string[];
params: Params;
poolId: BN;
}
function Join ({ className, id, isDisabled, params: { minJoinBond, minNominatorBond } }: Props): React.ReactElement<Props> | null {
function Join ({ className, isDisabled, ownAccounts, params: { minMemberBond }, poolId }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const [isOpen, toggleOpen] = useToggle();
const [accountId, setAccount] = useState<string | null>(null);
const [amount, setAmount] = useState<BN | undefined>();
const minAmount = useMemo(
() => minJoinBond && minNominatorBond && bnMax(minJoinBond, minNominatorBond),
[minJoinBond, minNominatorBond]
);
const isAmountError = useMemo(
() => !amount || !minAmount || amount.lt(minAmount),
[amount, minAmount]
);
const isAmountError = useAmountError(accountId, amount, minMemberBond);
if (isDisabled) {
return null;
@ -44,7 +36,7 @@ function Join ({ className, id, isDisabled, params: { minJoinBond, minNominatorB
<>
<Button
icon='plus'
isDisabled={!minAmount}
isDisabled={!minMemberBond}
label={t<string>('Join')}
onClick={toggleOpen}
/>
@ -58,16 +50,18 @@ function Join ({ className, id, isDisabled, params: { minJoinBond, minNominatorB
<Modal.Content>
<Modal.Columns hint={t<string>('The account that is to join the pool.')}>
<InputAddress
filter={ownAccounts}
label={t<string>('join pool from')}
onChange={setAccount}
type='account'
value={accountId}
withExclude
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The initial value to assign to the pool. It is set the the maximum of the minimum bond and the minium nomination value.')}>
<Modal.Columns hint={t<string>('The initial value to assign to the pool. It is set to the maximum of the minimum bond and the minium nomination value.')}>
<InputBalance
autoFocus
defaultValue={minAmount}
defaultValue={minMemberBond}
isError={isAmountError}
label={t<string>('initial value')}
onChange={setAmount}
@ -81,7 +75,7 @@ function Join ({ className, id, isDisabled, params: { minJoinBond, minNominatorB
isDisabled={!accountId || isAmountError}
label={t<string>('Join')}
onStart={toggleOpen}
params={[amount, id]}
params={[amount, poolId]}
tx={api.tx.nominationPools.join}
/>
</Modal.Actions>

View File

@ -2,50 +2,69 @@
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { Params } from './types';
import type { MembersMapEntry, Params } from './types';
import React, { useMemo } from 'react';
import React, { useCallback } from 'react';
import { AddressMini, ExpanderScroll } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber, stringify } from '@polkadot/util';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import Join from './Join';
import usePoolInfo from './usePoolInfo';
interface Props {
className?: string;
id: BN;
members: MembersMapEntry[];
ownAccounts?: string[];
params: Params;
poolId: BN;
}
function Pool ({ className, id, params }: Props): React.ReactElement<Props> | null {
const info = usePoolInfo(id);
const metadata = useMemo(
() => info && info.metadata && info.metadata.length
? info.metadata.isUtf8
? info.metadata.toUtf8()
: info.metadata.toString()
: null,
[info]
function Pool ({ className, members, ownAccounts, params, poolId }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const info = usePoolInfo(poolId);
const renderMembers = useCallback(
() => members.map(({ accountId, member }, count) => (
<AddressMini
balance={member.points}
key={`${count}:${accountId}`}
value={accountId}
withBalance
withShrink
/>
)),
[members]
);
if (!info) {
return null;
}
console.log(stringify(info, 2));
return (
<tr className={className}>
<td className='number'><h1>{formatNumber(id)}</h1></td>
<td className='start'>{metadata}</td>
<td className='number'><FormatBalance value={info.bonded?.points} /></td>
<td className='number'>{formatNumber(info.bonded?.delegatorCounter)}</td>
<td className='number'><h1>{formatNumber(poolId)}</h1></td>
<td className='start'>{info.metadata}</td>
<td className='number'>{info.bonded.state.type}</td>
<td className='number'><FormatBalance value={info.bonded.points} /></td>
<td className='number media--1100'>{!info.rewardClaimable.isZero() && <FormatBalance value={info.rewardClaimable} />}</td>
<td className='number'>
{members && members.length !== 0 && (
<ExpanderScroll
empty={t<string>('No members')}
renderChildren={renderMembers}
summary={t<string>('Members ({{count}})', { replace: { count: members.length } })}
/>
)}
</td>
<td className='button'>
<Join
id={id}
isDisabled={!info.bonded?.state.isOpen}
isDisabled={!info.bonded.state.isOpen || !info.bonded.memberCounter.ltn(params.maxMembersPerPool)}
ownAccounts={ownAccounts}
params={params}
poolId={poolId}
/>
</td>
</tr>

View File

@ -2,45 +2,94 @@
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@polkadot/util';
import type { OwnPool } from '../types';
import type { Params } from './types';
import React, { useMemo } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { Table } from '@polkadot/react-components';
import { Button, Table, ToggleGroup } from '@polkadot/react-components';
import { arrayFlatten } from '@polkadot/util';
import { useTranslation } from '../translate';
import Create from './Create';
import Pool from './Pool';
import useMembers from './useMembers';
interface Props {
className?: string;
ids?: BN[];
ownPools?: OwnPool[];
params: Params;
}
function Pools ({ className, ids, params }: Props): React.ReactElement<Props> {
function Pools ({ className, ids, ownPools, params }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const membersMap = useMembers();
const [typeIndex, setTypeIndex] = useState(() => ownPools && ownPools.length ? 0 : 1);
const ownAccounts = useMemo(
() => ownPools && arrayFlatten(ownPools.map(({ members }) => Object.keys(members))),
[ownPools]
);
const noCreate = useMemo(
() => !ids || (!!params.maxPools && (ids.length > params.maxPools)),
[ids, params]
);
const filtered = useMemo(
() => ownPools && ids
? typeIndex
? ids
: ids.filter((id) => ownPools.some(({ poolId }) => id.eq(poolId)))
: undefined,
[ids, ownPools, typeIndex]
);
const header = useMemo(() => [
[t('pools'), 'start', 2],
[t('state')],
[t('points')],
[t('delegators')]
[t('claimable'), 'media--1100'],
[t('members')]
], [t]);
const poolTypes = useRef([
{ text: t('Own pools'), value: 'mine' },
{ text: t('All pools'), value: 'all' }
]);
return (
<Table
className={className}
empty={ids && t<string>('No available nomination pools')}
emptySpinner={t<string>('Retrieving nomination pools')}
header={header}
>
{ids?.map((id) => (
<Pool
id={id}
key={id.toString()}
<>
<Button.Group>
<ToggleGroup
onChange={setTypeIndex}
options={poolTypes.current}
value={typeIndex}
/>
<Create
isDisabled={noCreate}
ownAccounts={ownAccounts}
params={params}
/>
))}
</Table>
</Button.Group>
<Table
className={className}
empty={filtered && t<string>('No available nomination pools')}
emptySpinner={t<string>('Retrieving nomination pools')}
header={header}
>
{membersMap && filtered && filtered.map((poolId) => (
<Pool
key={poolId.toString()}
members={membersMap[poolId.toString()]}
ownAccounts={ownAccounts}
params={params}
poolId={poolId}
/>
))}
</Table>
</>
);
}

View File

@ -16,25 +16,25 @@ interface Props {
poolCount?: number;
}
function Summary ({ className, params: { maxDelegators, maxDelegatorsPool, maxPools }, poolCount }: Props): React.ReactElement<Props> | null {
function Summary ({ className, params: { maxMembers, maxMembersPerPool, maxPools }, poolCount }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
return (
<SummaryBox className={className}>
{maxDelegators && (
{maxMembers && (
<CardSummary label={t<string>('pools')}>
{isNumber(poolCount) && <>{formatNumber(poolCount)}&nbsp;/&nbsp;</>}{formatNumber(maxPools)}
</CardSummary>
)}
<section>
{maxDelegatorsPool && (
<CardSummary label={t<string>('max. delegators')}>
{formatNumber(maxDelegators)}
{maxMembers && (
<CardSummary label={t<string>('max. members')}>
{formatNumber(maxMembers)}
</CardSummary>
)}
{maxPools && (
<CardSummary label={t<string>('max. delegators / pool')}>
{formatNumber(maxDelegatorsPool)}
{maxMembersPerPool && (
<CardSummary label={t<string>('max. members / pool')}>
{formatNumber(maxMembersPerPool)}
</CardSummary>
)}
</section>

View File

@ -1,11 +1,10 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useMemo } from 'react';
import type { OwnPool } from '../types';
import { Button } from '@polkadot/react-components';
import React from 'react';
import Create from './Create';
import Pools from './Pools';
import Summary from './Summary';
import useParams from './useParams';
@ -13,31 +12,22 @@ import usePoolIds from './usePoolIds';
interface Props {
className?: string;
ownPools?: OwnPool[];
}
function NominationPools ({ className }: Props): React.ReactElement<Props> {
function NominationPools ({ className, ownPools }: Props): React.ReactElement<Props> {
const ids = usePoolIds();
const params = useParams();
const noCreate = useMemo(
() => !ids || (!!params.maxPools && (ids.length > params.maxPools)),
[ids, params]
);
return (
<div className={className}>
<Summary
params={params}
poolCount={ids?.length}
/>
<Button.Group>
<Create
isDisabled={noCreate}
params={params}
/>
</Button.Group>
<Pools
ids={ids}
ownPools={ownPools}
params={params}
/>
</div>

View File

@ -1,40 +1,37 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Bytes } from '@polkadot/types';
import type { AccountId } from '@polkadot/types/interfaces';
import type { PalletNominationPoolsBondedPoolInner, PalletNominationPoolsPoolMember, PalletNominationPoolsRewardPool } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { PoolAccounts } from '../types';
export interface Params {
lastPoolId: BN;
maxDelegators: number;
maxDelegatorsPool: number;
maxMembers: number;
maxMembersPerPool: number;
maxPools: number;
minCreateBond?: BN;
minJoinBond?: BN;
minMemberBond?: BN;
minNominatorBond?: BN;
nextPoolId: BN;
}
// FIXME Once available, use types from lookup
export interface PoolInfo {
bonded: {
points: BN;
state: {
isOpen: boolean;
}
delegatorCounter: BN;
roles: {
depositor: AccountId;
root: AccountId;
nominator: AccountId;
stateToggler: AccountId;
};
} | null;
reward: {
balance: BN;
totalEarnings: BN;
points: BN;
} | null;
metadata: Bytes;
export interface PoolInfoBase {
bonded: PalletNominationPoolsBondedPoolInner;
reward: PalletNominationPoolsRewardPool;
metadata: string | null;
nominating: string[];
rewardClaimable: BN;
}
export interface PoolInfo extends PoolInfoBase, PoolAccounts {
// nothing extra
}
export interface MembersMapEntry {
accountId: string;
member: PalletNominationPoolsPoolMember;
}
export type MembersMap = Record<string, MembersMapEntry[]>;

View File

@ -0,0 +1,24 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveBalancesAll } from '@polkadot/api-derive/types';
import type { BN } from '@polkadot/util';
import { useMemo } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
function useAmountErrorImpl (accountId?: string | null, amount?: BN | null, minAmount?: BN | null): boolean {
const { api } = useApi();
const balances = useCall<DeriveBalancesAll>(!!accountId && api.derive.balances.all, [accountId]);
return useMemo(
() => !amount || amount.isZero() || !minAmount || minAmount.gt(amount) || (
!!balances &&
amount.gt(balances.availableBalance.sub(api.consts.balances.existentialDeposit))
),
[api, amount, balances, minAmount]
);
}
export default createNamedHook('useAmountError', useAmountErrorImpl);

View File

@ -0,0 +1,108 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Changes } from '@polkadot/react-hooks/useEventChanges';
import type { bool, Option, StorageKey, u32, u128 } from '@polkadot/types';
import type { AccountId32, EventRecord } from '@polkadot/types/interfaces';
import type { PalletNominationPoolsPoolMember } from '@polkadot/types/lookup';
import type { MembersMap, MembersMapEntry } from './types';
import { useEffect, useState } from 'react';
import { createNamedHook, useApi, useCall, useEventChanges, useMapEntries } from '@polkadot/react-hooks';
const EMPTY_START: AccountId32[] = [];
const OPT_ENTRIES = {
transform: (entries: [StorageKey<[AccountId32]>, Option<PalletNominationPoolsPoolMember>][]): MembersMap =>
entries.reduce((all: MembersMap, [{ args: [accountId] }, optMember]) => {
if (optMember.isSome) {
const member = optMember.unwrap();
const poolId = member.poolId.toString();
if (!all[poolId]) {
all[poolId] = [];
}
all[poolId].push({
accountId: accountId.toString(),
member
});
}
return all;
}, {})
};
const OPT_MULTI = {
transform: ([[ids], values]: [[AccountId32[]], Option<PalletNominationPoolsPoolMember>[]]): MembersMapEntry[] =>
ids
.filter((_, i) => values[i].isSome)
.map((accountId, i) => ({
accountId: accountId.toString(),
member: values[i].unwrap()
})),
withParamsTransform: true
};
function filterEvents (records: EventRecord[]): Changes<AccountId32> {
const added: AccountId32[] = [];
const removed: AccountId32[] = [];
records.forEach(({ event: { data, method } }): void => {
if (method === 'Bonded') {
const [accountId,,, joined] = data as unknown as [AccountId32, u32, u128, bool];
if (joined.isTrue) {
added.push(accountId);
}
}
});
return { added, removed };
}
function interleave (prev: MembersMap, additions: MembersMapEntry[]): MembersMap {
return additions.reduce<MembersMap>((all, entry) => {
const poolId = entry.member.poolId.toString();
const arr: MembersMapEntry[] = [];
if (all[poolId]) {
all[poolId].forEach((prev): void => {
if (prev.accountId !== entry.accountId) {
arr.push(prev);
}
});
}
arr.push(entry);
all[poolId] = arr;
return all;
}, { ...prev });
}
function useMembersImpl (): MembersMap | undefined {
const { api } = useApi();
const [membersMap, setMembersMap] = useState<MembersMap | undefined>();
const queryMap = useMapEntries(api.query.nominationPools.poolMembers, OPT_ENTRIES);
const ids = useEventChanges([
api.events.nominationPools.Bonded
], filterEvents, EMPTY_START);
const additions = useCall(ids.length !== 0 && api.query.nominationPools.poolMembers.multi, [ids], OPT_MULTI);
// initial entries
useEffect((): void => {
queryMap && setMembersMap(queryMap);
}, [queryMap]);
// additions via events
useEffect((): void => {
additions && setMembersMap((prev) => prev && interleave(prev, additions));
}, [additions]);
return membersMap;
}
export default createNamedHook('useMembers', useMembersImpl);

View File

@ -6,23 +6,24 @@ import type { BN } from '@polkadot/util';
import type { Params } from './types';
import { createNamedHook, useApi, useCallMulti } from '@polkadot/react-hooks';
import { BN_ONE, BN_ZERO } from '@polkadot/util';
import { BN_ONE, BN_ZERO, bnMax } from '@polkadot/util';
const queryOptions = {
const OPT_MULTI = {
defaultValue: {
lastPoolId: BN_ZERO,
maxDelegators: 0,
maxDelegatorsPool: 0,
maxMembers: 0,
maxMembersPerPool: 0,
maxPools: 0,
nextPoolId: BN_ONE
},
transform: ([lastPoolId, maxDelegators, maxDelegatorsPerPool, maxPools, minCreateBond, minJoinBond, minNominatorBond]: [BN, Option<u32>, Option<u32>, Option<u32>, BN, BN, BN]): Params => ({
transform: ([lastPoolId, maxPoolMembers, maxPoolMembersPerPool, maxPools, minCreateBond, minJoinBond, minNominatorBond]: [BN, Option<u32>, Option<u32>, Option<u32>, BN, BN, BN]): Params => ({
lastPoolId,
maxDelegators: maxDelegators.unwrapOr(BN_ZERO).toNumber(),
maxDelegatorsPool: maxDelegatorsPerPool.unwrapOr(BN_ZERO).toNumber(),
maxMembers: maxPoolMembers.unwrapOr(BN_ZERO).toNumber(),
maxMembersPerPool: maxPoolMembersPerPool.unwrapOr(BN_ZERO).toNumber(),
maxPools: maxPools.unwrapOr(BN_ZERO).toNumber(),
minCreateBond,
minJoinBond,
minMemberBond: minJoinBond && minNominatorBond && bnMax(minJoinBond, minNominatorBond),
minNominatorBond,
nextPoolId: lastPoolId.add(BN_ONE)
})
@ -39,7 +40,7 @@ function useParamsImpl (): Params {
api.query.nominationPools.minCreateBond,
api.query.nominationPools.minJoinBond,
api.query.staking.minNominatorBond
], queryOptions);
], OPT_MULTI);
}
export default createNamedHook('useParams', useParamsImpl);

View File

@ -7,14 +7,14 @@ import type { EventRecord } from '@polkadot/types/interfaces';
import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@polkadot/react-hooks';
const keyOptions = {
const OPT_KEYS = {
transform: (keys: StorageKey<[u32]>[]): u32[] =>
keys
.map(({ args: [poolId] }) => poolId)
.sort((a, b) => a.cmp(b))
};
function filter (records: EventRecord[]): Changes<u32> {
function filterEvents (records: EventRecord[]): Changes<u32> {
const added: u32[] = [];
const removed: u32[] = [];
@ -31,12 +31,12 @@ function filter (records: EventRecord[]): Changes<u32> {
function usePoolIdsImpl (): u32[] | undefined {
const { api } = useApi();
const startValue = useMapKeys(api.query.nominationPools.bondedPools, keyOptions);
const startValue = useMapKeys(api.query.nominationPools.bondedPools, OPT_KEYS);
return useEventChanges([
api.events.nominationPools.Created,
api.events.nominationPools.Destroyed
], filter, startValue);
], filterEvents, startValue);
}
export default createNamedHook('usePoolIds', usePoolIdsImpl);

View File

@ -1,29 +1,57 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
import type { Codec } from '@polkadot/types/types';
import type { Bytes, Option } from '@polkadot/types';
import type { FrameSystemAccountInfo, PalletNominationPoolsBondedPoolInner, PalletNominationPoolsRewardPool, PalletStakingNominations } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { PoolInfo } from './types';
import type { PoolInfo, PoolInfoBase } from './types';
import { useMemo } from 'react';
import { createNamedHook, useApi, useCallMulti } from '@polkadot/react-hooks';
import { BN_ZERO, bnMax } from '@polkadot/util';
const options = {
transform: ([bonded, metadata, reward]: [Option<PoolInfo['bonded'] & Codec>, PoolInfo['metadata'], Option<PoolInfo['reward'] & Codec>]): PoolInfo => ({
bonded: bonded.unwrapOr(null),
metadata,
reward: reward.unwrapOr(null)
})
import usePoolAccounts from '../usePoolAccounts';
const OPT_MULTI = {
defaultValue: null,
transform: ([optBonded, metadata, optReward, optNominating, accountInfo]: [Option<PalletNominationPoolsBondedPoolInner>, Bytes, Option<PalletNominationPoolsRewardPool>, Option<PalletStakingNominations>, FrameSystemAccountInfo]): PoolInfoBase | null =>
optBonded.isSome && optReward.isSome
? {
bonded: optBonded.unwrap(),
metadata: metadata.length
? metadata.isUtf8
? metadata.toUtf8()
: metadata.toString()
: null,
nominating: optNominating
.unwrapOr({ targets: [] })
.targets.map((n) => n.toString()),
reward: optReward.unwrap(),
rewardClaimable: accountInfo.data.free
}
: null
};
function usePoolInfoImpl (id: BN): PoolInfo {
function usePoolInfoImpl (poolId: BN): PoolInfo | null | undefined {
const { api } = useApi();
const accounts = usePoolAccounts(poolId);
const baseInfo = useCallMulti([
[api.query.nominationPools.bondedPools, poolId],
[api.query.nominationPools.metadata, poolId],
[api.query.nominationPools.rewardPools, poolId],
[api.query.staking.nominators, accounts.stashId],
[api.query.system.account, accounts.rewardId]
], OPT_MULTI);
return useCallMulti([
[api.query.nominationPools.bondedPools, id],
[api.query.nominationPools.metadata, id],
[api.query.nominationPools.rewardPools, id]
], options);
return useMemo(
() => baseInfo && {
...accounts,
...baseInfo,
rewardClaimable: bnMax(BN_ZERO, baseInfo.rewardClaimable.sub(api.consts.balances.existentialDeposit))
},
[api, baseInfo, accounts]
);
}
export default createNamedHook('usePoolInfo', usePoolInfoImpl);

View File

@ -26,6 +26,7 @@ import Slashes from './Slashes';
import Targets from './Targets';
import { useTranslation } from './translate';
import useNominations from './useNominations';
import useOwnPools from './useOwnPools';
import useSortedTargets from './useSortedTargets';
import Validators from './Validators';
@ -60,6 +61,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
api.query.session.validators,
(api.query.parasShared || api.query.shared)?.activeValidatorIndices
], OPT_MULTI);
const ownPools = useOwnPools();
const ownStashes = useOwnStashInfos();
const slashes = useAvailableSlashes();
const targets = useSortedTargets(favorites, withLedger);
@ -147,11 +149,12 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
<Route path={`${basePath}/payout`}>
<Payouts
isInElection={isInElection}
ownPools={ownPools}
ownValidators={ownValidators}
/>
</Route>
<Route path={`${basePath}/pools`}>
<Pools />
<Pools ownPools={ownPools} />
</Route>
<Route path={[`${basePath}/query/:value`, `${basePath}/query`]}>
<Query />
@ -179,6 +182,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
className={pathname === `${basePath}/actions` ? '' : '--hidden'}
isInElection={isInElection}
minCommission={minCommission}
ownPools={ownPools}
ownStashes={ownStashes}
targets={targets}
/>

View File

@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
import type { Inflation } from '@polkadot/react-hooks/types';
import type { u32 } from '@polkadot/types';
import type { AccountId, Balance, BlockNumber, EraIndex, Exposure, Hash, SessionIndex, ValidatorPrefs, ValidatorPrefsTo196 } from '@polkadot/types/interfaces';
import type { PalletNominationPoolsPoolMember } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
export type Nominators = Record<string, string[]>;
@ -94,3 +96,17 @@ export interface SortedTargets {
validatorIds?: string[];
waitingIds?: string[];
}
export interface PoolAccounts {
rewardId: string;
stashId: string;
}
export interface OwnPoolBase {
members: Record<string, PalletNominationPoolsPoolMember>;
poolId: u32;
}
export interface OwnPool extends OwnPoolBase, PoolAccounts {
// nothing additional, only combined
}

View File

@ -0,0 +1,50 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option } from '@polkadot/types';
import type { PalletNominationPoolsPoolMember } from '@polkadot/types/lookup';
import type { OwnPool, OwnPoolBase } from './types';
import { useMemo } from 'react';
import { createNamedHook, useAccounts, useApi, useCall } from '@polkadot/react-hooks';
import { createAccount } from './usePoolAccounts';
const OPT_MULTI = {
transform: ([[ids], opts]: [[string[]], Option<PalletNominationPoolsPoolMember>[]]): OwnPoolBase[] =>
ids.reduce((pools: OwnPoolBase[], accountId, index): OwnPoolBase[] => {
if (opts[index].isSome) {
const member = opts[index].unwrap();
let entry = pools.find(({ poolId }) => poolId.eq(member.poolId));
if (!entry) {
entry = { members: {}, poolId: member.poolId };
pools.push(entry);
}
entry.members[accountId] = member;
}
return pools;
}, []),
withParamsTransform: true
};
function useOwnPoolsImpl (): OwnPool[] | undefined {
const { api } = useApi();
const { allAccounts } = useAccounts();
const base = useCall(api.query.nominationPools?.poolMembers.multi, [allAccounts], OPT_MULTI);
return useMemo(
() => base && base.map((base) => ({
...base,
rewardId: createAccount(api, base.poolId, 1),
stashId: createAccount(api, base.poolId, 0)
})),
[api, base]
);
}
export default createNamedHook('useOwnPools', useOwnPoolsImpl);

View File

@ -0,0 +1,35 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { BN } from '@polkadot/util';
import type { PoolAccounts } from './types';
import { useMemo } from 'react';
import { createNamedHook, useApi } from '@polkadot/react-hooks';
import { bnToU8a, stringToU8a, u8aConcat } from '@polkadot/util';
const EMPTY_H256 = new Uint8Array(32);
const ADDR_PREFIX = stringToU8a('modlpy/npols');
export function createAccount (api: ApiPromise, poolId: BN, index: number): string {
return api.registry.createType(
'AccountId32',
u8aConcat(ADDR_PREFIX, new Uint8Array([index]), bnToU8a(poolId, { bitLength: 32 }), EMPTY_H256)
).toString();
}
function usePoolAccountsImpl (poolId: BN): PoolAccounts {
const { api } = useApi();
return useMemo(
() => ({
rewardId: createAccount(api, poolId, 1),
stashId: createAccount(api, poolId, 0)
}),
[api, poolId]
);
}
export default createNamedHook('usePoolAccounts', usePoolAccountsImpl);

View File

@ -14,10 +14,10 @@
"version": "0.113.2-1",
"dependencies": {
"@babel/runtime": "^7.17.9",
"@polkadot/api": "^8.2.2-1",
"@polkadot/api": "^8.2.2-2",
"@polkadot/extension-compat-metamask": "^0.43.2-1",
"@polkadot/extension-dapp": "^0.43.2-1",
"@polkadot/rpc-provider": "^8.2.2-1",
"@polkadot/rpc-provider": "^8.2.2-2",
"fflate": "^0.7.3",
"rxjs": "^7.5.5"
}

View File

@ -39,6 +39,7 @@ interface Props {
type?: KeyringOption$Type;
value?: string | Uint8Array | string[] | null;
withEllipsis?: boolean;
withExclude?: boolean;
withLabel?: boolean;
}
@ -241,11 +242,19 @@ class InputAddress extends React.PureComponent<Props, State> {
}
private getFiltered (): Option[] {
const { filter, optionsAll, type = DEFAULT_TYPE } = this.props;
const { filter, optionsAll, type = DEFAULT_TYPE, withExclude = false } = this.props;
return !optionsAll
? []
: dedupe(optionsAll[type]).filter(({ value }) => !filter || (!!value && filter.includes(value)));
: dedupe(optionsAll[type]).filter(({ value }) =>
!filter || (
!!value && (
withExclude
? !filter.includes(value)
: filter.includes(value)
)
)
);
}
private onChange = (address: string): void => {

View File

@ -4,6 +4,7 @@
import type { DeriveStakingAccount } from '@polkadot/api-derive/types';
import type { Option } from '@polkadot/types';
import type { SlashingSpans } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';
import React from 'react';
@ -13,23 +14,30 @@ import { FormatBalance } from '@polkadot/react-query';
import { useTranslation } from './translate';
import TxButton from './TxButton';
interface Props {
className?: string;
stakingInfo?: DeriveStakingAccount;
interface DeriveStakingAccountPartial {
controllerId: DeriveStakingAccount['controllerId'] | string;
stashId: DeriveStakingAccount['stashId'] | string;
redeemable?: BN;
}
const transformSpan = {
interface Props {
className?: string;
isPool?: boolean;
stakingInfo?: DeriveStakingAccountPartial;
}
const OPT_SPAN = {
transform: (optSpans: Option<SlashingSpans>): number =>
optSpans.isNone
? 0
: optSpans.unwrap().prior.length + 1
};
function StakingRedeemable ({ className = '', stakingInfo }: Props): React.ReactElement<Props> | null {
function StakingRedeemable ({ className = '', isPool, stakingInfo }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const { allAccounts } = useAccounts();
const { t } = useTranslation();
const spanCount = useCall<number>(api.query.staking.slashingSpans, [stakingInfo?.stashId], transformSpan);
const spanCount = useCall<number>(api.query.staking.slashingSpans, [stakingInfo?.stashId], OPT_SPAN);
if (!stakingInfo?.redeemable?.gtn(0)) {
return null;
@ -46,12 +54,17 @@ function StakingRedeemable ({ className = '', stakingInfo }: Props): React.React
isIcon
key='unlock'
params={
api.tx.staking.withdrawUnbonded.meta.args.length === 1
? [spanCount]
: []
isPool
? [stakingInfo.controllerId, spanCount]
: api.tx.staking.withdrawUnbonded.meta.args.length === 1
? [spanCount]
: []
}
tooltip={t<string>('Withdraw these unbonded funds')}
tx={api.tx.staking.withdrawUnbonded}
tx={
isPool
? api.tx.nominationPools.withdrawUnbonded
: api.tx.staking.withdrawUnbonded}
/>
)
: <span className='icon-void'>&nbsp;</span>}

View File

@ -1,7 +1,7 @@
// Copyright 2017-2022 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveSessionProgress, DeriveStakingAccount, DeriveUnlocking } from '@polkadot/api-derive/types';
import type { DeriveSessionProgress, DeriveStakingAccount } from '@polkadot/api-derive/types';
import React, { useMemo } from 'react';
import styled from 'styled-components';
@ -14,20 +14,30 @@ import Icon from './Icon';
import Tooltip from './Tooltip';
import { useTranslation } from './translate';
interface Unlocking {
remainingEras: BN;
value: BN;
}
interface DeriveStakingAccountPartial {
accountId: DeriveStakingAccount['accountId'] | string;
unlocking?: Unlocking[];
}
interface Props {
iconPosition: 'left' | 'right';
className?: string;
stakingInfo?: DeriveStakingAccount;
stakingInfo?: DeriveStakingAccountPartial;
}
function extractTotals (stakingInfo?: DeriveStakingAccount, progress?: DeriveSessionProgress): [[DeriveUnlocking, BN, BN][], BN] {
function extractTotals (stakingInfo?: DeriveStakingAccountPartial, progress?: DeriveSessionProgress): [[Unlocking, BN, BN][], BN] {
if (!stakingInfo?.unlocking || !progress) {
return [[], BN_ZERO];
}
const mapped = stakingInfo.unlocking
.filter(({ remainingEras, value }) => value.gt(BN_ZERO) && remainingEras.gt(BN_ZERO))
.map((unlock): [DeriveUnlocking, BN, BN] => [
.map((unlock): [Unlocking, BN, BN] => [
unlock,
unlock.remainingEras,
unlock.remainingEras

View File

@ -1,15 +1,21 @@
// Copyright 2017-2022 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import { Button } from '@polkadot/react-components';
interface Option {
isDisabled?: boolean;
text: string;
value: number;
}
interface Props {
className?: string;
onChange: (index: number) => void;
options: { isDisabled?: boolean, text: string, value: number }[];
options: (Option | null | false)[];
value: number;
}
@ -45,13 +51,18 @@ function ToggleIndex ({ index, isDisabled, isSelected, onChange, text }: ToggleP
const ToggleIndexMemo = React.memo(ToggleIndex);
function ToggleGroup ({ className = '', onChange, options, value }: Props): React.ReactElement<Props> | null {
if (!options.length || !options[0].value) {
const available = useMemo(
() => options.filter((o): o is Option => !!o),
[options]
);
if (!available.length || !available[0].value) {
return null;
}
return (
<div className={`ui--ToggleGroup ${className}`}>
{options.map(({ isDisabled, text }, index): React.ReactNode => (
{available.map(({ isDisabled, text }, index): React.ReactNode => (
<ToggleIndexMemo
index={index}
isDisabled={isDisabled}

View File

@ -8,6 +8,8 @@ import type { MountedRef } from './useIsMountedRef';
import { useEffect, useRef, useState } from 'react';
import { isUndefined } from '@polkadot/util';
import { useApi } from './useApi';
import { handleError, transformIdentity, unsubscribe } from './useCall';
import { useIsMountedRef } from './useIsMountedRef';
@ -68,7 +70,7 @@ export function useCallMulti <T> (calls?: QueryableStorageMultiArg<'promise'>[]
const { api } = useApi();
const mountedRef = useIsMountedRef();
const tracker = useRef<Tracker>({ error: null, fn: null, isActive: false, serialized: null, subscriber: null, type: 'useCallMulti' });
const [value, setValue] = useState<T>(() => (options || {}).defaultValue || [] as unknown as T);
const [value, setValue] = useState<T>(() => (isUndefined((options || {}).defaultValue) ? [] : (options || {}).defaultValue) as unknown as T);
// initial effect, we need an un-subscription
useEffect((): () => void => {

View File

@ -42,6 +42,8 @@ const EMPTY_STATE: State = {
rewardCount: 0
};
const OPT_REWARDS = { withParams: true };
function getRewards ([[stashIds], available]: [[string[]], DeriveStakerReward[][]]): State {
const allRewards: Record<string, DeriveStakerReward[]> = {};
@ -99,14 +101,14 @@ function getValRewards (api: ApiPromise, validatorEras: ValidatorWithEras[], era
};
}
function useOwnEraRewardsImpl (maxEras?: number, ownValidators?: StakerState[]): State {
function useOwnEraRewardsImpl (maxEras?: number, ownValidators?: StakerState[], additional?: string[]): State {
const { api } = useApi();
const mountedRef = useIsMountedRef();
const stashIds = useOwnStashIds();
const stashIds = useOwnStashIds(additional);
const allEras = useCall<EraIndex[]>(api.derive.staking?.erasHistoric);
const [{ filteredEras, validatorEras }, setFiltered] = useState<Filtered>(EMPTY_FILTERED);
const [state, setState] = useState<State>(EMPTY_STATE);
const stakerRewards = useCall<[[string[]], DeriveStakerReward[][]]>(!ownValidators?.length && !!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], { withParams: true });
const stakerRewards = useCall<[[string[]], DeriveStakerReward[][]]>(!ownValidators?.length && !!filteredEras.length && stashIds && api.derive.staking?.stakerRewardsMultiEras, [stashIds, filteredEras], OPT_REWARDS);
const erasPoints = useCall<DeriveEraPoints[]>(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasPoints, [filteredEras, false]);
const erasRewards = useCall<DeriveEraRewards[]>(!!validatorEras.length && !!filteredEras.length && api.derive.staking._erasRewards, [filteredEras, false]);

View File

@ -31,26 +31,32 @@ function getStashes (allAccounts: string[], ownBonded: Option<AccountId>[], ownL
return result;
}
function useOwnStashesImpl (): [string, IsInKeyring][] | undefined {
const { allAccounts, hasAccounts } = useAccounts();
function useOwnStashesImpl (additional?: string[]): [string, IsInKeyring][] | undefined {
const { allAccounts } = useAccounts();
const { api } = useApi();
const ownBonded = useCall<Option<AccountId>[]>(hasAccounts && api.query.staking?.bonded.multi, [allAccounts]);
const ownLedger = useCall<Option<StakingLedger>[]>(hasAccounts && api.query.staking?.ledger.multi, [allAccounts]);
const ids = useMemo(
() => allAccounts.concat(additional || []),
[allAccounts, additional]
);
const ownBonded = useCall<Option<AccountId>[]>(ids.length !== 0 && api.query.staking?.bonded.multi, [ids]);
const ownLedger = useCall<Option<StakingLedger>[]>(ids.length !== 0 && api.query.staking?.ledger.multi, [ids]);
return useMemo(
() => hasAccounts
() => ids.length
? ownBonded && ownLedger
? getStashes(allAccounts, ownBonded, ownLedger)
? getStashes(ids, ownBonded, ownLedger)
: undefined
: [],
[allAccounts, hasAccounts, ownBonded, ownLedger]
[ids, ownBonded, ownLedger]
);
}
export const useOwnStashes = createNamedHook('useOwnStashes', useOwnStashesImpl);
function useOwnStashIdsImpl (): string[] | undefined {
const ownStashes = useOwnStashes();
function useOwnStashIdsImpl (additional?: string[]): string[] | undefined {
const ownStashes = useOwnStashes(additional);
return useMemo(
() => ownStashes

View File

@ -17,7 +17,7 @@
"testcontainers": "^8.7.2"
},
"devDependencies": {
"@polkadot/types-support": "^8.2.2-1",
"@polkadot/types-support": "^8.2.2-2",
"@testing-library/jest-dom": "^5.16.4",
"tsconfig-paths": "^3.14.1"
}

216
yarn.lock
View File

@ -2573,90 +2573,90 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/api-augment@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/api-augment@npm:8.2.2-1"
"@polkadot/api-augment@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/api-augment@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/api-base": 8.2.2-1
"@polkadot/rpc-augment": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/types-augment": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/api-base": 8.2.2-2
"@polkadot/rpc-augment": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/types-augment": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/util": ^9.0.1
checksum: 7ec66b8f58c19cd03fee6024b73bd61e522c81ad12b50474974eaf28e4930db74e5eeddfa1cdbe116b3106115ae7e9c5878d007504e128851c173beb3119e8a3
checksum: c4329b71adfcd814fc6f29ad3f2a7191aca9996ad2a295a82fe41a43644d66ceb825100a5ebbd7cb15031c565b2a8fd0233534ea4e1bf55ce54c7e67dd29ed77
languageName: node
linkType: hard
"@polkadot/api-base@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/api-base@npm:8.2.2-1"
"@polkadot/api-base@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/api-base@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/rpc-core": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/rpc-core": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/util": ^9.0.1
rxjs: ^7.5.5
checksum: dfd5f495aa2bc0d0802822ea09ecc834f636b5373db2529fcc33bbe120552cc1394c13a605bd40110914e841124d7cd30772942d8e98237d2e1f052217aff73d
checksum: d7e92a3454afaef132456b1a4aa95f730ff4b4363b7618bb25233fcb94410002192f5cc71647edaca84db6a657becdacb7c048456f7ade690232d4adbf1428cc
languageName: node
linkType: hard
"@polkadot/api-contract@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/api-contract@npm:8.2.2-1"
"@polkadot/api-contract@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/api-contract@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/api": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/types-create": 8.2.2-1
"@polkadot/api": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/types-create": 8.2.2-2
"@polkadot/util": ^9.0.1
"@polkadot/util-crypto": ^9.0.1
rxjs: ^7.5.5
checksum: 3f5abf489ce65644ce6cb48a417b5d4d022c6cd52abe0a01b1128d55892830ebe733400bb8a8053c27720f413e9fe281f32d54f72d24eecd2f8b1d4c2c85f87e
checksum: a3a2dd04ea7f803bb69a9ae026b0805ef0b77bc061a3e5445dc3f7382a32e94ffad9dac2d90617205327d74ec2e06f5620767446f58d4d2382eb2c377ec71afb
languageName: node
linkType: hard
"@polkadot/api-derive@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/api-derive@npm:8.2.2-1"
"@polkadot/api-derive@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/api-derive@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/api": 8.2.2-1
"@polkadot/api-augment": 8.2.2-1
"@polkadot/api-base": 8.2.2-1
"@polkadot/rpc-core": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/api": 8.2.2-2
"@polkadot/api-augment": 8.2.2-2
"@polkadot/api-base": 8.2.2-2
"@polkadot/rpc-core": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/util": ^9.0.1
"@polkadot/util-crypto": ^9.0.1
rxjs: ^7.5.5
checksum: e9d676c682d3bc329ebb04e7b6a12380ce681c5cebc0c9b882d4691be16aaea1c11db30022cb89445738b026f1efc52141fa16d046106a730df80553427a31ef
checksum: 428e5dbd291a1da87fcb07aec885f4f4d1d6944f9e6e15e49a02d099d679542cb47cbe421ef35624d941b4f60c4d8d092544d3aff42e31227fc4c749f21e8369
languageName: node
linkType: hard
"@polkadot/api@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/api@npm:8.2.2-1"
"@polkadot/api@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/api@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/api-augment": 8.2.2-1
"@polkadot/api-base": 8.2.2-1
"@polkadot/api-derive": 8.2.2-1
"@polkadot/api-augment": 8.2.2-2
"@polkadot/api-base": 8.2.2-2
"@polkadot/api-derive": 8.2.2-2
"@polkadot/keyring": ^9.0.1
"@polkadot/rpc-augment": 8.2.2-1
"@polkadot/rpc-core": 8.2.2-1
"@polkadot/rpc-provider": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/types-augment": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/types-create": 8.2.2-1
"@polkadot/types-known": 8.2.2-1
"@polkadot/rpc-augment": 8.2.2-2
"@polkadot/rpc-core": 8.2.2-2
"@polkadot/rpc-provider": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/types-augment": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/types-create": 8.2.2-2
"@polkadot/types-known": 8.2.2-2
"@polkadot/util": ^9.0.1
"@polkadot/util-crypto": ^9.0.1
eventemitter3: ^4.0.7
rxjs: ^7.5.5
checksum: 1b04dc99366a84ae462e36d91c184a7f03e7806065317d2eb697d5604021c359ce9a44a9c60a5d9d0306e87aee481a4fc6b463a66b7201cdcbfb0b86a23279e1
checksum: 1afdcdbbfa697f853a155258c3f15f2265ec254cd5014fa638dd50f1fa64135a0a2d2ecf5399bcd054b09a5485c86f67e4b60c3fda501e96e5a65f697fa6dcba
languageName: node
linkType: hard
@ -2732,7 +2732,7 @@ __metadata:
resolution: "@polkadot/app-contracts@workspace:packages/page-contracts"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/api-contract": ^8.2.2-1
"@polkadot/api-contract": ^8.2.2-2
languageName: unknown
linkType: soft
@ -2950,10 +2950,10 @@ __metadata:
"@metaverse-network-sdk/type-definitions": ^0.0.1-13
"@parallel-finance/type-definitions": 1.6.5
"@phala/typedefs": 0.2.30
"@polkadot/api": ^8.2.2-1
"@polkadot/api-derive": ^8.2.2-1
"@polkadot/api": ^8.2.2-2
"@polkadot/api-derive": ^8.2.2-2
"@polkadot/networks": ^9.0.1
"@polkadot/types": ^8.2.2-1
"@polkadot/types": ^8.2.2-2
"@polkadot/util": ^9.0.1
"@polkadot/x-fetch": ^9.0.1
"@polymathnetwork/polymesh-types": 0.0.2
@ -3257,10 +3257,10 @@ __metadata:
resolution: "@polkadot/react-api@workspace:packages/react-api"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/api": ^8.2.2-1
"@polkadot/api": ^8.2.2-2
"@polkadot/extension-compat-metamask": ^0.43.2-1
"@polkadot/extension-dapp": ^0.43.2-1
"@polkadot/rpc-provider": ^8.2.2-1
"@polkadot/rpc-provider": ^8.2.2-2
fflate: ^0.7.3
rxjs: ^7.5.5
languageName: unknown
@ -3395,41 +3395,41 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/rpc-augment@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/rpc-augment@npm:8.2.2-1"
"@polkadot/rpc-augment@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/rpc-augment@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/rpc-core": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/rpc-core": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/util": ^9.0.1
checksum: a0220d38d9d32f47846dfaf20f9c375b641e4fc90349252f25cc77b6c006060ab45d9344f28aed72bbfa8604210af692246b57c73031cca3ac4962a01b1b972f
checksum: 7e13d6b6935375cc61a8a2d4489a82f5a405ad250cc67eca64e161bf3bad988d6b196d187ac3e7e3acb26b29fb479e7be91d40f6b8341d4153c92ef4728d3378
languageName: node
linkType: hard
"@polkadot/rpc-core@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/rpc-core@npm:8.2.2-1"
"@polkadot/rpc-core@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/rpc-core@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/rpc-augment": 8.2.2-1
"@polkadot/rpc-provider": 8.2.2-1
"@polkadot/types": 8.2.2-1
"@polkadot/rpc-augment": 8.2.2-2
"@polkadot/rpc-provider": 8.2.2-2
"@polkadot/types": 8.2.2-2
"@polkadot/util": ^9.0.1
rxjs: ^7.5.5
checksum: cee6de55a9fead02523105edc2377f2888fb55223fc5cf6939382b5940cf261012156c70fe6d8ec22c14f2a32ca3bf0321581da5a4ea04329f8cee4c4744e126
checksum: 07307ed5fbab35328ead6631cd6f2a6db17119dc29aaddbda8b4d2f277ea68824412dc28c295091ef35a87d3914324e6e1b433fe7216b629ed66dd554d9a9767
languageName: node
linkType: hard
"@polkadot/rpc-provider@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/rpc-provider@npm:8.2.2-1"
"@polkadot/rpc-provider@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/rpc-provider@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/keyring": ^9.0.1
"@polkadot/types": 8.2.2-1
"@polkadot/types-support": 8.2.2-1
"@polkadot/types": 8.2.2-2
"@polkadot/types-support": 8.2.2-2
"@polkadot/util": ^9.0.1
"@polkadot/util-crypto": ^9.0.1
"@polkadot/x-fetch": ^9.0.1
@ -3439,7 +3439,7 @@ __metadata:
eventemitter3: ^4.0.7
mock-socket: ^9.1.3
nock: ^13.2.4
checksum: d03398fdcb0cc45cd5f3f29043a0cbcb13ea7bcd57e441c6d46d45be50228ed190f4a0c72a6f075ea269caa93d04b285e5ee7403f430d4b2277475dfd66d2aee
checksum: bcf2750b9e3d59d0aacaf4c300ce0dff67e17e89b390a27090919d2fbf20203798af8ee0e7f8438e9d38229b9ace4b0fe3d49721b8cb3c4296d288a015c6bda6
languageName: node
linkType: hard
@ -3447,7 +3447,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@polkadot/test-support@workspace:packages/test-support"
dependencies:
"@polkadot/types-support": ^8.2.2-1
"@polkadot/types-support": ^8.2.2-2
"@testing-library/jest-dom": ^5.16.4
"@testing-library/react": ^13.1.1
testcontainers: ^8.7.2
@ -3455,76 +3455,76 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/types-augment@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/types-augment@npm:8.2.2-1"
"@polkadot/types-augment@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/types-augment@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/types": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/types": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/util": ^9.0.1
checksum: c6a903335f36804ec03477c74ed4549403005520ab62fb2450c9238221de123e58afe43a600a113bfe00c04be010c48169d8ae85ca9784a5a4ef047d8d9ac936
checksum: a46fb01ad829f741e7ea32da8b0c24e7abce30f58fbde72d72ca92cfab3bccceca447dc0e255a01948adced7241b223464851bac8c907519697f640b86a3e0b6
languageName: node
linkType: hard
"@polkadot/types-codec@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/types-codec@npm:8.2.2-1"
"@polkadot/types-codec@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/types-codec@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/util": ^9.0.1
checksum: 756d1b7d773259260400e3e711d435a8caeef642d221e1567fb7d565747eefcc9d8734320f773eacc3a4642cbc348b8be8edc5a1926fbd44ca67b909a214d731
checksum: 7b996fe023609774ee29fc2f8920f3810e01ccba8108d3f86e63637c236a23bf02d96bab2d24e90ad059256c98aee1daf7911748801214e99d5e4beb5de65828
languageName: node
linkType: hard
"@polkadot/types-create@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/types-create@npm:8.2.2-1"
"@polkadot/types-create@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/types-create@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/types-codec": 8.2.2-1
"@polkadot/types-codec": 8.2.2-2
"@polkadot/util": ^9.0.1
checksum: a6b0928301cc2db31c5c694adce43e6a6148e06e278d36708b8f692d11527a4cd0cd42760fbe9f5bbf9e1c77d34db72d3c84377326a6cdb65af2eba2e62190d3
checksum: 3374b415be05342c71a22340eb7d8120c3503193d4481719fdc678d1a7a8b978795d9a166a827393c557d346ac87151760ced5fadec0464b03dfbd6d917853d9
languageName: node
linkType: hard
"@polkadot/types-known@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/types-known@npm:8.2.2-1"
"@polkadot/types-known@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/types-known@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/networks": ^9.0.1
"@polkadot/types": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/types-create": 8.2.2-1
"@polkadot/types": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/types-create": 8.2.2-2
"@polkadot/util": ^9.0.1
checksum: bc9088e3d8839ee41d22db98d4e4b31eccb8f35df94300d02a0424fbcf3f4ad5dd60e691333f4603db472fb27d4b1d99a389bab1ccf300565e8446aeabd9ea74
checksum: 7ba88d8b928b1f381391dd3fab66b0ff549fc67202608d8ed8265848c18195e929febfda1a6b9838ec4fc50ea02c0cc85357651adc8e869f0924429247e089ed
languageName: node
linkType: hard
"@polkadot/types-support@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/types-support@npm:8.2.2-1"
"@polkadot/types-support@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/types-support@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/util": ^9.0.1
checksum: 5201d237fbb6a82b14aecfcb4db0bc612916de9458e3302df184a4352fc192c4c0d23e0612414362baf70a1468546b4ed8a8653b47425d80bc105fe66201aa84
checksum: eb2a0e277089ad1ad0fce6f518e2b21f408c1afeeee5dc98cb13bc41928269383bdb4a38b8e57ecda9831152e33644ebbd95706bea3db37c03610508f4306b28
languageName: node
linkType: hard
"@polkadot/types@npm:^8.2.2-1":
version: 8.2.2-1
resolution: "@polkadot/types@npm:8.2.2-1"
"@polkadot/types@npm:^8.2.2-2":
version: 8.2.2-2
resolution: "@polkadot/types@npm:8.2.2-2"
dependencies:
"@babel/runtime": ^7.17.9
"@polkadot/keyring": ^9.0.1
"@polkadot/types-augment": 8.2.2-1
"@polkadot/types-codec": 8.2.2-1
"@polkadot/types-create": 8.2.2-1
"@polkadot/types-augment": 8.2.2-2
"@polkadot/types-codec": 8.2.2-2
"@polkadot/types-create": 8.2.2-2
"@polkadot/util": ^9.0.1
"@polkadot/util-crypto": ^9.0.1
rxjs: ^7.5.5
checksum: 036fe3ffcdc2f8a767b50297aae061762415a43ba4402c2bf3186fcfaaa15685b1acd04e8963fbf6b5af9aa4fb7e39ad555178be7c710d1f3468ec01f0570e1b
checksum: e887021c607213a9ad020f989e4389e38982aa438be47074cdfb18143b4fd42d207b029bf87dad6fc2224189cd4c34c6553674843dfbb0e2bb678878cafb1122
languageName: node
linkType: hard