Multisig approvals (#2627)
This commit is contained in:
parent
249db4b356
commit
b536e8e11a
@ -4,15 +4,15 @@
|
||||
|
||||
import { DeriveBalancesAll } from '@polkadot/api-derive/types';
|
||||
import { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import { RecoveryConfig } from '@polkadot/types/interfaces';
|
||||
import { H256, Multisig, RecoveryConfig } from '@polkadot/types/interfaces';
|
||||
import { SortedAccount } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, LinkExternal, Menu, Popup, Tag } from '@polkadot/react-components';
|
||||
import { useAccountInfo, useApi, useCall, useToggle } from '@polkadot/react-hooks';
|
||||
import { Option } from '@polkadot/types';
|
||||
import { useAccountInfo, useApi, useCall, useIncrement, useToggle } from '@polkadot/react-hooks';
|
||||
import { Option, StorageKey } from '@polkadot/types';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { formatBalance, formatNumber } from '@polkadot/util';
|
||||
|
||||
@ -21,6 +21,7 @@ import Backup from './modals/Backup';
|
||||
import ChangePass from './modals/ChangePass';
|
||||
import Derive from './modals/Derive';
|
||||
import Identity from './modals/Identity';
|
||||
import MultisigApprove from './modals/MultisigApprove';
|
||||
import RecoverAccount from './modals/RecoverAccount';
|
||||
import RecoverSetup from './modals/RecoverSetup';
|
||||
import Transfer from './modals/Transfer';
|
||||
@ -47,16 +48,24 @@ function calcVisible (filter: string, name: string, tags: string[]): boolean {
|
||||
function Account ({ account: { address, meta }, className, filter, isFavorite, setBalance, toggleFavorite }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const api = useApi();
|
||||
const [multiInc, refreshMulti] = useIncrement();
|
||||
const balancesAll = useCall<DeriveBalancesAll>(api.api.derive.balances.all, [address]);
|
||||
const recoveryInfo = useCall<RecoveryConfig | null>(api.api.query.recovery?.recoverable, [address], {
|
||||
transform: (opt: Option<RecoveryConfig>) => opt.unwrapOr(null)
|
||||
});
|
||||
const { flags: { isDevelopment, isEditable, isExternal }, genesisHash, name: accName, onSaveGenesisHash, tags } = useAccountInfo(address);
|
||||
const multiInfos = useCall<[H256, Multisig][]>(multiInc && api.api.query.utility?.multisigs.entries as any, [address], {
|
||||
transform: (infos: [StorageKey, Option<Multisig>][]): [H256, Multisig][] =>
|
||||
infos
|
||||
.filter(([, opt]) => opt.isSome)
|
||||
.map(([key, opt]) => [key.args[1] as H256, opt.unwrap()])
|
||||
});
|
||||
const { flags: { isDevelopment, isEditable, isExternal, isMultisig }, genesisHash, name: accName, onSaveGenesisHash, tags } = useAccountInfo(address);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
const [isBackupOpen, toggleBackup] = useToggle();
|
||||
const [isDeriveOpen, toggleDerive] = useToggle();
|
||||
const [isForgetOpen, toggleForget] = useToggle();
|
||||
const [isIdentityOpen, toggleIdentity] = useToggle();
|
||||
const [isMultisigOpen, toggleMultisig] = useToggle();
|
||||
const [isPasswordOpen, togglePassword] = useToggle();
|
||||
const [isRecoverAccountOpen, toggleRecoverAccount] = useToggle();
|
||||
const [isRecoverSetupOpen, toggleRecoverSetup] = useToggle();
|
||||
@ -101,6 +110,14 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
|
||||
[address, t]
|
||||
);
|
||||
|
||||
const _closeMultisig = useCallback(
|
||||
(): void => {
|
||||
toggleMultisig();
|
||||
refreshMulti();
|
||||
},
|
||||
[refreshMulti, toggleMultisig]
|
||||
);
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
@ -199,6 +216,16 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
|
||||
senderId={address}
|
||||
/>
|
||||
)}
|
||||
{isMultisigOpen && multiInfos && (
|
||||
<MultisigApprove
|
||||
address={address}
|
||||
key='multisig-approve'
|
||||
onClose={_closeMultisig}
|
||||
ongoing={multiInfos}
|
||||
threshold={meta.threshold}
|
||||
who={meta.who}
|
||||
/>
|
||||
)}
|
||||
{isRecoverAccountOpen && (
|
||||
<RecoverAccount
|
||||
address={address}
|
||||
@ -311,6 +338,17 @@ function Account ({ account: { address, meta }, className, filter, isFavorite, s
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
{isMultisig && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
disabled={!multiInfos || !multiInfos.length}
|
||||
onClick={toggleMultisig}
|
||||
>
|
||||
{t('Multisig approvals')}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
{!api.isDevelopment && (
|
||||
<>
|
||||
<Menu.Divider />
|
||||
|
||||
@ -18,7 +18,7 @@ import { Button, Input, Table } from '@polkadot/react-components';
|
||||
import { useTranslation } from '../translate';
|
||||
import CreateModal from './modals/Create';
|
||||
import ImportModal from './modals/Import';
|
||||
import Multisig from './modals/Multisig';
|
||||
import Multisig from './modals/MultisigCreate';
|
||||
import QrModal from './modals/Qr';
|
||||
import Account from './Account';
|
||||
import Banner from './Banner';
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
// Copyright 2017-2020 @polkadot/app-accounts authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { H256, Multisig } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Dropdown, InputAddress, Modal, TxButton } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../../translate';
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
className?: string;
|
||||
onClose: () => void;
|
||||
ongoing: [H256, Multisig][];
|
||||
threshold: number;
|
||||
who: string[];
|
||||
}
|
||||
|
||||
interface Option {
|
||||
text: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function MultisigApprove ({ className, onClose, ongoing, threshold, who }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [hash, setHash] = useState<string | null>(ongoing[0][0].toHex());
|
||||
const [multisig, setMultisig] = useState<[H256, Multisig] | null>(null);
|
||||
const [signatory, setSignatory] = useState<string | null>(null);
|
||||
const [type, setType] = useState<string | null>('aye');
|
||||
const calltypes = useMemo<Option[]>(
|
||||
() => [
|
||||
{ text: t('Approve this call hash'), value: 'aye' },
|
||||
{ text: t('Cancel this call hash'), value: 'nay' }
|
||||
],
|
||||
[t]
|
||||
);
|
||||
const hashes = useMemo<Option[]>(
|
||||
() => ongoing.map(([h]) => ({ text: h.toHex(), value: h.toHex() })),
|
||||
[ongoing]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
hash && setMultisig(
|
||||
ongoing.find(([h]) => h.eq(hash)) || null
|
||||
);
|
||||
}, [hash, ongoing]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={className}
|
||||
header={t('Pending call hashes')}
|
||||
>
|
||||
<Modal.Content>
|
||||
<InputAddress
|
||||
filter={who}
|
||||
help={t('The signatory to send the approval/cancel from')}
|
||||
label={t('signatory')}
|
||||
onChange={setSignatory}
|
||||
/>
|
||||
<Dropdown
|
||||
help={t('The call hashes that have not been executed as of yet.')}
|
||||
label={t('pending hashes')}
|
||||
onChange={setHash}
|
||||
options={hashes}
|
||||
value={hash}
|
||||
/>
|
||||
<Dropdown
|
||||
help={t('Either approve or reject this call.')}
|
||||
label={t('approval type')}
|
||||
onChange={setType}
|
||||
options={calltypes}
|
||||
value={type}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Actions onCancel={onClose}>
|
||||
<TxButton
|
||||
accountId={signatory}
|
||||
icon={type === 'aye' ? 'check' : 'cancel'}
|
||||
isDisabled={!multisig}
|
||||
label={type === 'aye' ? 'Approve' : 'Reject'}
|
||||
onStart={onClose}
|
||||
params={[threshold, who.filter((who) => who !== signatory), multisig ? multisig[1].when : null, hash]}
|
||||
tx={
|
||||
type === 'aye'
|
||||
? 'utility.approveAsMulti'
|
||||
: 'utility.cancelAsMulti'
|
||||
}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(MultisigApprove);
|
||||
Loading…
Reference in New Issue
Block a user