Para updates (#4906)

* Para updates

* Update packages/page-parachains/src/Crowdloan/Fund.tsx

* Handle current Rococo for registrar.paras
This commit is contained in:
Jaco Greeff 2021-03-23 11:40:41 +01:00 committed by GitHub
parent 011740ad30
commit 270a73f09a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 243 additions and 85 deletions

View File

@ -3,13 +3,15 @@
import type BN from 'bn.js';
import type { AuctionIndex, BlockNumber, LeasePeriodOf } from '@polkadot/types/interfaces';
import type { OwnedId, OwnerInfo } from '../types';
import React, { useMemo, useState } from 'react';
import { Button, Dropdown, InputAddress, InputBalance, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { Button, Dropdown, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useBestNumber, useToggle } from '@polkadot/react-hooks';
import { BN_ZERO, formatNumber } from '@polkadot/util';
import InputOwner from '../InputOwner';
import { useTranslation } from '../translate';
import { RANGES } from './constants';
@ -17,6 +19,7 @@ interface Props {
auctionInfo: [LeasePeriodOf, BlockNumber] | null;
className?: string;
id: AuctionIndex | null;
ownedIds: OwnedId[];
}
interface Option {
@ -24,14 +27,13 @@ interface Option {
value: number;
}
function Bid ({ auctionInfo, className, id }: Props): React.ReactElement<Props> {
function Bid ({ auctionInfo, className, id, ownedIds }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { hasAccounts } = useAccounts();
const bestNumber = useBestNumber();
const [accountId, setAccountId] = useState<string | null>(null);
const [{ accountId, paraId }, setOwnerInfo] = useState<OwnerInfo>({ accountId: null, paraId: 0 });
const [amount, setAmount] = useState<BN | undefined>(BN_ZERO);
const [paraId, setParaId] = useState<BN | undefined>(BN_ZERO);
const [range, setRange] = useState(0);
const [isOpen, toggleOpen] = useToggle();
@ -57,7 +59,7 @@ function Bid ({ auctionInfo, className, id }: Props): React.ReactElement<Props>
<>
<Button
icon='plus'
isDisabled={!hasAccounts || !id || !leasePeriod || !endBlock || bestNumber?.gte(endBlock.add(api.consts.auctions.endingPeriod as BlockNumber))}
isDisabled={!ownedIds.length || !hasAccounts || !id || !leasePeriod || !endBlock || bestNumber?.gte(endBlock.add(api.consts.auctions.endingPeriod as BlockNumber))}
label={t<string>('Bid')}
onClick={toggleOpen}
/>
@ -68,23 +70,10 @@ function Bid ({ auctionInfo, className, id }: Props): React.ReactElement<Props>
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('This account will be associated with the bid. This should match the registrar for the parachain.')}>
<InputAddress
label={t<string>('bid from')}
onChange={setAccountId}
type='account'
value={accountId}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The parachain id this bid is placed for')}>
<InputNumber
autoFocus
defaultValue={paraId?.toString()}
isZeroable={false}
label={t<string>('parachain id')}
onChange={setParaId}
/>
</Modal.Columns>
<InputOwner
onChange={setOwnerInfo}
ownedIds={ownedIds}
/>
<Modal.Columns hint={t<string>('The amount to to bid for this parachain slot')}>
<InputBalance
isZeroable={false}
@ -105,7 +94,7 @@ function Bid ({ auctionInfo, className, id }: Props): React.ReactElement<Props>
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!paraId?.gt(BN_ZERO) || !amount?.gt(BN_ZERO) || !leasePeriod}
isDisabled={!paraId || !amount?.gt(BN_ZERO) || !leasePeriod}
label={t<string>('Bid')}
onStart={toggleOpen}
params={[paraId, id, leasePeriod?.addn(RANGES[range][0]), leasePeriod?.addn(RANGES[range][1]), amount]}

View File

@ -39,7 +39,7 @@ function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLates
<td className='number'><h1>{formatNumber(paraId)}</h1></td>
<td><ParaLink id={paraId} /></td>
<td className='address'><AddressMini value={accountId} /></td>
<td className='number'><Digits value={`${formatNumber(leasePeriod.addn(range[0]))} - ${formatNumber(leasePeriod.addn(range[1]))}`} /></td>
<td className='all number'><Digits value={`${formatNumber(leasePeriod.addn(range[0]))} - ${formatNumber(leasePeriod.addn(range[1]))}`} /></td>
<td className='number'><FormatBalance value={value} /></td>
</tr>
);

View File

@ -4,6 +4,7 @@
import type { Option } from '@polkadot/types';
import type { AuctionIndex, BlockNumber, LeasePeriodOf } from '@polkadot/types/interfaces';
import type { ITuple } from '@polkadot/types/types';
import type { OwnedId } from '../types';
import React from 'react';
@ -17,6 +18,7 @@ import useWinningData from './useWinningData';
interface Props {
className?: string;
ownedIds: OwnedId[];
}
interface QueryState {
@ -35,7 +37,7 @@ const optionsMulti = {
})
};
function Auctions ({ className }: Props): React.ReactElement<Props> {
function Auctions ({ className, ownedIds }: Props): React.ReactElement<Props> {
const { api } = useApi();
const { auctionInfo, numAuctions } = useCallMulti<QueryState>([
api.query.auctions.auctionCounter,
@ -53,7 +55,8 @@ function Auctions ({ className }: Props): React.ReactElement<Props> {
<Button.Group>
<Bid
auctionInfo={auctionInfo}
id={ numAuctions}
id={numAuctions}
ownedIds={ownedIds}
/>
</Button.Group>
<Auction

View File

@ -7,8 +7,8 @@ import type { Campaign } from './types';
import React, { useMemo } from 'react';
import { AddressMini, Digits, ParaLink } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { AddressMini, Digits, ParaLink, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi } from '@polkadot/react-hooks';
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
@ -25,6 +25,16 @@ interface Props {
function Fund ({ bestNumber, className, isOngoing, value: { info: { cap, depositor, end, firstSlot, lastSlot, raised, retiring }, paraId } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const isDepositor = useMemo(
(): boolean => {
const address = depositor.toString();
return allAccounts.some((a) => a === address);
},
[allAccounts, depositor]
);
const blocksLeft = useMemo(
() => bestNumber && end.gt(bestNumber) && end.sub(bestNumber),
@ -42,6 +52,7 @@ function Fund ({ bestNumber, className, isOngoing, value: { info: { cap, deposit
);
const canContribute = blocksLeft && !isCapped && retiring.isFalse;
const canDissolve = raised.isZero();
return (
<tr className={className}>
@ -58,7 +69,7 @@ function Fund ({ bestNumber, className, isOngoing, value: { info: { cap, deposit
}
</td>
<td className='address'><AddressMini value={depositor} /></td>
<td className='number together'>
<td className='all number together'>
{blocksLeft && (
<BlockToTime value={blocksLeft} />
)}
@ -83,6 +94,16 @@ function Fund ({ bestNumber, className, isOngoing, value: { info: { cap, deposit
raised={raised}
/>
)}
{canDissolve && (
<TxButton
accountId={depositor}
icon='times'
isDisabled={!isDepositor}
label={t<string>('Dissolve')}
params={[paraId]}
tx={api.tx.crowdloan.dissolve}
/>
)}
</td>
)}
</tr>

View File

@ -2,30 +2,31 @@
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { OwnedId, OwnerInfo } from '../types';
import React, { useState } from 'react';
import { Button, InputAddress, InputBalance, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
import { Button, InputBalance, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { BN_ZERO } from '@polkadot/util';
import InputOwner from '../InputOwner';
import { useTranslation } from '../translate';
interface Props {
bestNumber?: BN;
className?: string;
ownedIds: OwnedId[];
}
function FundAdd ({ bestNumber, className }: Props): React.ReactElement<Props> {
function FundAdd ({ bestNumber, className, ownedIds }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
const [{ accountId, paraId }, setOwnerInfo] = useState<OwnerInfo>({ accountId: null, paraId: 0 });
const [cap, setCap] = useState<BN | undefined>();
const [endBlock, setEndBlock] = useState<BN | undefined>();
const [firstSlot, setFirstSlot] = useState<BN | undefined>();
const [lastSlot, setLastSlot] = useState<BN | undefined>();
const [paraId, setParaId] = useState<BN | undefined>();
const [isOpen, toggleOpen] = useToggle();
const isEndError = !bestNumber || !endBlock || endBlock.lt(bestNumber);
@ -37,7 +38,7 @@ function FundAdd ({ bestNumber, className }: Props): React.ReactElement<Props> {
<>
<Button
icon='plus'
isDisabled={!hasAccounts}
isDisabled={!ownedIds.length}
label={t<string>('Add fund')}
onClick={toggleOpen}
/>
@ -48,22 +49,10 @@ function FundAdd ({ bestNumber, className }: Props): React.ReactElement<Props> {
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('This account will be associated with the fund and pay the deposit. This should match the registrar for the parachain.')}>
<InputAddress
label={t<string>('propose from')}
onChange={setAccountId}
type='account'
value={accountId}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The parachain id this fund applies to')}>
<InputNumber
autoFocus
isZeroable={false}
label={t<string>('parachain id')}
onChange={setParaId}
/>
</Modal.Columns>
<InputOwner
onChange={setOwnerInfo}
ownedIds={ownedIds}
/>
<Modal.Columns hint={t<string>('The amount to be raised in this funding campaign')}>
<InputBalance
isZeroable={false}
@ -94,7 +83,7 @@ function FundAdd ({ bestNumber, className }: Props): React.ReactElement<Props> {
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!paraId?.gt(BN_ZERO) || !cap?.gt(BN_ZERO) || !firstSlot?.gte(BN_ZERO) || isEndError || isLastError}
isDisabled={!paraId || !cap?.gt(BN_ZERO) || !firstSlot?.gte(BN_ZERO) || isEndError || isLastError}
label={t<string>('Add')}
onStart={toggleOpen}
params={[paraId, cap, firstSlot, lastSlot, endBlock, null]}

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { Balance, BlockNumber, ParaId } from '@polkadot/types/interfaces';
import type { Balance, BalanceOf, BlockNumber, ParaId } from '@polkadot/types/interfaces';
import React, { useState } from 'react';
@ -30,7 +30,7 @@ function FundContribute ({ cap, className, paraId, raised }: Props): React.React
// TODO verifier signature
const remaining = cap.sub(raised);
const isAmountBelow = !amount || amount.lt(api.consts.crowdloan.minContribution as BlockNumber);
const isAmountBelow = !amount || amount.lt(api.consts.crowdloan.minContribution as BalanceOf);
const isAmountOver = !!(amount && amount.gt(remaining));
const isAmountError = isAmountBelow || isAmountOver;
@ -60,6 +60,7 @@ function FundContribute ({ cap, className, paraId, raised }: Props): React.React
<Modal.Columns hint={t<string>('The amount to contribute. Should be less than the remaining value and more than the minimum contribution amount.')}>
<InputBalance
autoFocus
defaultValue={api.consts.crowdloan.minContribution as BalanceOf}
isError={isAmountError}
isZeroable={false}
label={t<string>('contribution')}

View File

@ -1,6 +1,8 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { OwnedId } from '../types';
import React from 'react';
import { Button } from '@polkadot/react-components';
@ -13,9 +15,10 @@ import useFunds from './useFunds';
interface Props {
className?: string;
ownedIds: OwnedId[];
}
function Crowdloan ({ className }: Props): React.ReactElement<Props> {
function Crowdloan ({ className, ownedIds }: Props): React.ReactElement<Props> {
const bestNumber = useBestNumber();
const { activeCap, activeRaised, funds, totalCap, totalRaised } = useFunds();
@ -29,7 +32,10 @@ function Crowdloan ({ className }: Props): React.ReactElement<Props> {
totalRaised={totalRaised}
/>
<Button.Group>
<FundAdd bestNumber={bestNumber} />
<FundAdd
bestNumber={bestNumber}
ownedIds={ownedIds}
/>
</Button.Group>
<Funds
bestNumber={bestNumber}

View File

@ -0,0 +1,64 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { OwnedId, OwnerInfo } from './types';
import React, { useEffect, useMemo, useState } from 'react';
import { Dropdown, InputAddress, Modal } from '@polkadot/react-components';
import { useTranslation } from './translate';
interface Props {
onChange: (owner: OwnerInfo) => void;
ownedIds: OwnedId[];
}
function InputOwner ({ onChange, ownedIds }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [accountId, setAccountId] = useState<string | null>(null);
const [paraId, setParaId] = useState<number>(0);
useEffect((): void => {
accountId && paraId && onChange({ accountId, paraId });
}, [accountId, onChange, paraId]);
const owners = useMemo(
() => ownedIds.map(({ manager }) => manager),
[ownedIds]
);
const optIds = useMemo(
() => ownedIds
.filter(({ manager }) => manager === accountId)
.map(({ paraId }) => ({ text: paraId.toString(), value: paraId.toNumber() })),
[accountId, ownedIds]
);
return (
<>
<Modal.Columns hint={t<string>('This account that has been used to register the parachain. This will pay all associated fees.')}>
<InputAddress
filter={owners}
label={t<string>('parachain owner')}
onChange={setAccountId}
type='account'
value={accountId}
/>
</Modal.Columns>
{accountId && (
<Modal.Columns hint={t<string>('The parachain id, as associated with the account on the registrar')}>
<Dropdown
defaultValue={optIds[0].value}
key={accountId}
label={t<string>('parachain id')}
onChange={setParaId}
options={optIds}
/>
</Modal.Columns>
)}
</>
);
}
export default React.memo(InputOwner);

View File

@ -1,8 +1,8 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Option, Vec } from '@polkadot/types';
import type { AccountId, BlockNumber, CandidatePendingAvailability, HeadData, Header, ParaId, ParaLifecycle } from '@polkadot/types/interfaces';
import type { bool, Option, Vec } from '@polkadot/types';
import type { AccountId, BlockNumber, CandidatePendingAvailability, HeadData, Header, ParaId, ParaInfo, ParaLifecycle } from '@polkadot/types/interfaces';
import type { Codec } from '@polkadot/types/types';
import type { EventMapInfo, QueuedAction } from './types';
@ -12,7 +12,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { AddressMini, Badge, Expander, ParaLink } from '@polkadot/react-components';
import { useApi, useCall, useCallMulti, useParaApi } from '@polkadot/react-hooks';
import { BlockToTime } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import { formatNumber, isFunction } from '@polkadot/util';
import { useTranslation } from '../translate';
import { sliceHex } from '../util';
@ -31,11 +31,12 @@ interface Props {
validators?: AccountId[];
}
type QueryResult = [Option<HeadData>, Option<BlockNumber>, Option<ParaLifecycle>, Vec<Codec>, Vec<Codec>, Vec<Codec>, Vec<Codec>, Option<BlockNumber>, Option<CandidatePendingAvailability>];
type QueryResult = [Option<HeadData>, Option<BlockNumber>, Option<ParaLifecycle>, Vec<Codec>, Vec<Codec>, Vec<Codec>, Vec<Codec>, Option<BlockNumber>, Option<CandidatePendingAvailability>, Option<ParaInfo | bool>];
interface QueryState {
headHex: string | null;
lifecycle: ParaLifecycle | null;
paraInfo: ParaInfo | null;
pendingAvail: CandidatePendingAvailability | null;
updateAt: BlockNumber | null;
qDmp: number;
@ -45,6 +46,12 @@ interface QueryState {
watermark: BlockNumber | null;
}
function getParaInfo (info: ParaInfo | bool | null): ParaInfo | null {
return !info || isFunction((info as bool).isTrue)
? null
: info as ParaInfo;
}
const transformHeader = {
transform: (header: Header) => header.number.unwrap()
};
@ -53,6 +60,7 @@ const optionsMulti = {
defaultValue: {
headHex: null,
lifecycle: null,
paraInfo: null,
pendingAvail: null,
qDmp: 0,
qHrmpE: 0,
@ -61,11 +69,12 @@ const optionsMulti = {
updateAt: null,
watermark: null
},
transform: ([headData, optUp, optLifecycle, dmp, ump, hrmpE, hrmpI, optWm, optPending]: QueryResult): QueryState => ({
transform: ([headData, optUp, optLifecycle, dmp, ump, hrmpE, hrmpI, optWm, optPending, optInfo]: QueryResult): QueryState => ({
headHex: headData.isSome
? sliceHex(headData.unwrap())
: null,
lifecycle: optLifecycle.unwrapOr(null),
paraInfo: getParaInfo(optInfo.unwrapOr(null)),
pendingAvail: optPending.unwrapOr(null),
qDmp: dmp.length,
qHrmpE: hrmpE.length,
@ -99,7 +108,8 @@ function Parachain ({ bestNumber, className = '', id, isScheduled, lastBacked, l
[api.query.hrmp.hrmpEgressChannelsIndex, id],
[api.query.hrmp.hrmpIngressChannelsIndex, id],
[api.query.hrmp.hrmpWatermarks, id],
[api.query.inclusion.pendingAvailability, id]
[api.query.inclusion.pendingAvailability, id],
[api.query.registrar.paras, id]
], optionsMulti);
const [nonBacked, setNonBacked] = useState<AccountId[]>([]);

View File

@ -3,16 +3,17 @@
import type BN from 'bn.js';
import type { Option, Vec } from '@polkadot/types';
import type { AccountId, BalanceOf, HeadData, ParaGenesisArgs, ParaId, ParaLifecycle } from '@polkadot/types/interfaces';
import type { AccountId, BalanceOf, HeadData, ParaGenesisArgs, ParaId, ParaInfo, ParaLifecycle } from '@polkadot/types/interfaces';
import type { ITuple } from '@polkadot/types/types';
import type { LeaseInfo, QueuedAction } from './types';
import React from 'react';
import { ParaLink } from '@polkadot/react-components';
import { AddressSmall, ParaLink } from '@polkadot/react-components';
import { useApi, useCallMulti } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import { sliceHex } from '../util';
import Lifecycle from './Lifecycle';
@ -24,17 +25,19 @@ interface Props {
interface MultiState {
headHex: string | null;
leases: LeaseInfo[];
leases: LeaseInfo[] | null;
lifecycle: ParaLifecycle | null;
manager: AccountId | null;
}
const optMulti = {
defaultValue: {
headHex: null,
leases: [],
lifecycle: null
leases: null,
lifecycle: null,
manager: null
},
transform: ([optHead, optGenesis, optLifecycle, leases]: [Option<HeadData>, Option<ParaGenesisArgs>, Option<ParaLifecycle>, Vec<Option<ITuple<[AccountId, BalanceOf]>>>]): MultiState => ({
transform: ([optHead, optGenesis, optLifecycle, optInfo, leases]: [Option<HeadData>, Option<ParaGenesisArgs>, Option<ParaLifecycle>, Option<ParaInfo>, Vec<Option<ITuple<[AccountId, BalanceOf]>>>]): MultiState => ({
headHex: optHead.isSome
? sliceHex(optHead.unwrap())
: optGenesis.isSome
@ -57,16 +60,21 @@ const optMulti = {
})
.filter((item): item is LeaseInfo => !!item)
: [],
lifecycle: optLifecycle.unwrapOr(null)
lifecycle: optLifecycle.unwrapOr(null),
manager: optInfo.isSome
? optInfo.unwrap().manager
: null
})
};
function Upcoming ({ currentPeriod, id, nextAction }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { headHex, leases, lifecycle } = useCallMulti<MultiState>([
const { headHex, leases, lifecycle, manager } = useCallMulti<MultiState>([
[api.query.paras.heads, id],
[api.query.paras.upcomingParasGenesis, id],
[api.query.paras.paraLifecycles, id],
[api.query.registrar?.paras, id],
[api.query.slots?.leases, id]
], optMulti);
@ -74,6 +82,7 @@ function Upcoming ({ currentPeriod, id, nextAction }: Props): React.ReactElement
<tr>
<td className='number'><h1>{formatNumber(id)}</h1></td>
<td className='badge together'><ParaLink id={id} /></td>
<td className='address'>{manager && <AddressSmall value={manager} />}</td>
<td className='start together hash'>{headHex}</td>
<td className='start'>
<Lifecycle
@ -83,9 +92,11 @@ function Upcoming ({ currentPeriod, id, nextAction }: Props): React.ReactElement
</td>
<td className='all' />
<td className='start together'>
{currentPeriod &&
leases.map(({ period }) => formatNumber(currentPeriod.addn(period))).join(', ')
}
{currentPeriod && leases && (
leases.length
? leases.map(({ period }) => formatNumber(currentPeriod.addn(period))).join(', ')
: t('None')
)}
</td>
</tr>
);

View File

@ -22,7 +22,7 @@ function Upcomings ({ actionsQueue, currentPeriod, ids }: Props): React.ReactEle
const { t } = useTranslation();
const headerRef = useRef([
[t('upcoming'), 'start', 2],
[t('upcoming'), 'start', 3],
[t('head'), 'start'],
[t('lifecycle'), 'start'],
[],

View File

@ -12,8 +12,8 @@ import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
import { useProposal } from '../useProposals';
import { sliceHex } from '../util';
import { useProposal } from './useProposals';
interface Props {
approvedIds: ParaId[];

View File

@ -1,7 +1,7 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useMemo } from 'react';
import React, { useRef } from 'react';
import { Route, Switch } from 'react-router';
import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
@ -9,12 +9,13 @@ import styled from 'styled-components';
import { Tabs } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import useProposals from './Proposals/useProposals';
import Auctions from './Auctions';
import Crowdloan from './Crowdloan';
import Overview from './Overview';
import Proposals from './Proposals';
import { useTranslation } from './translate';
import useOwnedIds from './useOwnedIds';
import useProposals from './useProposals';
interface Props {
basePath: string;
@ -25,9 +26,10 @@ function ParachainsApp ({ basePath, className }: Props): React.ReactElement<Prop
const { t } = useTranslation();
const { api } = useApi();
const { pathname } = useLocation();
const ownedIds = useOwnedIds();
const proposals = useProposals();
const items = useMemo(() => [
const items = useRef([
{
isRoot: true,
name: 'overview',
@ -45,20 +47,20 @@ function ParachainsApp ({ basePath, className }: Props): React.ReactElement<Prop
name: 'crowdloan',
text: t<string>('Crowdloan')
}
].filter((q): q is { name: string; text: string } => !!q), [api, t]);
].filter((q): q is { name: string; text: string } => !!q));
return (
<main className={className}>
<Tabs
basePath={basePath}
items={items}
items={items.current}
/>
<Switch>
<Route path={`${basePath}/auctions`}>
<Auctions />
<Auctions ownedIds={ownedIds} />
</Route>
<Route path={`${basePath}/crowdloan`}>
<Crowdloan />
<Crowdloan ownedIds={ownedIds} />
</Route>
<Route path={`${basePath}/proposals`}>
<Proposals proposals={proposals} />

View File

@ -1,7 +1,7 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ParachainProposal, ParaId, SessionIndex } from '@polkadot/types/interfaces';
import type { ParachainProposal, ParaId, ParaInfo, SessionIndex } from '@polkadot/types/interfaces';
export interface ProposalExt {
id: ParaId;
@ -20,3 +20,14 @@ export interface Proposals {
proposalIds: ParaId[];
scheduled: ScheduledProposals[];
}
export interface OwnedId {
manager: string;
paraId: ParaId;
paraInfo: ParaInfo;
}
export interface OwnerInfo {
accountId: string | null;
paraId: number;
}

View File

@ -0,0 +1,51 @@
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { bool, Option } from '@polkadot/types';
import type { ParaId, ParaInfo } from '@polkadot/types/interfaces';
import type { OwnedId } from './types';
import { useEffect, useState } from 'react';
import { useAccounts, useApi, useEventTrigger } from '@polkadot/react-hooks';
import { isFunction } from '@polkadot/util';
// Current Rococo returns a bool value for this entry
function isParaInfo (info: ParaInfo | bool): info is ParaInfo {
return !isFunction((info as bool).isTrue);
}
export default function useOwnedIds (): OwnedId[] {
const { api } = useApi();
const { allAccounts } = useAccounts();
const trigger = useEventTrigger([api.events.registrar?.Registered]);
const [state, setState] = useState<OwnedId[]>([]);
useEffect((): void => {
allAccounts && trigger &&
api.query.registrar?.paras
.entries<Option<ParaInfo | bool>, [ParaId]>()
.then((entries) => setState(() =>
entries
.map(([{ args: [paraId] }, optInfo]): OwnedId | null => {
if (!optInfo.isSome) {
return null;
}
const paraInfo = optInfo.unwrap();
return isParaInfo(paraInfo)
? {
manager: paraInfo.manager.toString(),
paraId,
paraInfo
}
: null;
})
.filter((info): info is OwnedId => !!info && allAccounts.some((a) => a === info.manager))
))
.catch(console.error);
}, [allAccounts, api, trigger]);
return state;
}

View File

@ -3,7 +3,7 @@
import type { Option } from '@polkadot/types';
import type { ParachainProposal, ParaId, SessionIndex } from '@polkadot/types/interfaces';
import type { ProposalExt, Proposals, ScheduledProposals } from '../types';
import type { ProposalExt, Proposals, ScheduledProposals } from './types';
import { useEffect, useMemo, useState } from 'react';