Multisig approvals (#2627)

This commit is contained in:
Jaco Greeff 2020-04-24 22:17:54 +02:00 committed by GitHub
parent 249db4b356
commit b536e8e11a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 5 deletions

View File

@ -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 />

View File

@ -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';

View File

@ -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);