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:
parent
28bbe4e986
commit
47913892cd
28
package.json
28
package.json
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
58
packages/page-staking/src/Actions/Accounts.tsx
Normal file
58
packages/page-staking/src/Actions/Accounts.tsx
Normal 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);
|
||||
197
packages/page-staking/src/Actions/Pool/Account.tsx
Normal file
197
packages/page-staking/src/Actions/Pool/Account.tsx
Normal 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);
|
||||
93
packages/page-staking/src/Actions/Pool/BondExtra.tsx
Normal file
93
packages/page-staking/src/Actions/Pool/BondExtra.tsx
Normal 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);
|
||||
97
packages/page-staking/src/Actions/Pool/Unbond.tsx
Normal file
97
packages/page-staking/src/Actions/Pool/Unbond.tsx
Normal 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);
|
||||
48
packages/page-staking/src/Actions/Pool/index.tsx
Normal file
48
packages/page-staking/src/Actions/Pool/index.tsx
Normal 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);
|
||||
10
packages/page-staking/src/Actions/Pool/types.ts
Normal file
10
packages/page-staking/src/Actions/Pool/types.ts
Normal 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;
|
||||
}
|
||||
46
packages/page-staking/src/Actions/Pool/useAccountInfo.ts
Normal file
46
packages/page-staking/src/Actions/Pool/useAccountInfo.ts
Normal 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);
|
||||
60
packages/page-staking/src/Actions/Pools.tsx
Normal file
60
packages/page-staking/src/Actions/Pools.tsx
Normal 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);
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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={
|
||||
|
||||
44
packages/page-staking/src/Actions/partials/PoolInfo.tsx
Normal file
44
packages/page-staking/src/Actions/partials/PoolInfo.tsx
Normal 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);
|
||||
22
packages/page-staking/src/Actions/useSlashingSpans.ts
Normal file
22
packages/page-staking/src/Actions/useSlashingSpans.ts
Normal 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);
|
||||
@ -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),
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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)} / </>}{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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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[]>;
|
||||
|
||||
24
packages/page-staking/src/Pools/useAmountError.ts
Normal file
24
packages/page-staking/src/Pools/useAmountError.ts
Normal 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);
|
||||
108
packages/page-staking/src/Pools/useMembers.ts
Normal file
108
packages/page-staking/src/Pools/useMembers.ts
Normal 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);
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
50
packages/page-staking/src/useOwnPools.ts
Normal file
50
packages/page-staking/src/useOwnPools.ts
Normal 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);
|
||||
35
packages/page-staking/src/usePoolAccounts.ts
Normal file
35
packages/page-staking/src/usePoolAccounts.ts
Normal 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);
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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'> </span>}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
216
yarn.lock
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user