From b536e8e11a573660b32a0f6685f10cd618d45de2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 24 Apr 2020 22:17:54 +0200 Subject: [PATCH] Multisig approvals (#2627) --- .../page-accounts/src/Accounts/Account.tsx | 46 ++++++++- packages/page-accounts/src/Accounts/index.tsx | 2 +- .../src/Accounts/modals/MultisigApprove.tsx | 96 +++++++++++++++++++ .../{Multisig.tsx => MultisigCreate.tsx} | 0 4 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 packages/page-accounts/src/Accounts/modals/MultisigApprove.tsx rename packages/page-accounts/src/Accounts/modals/{Multisig.tsx => MultisigCreate.tsx} (100%) diff --git a/packages/page-accounts/src/Accounts/Account.tsx b/packages/page-accounts/src/Accounts/Account.tsx index d201cec18e..90d32c14bb 100644 --- a/packages/page-accounts/src/Accounts/Account.tsx +++ b/packages/page-accounts/src/Accounts/Account.tsx @@ -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 | null { const { t } = useTranslation(); const api = useApi(); + const [multiInc, refreshMulti] = useIncrement(); const balancesAll = useCall(api.api.derive.balances.all, [address]); const recoveryInfo = useCall(api.api.query.recovery?.recoverable, [address], { transform: (opt: Option) => 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][]): [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 && ( + + )} {isRecoverAccountOpen && ( )} + {isMultisig && ( + <> + + + {t('Multisig approvals')} + + + )} {!api.isDevelopment && ( <> diff --git a/packages/page-accounts/src/Accounts/index.tsx b/packages/page-accounts/src/Accounts/index.tsx index 01f6cac295..d387cce9e8 100644 --- a/packages/page-accounts/src/Accounts/index.tsx +++ b/packages/page-accounts/src/Accounts/index.tsx @@ -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'; diff --git a/packages/page-accounts/src/Accounts/modals/MultisigApprove.tsx b/packages/page-accounts/src/Accounts/modals/MultisigApprove.tsx new file mode 100644 index 0000000000..ee5859d8a2 --- /dev/null +++ b/packages/page-accounts/src/Accounts/modals/MultisigApprove.tsx @@ -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 { + const { t } = useTranslation(); + const [hash, setHash] = useState(ongoing[0][0].toHex()); + const [multisig, setMultisig] = useState<[H256, Multisig] | null>(null); + const [signatory, setSignatory] = useState(null); + const [type, setType] = useState('aye'); + const calltypes = useMemo( + () => [ + { text: t('Approve this call hash'), value: 'aye' }, + { text: t('Cancel this call hash'), value: 'nay' } + ], + [t] + ); + const hashes = useMemo( + () => ongoing.map(([h]) => ({ text: h.toHex(), value: h.toHex() })), + [ongoing] + ); + + useEffect((): void => { + hash && setMultisig( + ongoing.find(([h]) => h.eq(hash)) || null + ); + }, [hash, ongoing]); + + return ( + + + + + + + + who !== signatory), multisig ? multisig[1].when : null, hash]} + tx={ + type === 'aye' + ? 'utility.approveAsMulti' + : 'utility.cancelAsMulti' + } + /> + + + ); +} + +export default React.memo(MultisigApprove); diff --git a/packages/page-accounts/src/Accounts/modals/Multisig.tsx b/packages/page-accounts/src/Accounts/modals/MultisigCreate.tsx similarity index 100% rename from packages/page-accounts/src/Accounts/modals/Multisig.tsx rename to packages/page-accounts/src/Accounts/modals/MultisigCreate.tsx