Allow to upload a JSON file with the list of multisig signatories (#6911)
* Allow to upload a JSON file with the list of multisig signatories * Fix linter errors * Chore * Toggle between signatories input modes
This commit is contained in:
parent
dc391905de
commit
989e698698
@ -5,11 +5,13 @@ import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { ModalProps } from '../types';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button, Input, InputAddressMulti, InputNumber, Modal } from '@polkadot/react-components';
|
||||
import { AddressMini, Button, IconLink, Input, InputAddressMulti, InputFile, InputNumber, Labelled, MarkError, Modal, Toggle } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
import { BN } from '@polkadot/util';
|
||||
import { assert, BN, u8aToString } from '@polkadot/util';
|
||||
import { validateAddress } from '@polkadot/util-crypto';
|
||||
|
||||
import useKnownAddresses from '../Accounts/useKnownAddresses';
|
||||
import { useTranslation } from '../translate';
|
||||
@ -26,9 +28,41 @@ interface CreateOptions {
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
interface UploadedFileData {
|
||||
isUploadedFileValid: boolean;
|
||||
uploadedFileError: string;
|
||||
uploadedSignatories: string[];
|
||||
}
|
||||
|
||||
const MAX_SIGNATORIES = 16;
|
||||
const BN_TWO = new BN(2);
|
||||
|
||||
const acceptedFormats = ['application/json'].join(', ');
|
||||
|
||||
function parseFile (file: Uint8Array): UploadedFileData {
|
||||
let uploadError = '';
|
||||
let items: string[];
|
||||
|
||||
try {
|
||||
items = JSON.parse(u8aToString(file)) as string[];
|
||||
assert(Array.isArray(items) && !!items.length, 'JSON file should contain an array of signatories');
|
||||
|
||||
items = items.filter((item) => validateAddress(item));
|
||||
items = [...new Set(items)]; // remove duplicates
|
||||
|
||||
assert(items.length <= MAX_SIGNATORIES, `Maximum you can have ${MAX_SIGNATORIES} signatories`);
|
||||
} catch (error) {
|
||||
items = [];
|
||||
uploadError = (error as Error).message ? (error as Error).message : (error as Error).toString();
|
||||
}
|
||||
|
||||
return {
|
||||
isUploadedFileValid: !uploadError,
|
||||
uploadedFileError: uploadError,
|
||||
uploadedSignatories: items
|
||||
};
|
||||
}
|
||||
|
||||
function createMultisig (signatories: string[], threshold: BN | number, { genesisHash, name, tags = [] }: CreateOptions, success: string): ActionStatus {
|
||||
// we will fill in all the details below
|
||||
const status = { action: 'create' } as ActionStatus;
|
||||
@ -53,7 +87,13 @@ function Multisig ({ className = '', onClose, onStatusChange }: Props): React.Re
|
||||
const { t } = useTranslation();
|
||||
const availableSignatories = useKnownAddresses();
|
||||
const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
|
||||
const [{ isUploadedFileValid, uploadedFileError, uploadedSignatories }, setUploadedFile] = useState<UploadedFileData>({
|
||||
isUploadedFileValid: true,
|
||||
uploadedFileError: '',
|
||||
uploadedSignatories: []
|
||||
});
|
||||
const [signatories, setSignatories] = useState<string[]>(['']);
|
||||
const [showSignaturesUpload, setShowSignaturesUpload] = useState(false);
|
||||
const [{ isThresholdValid, threshold }, setThreshold] = useState({ isThresholdValid: true, threshold: BN_TWO });
|
||||
|
||||
const _createMultisig = useCallback(
|
||||
@ -78,6 +118,38 @@ function Multisig ({ className = '', onClose, onStatusChange }: Props): React.Re
|
||||
[signatories]
|
||||
);
|
||||
|
||||
const _onChangeFile = useCallback(
|
||||
(file: Uint8Array) => {
|
||||
const fileData = parseFile(file);
|
||||
|
||||
setUploadedFile(fileData);
|
||||
|
||||
if (fileData.isUploadedFileValid || uploadedSignatories.length) {
|
||||
setSignatories(fileData.uploadedSignatories.length ? fileData.uploadedSignatories : ['']);
|
||||
}
|
||||
},
|
||||
[uploadedSignatories]
|
||||
);
|
||||
|
||||
const resetFileUpload = useCallback(
|
||||
() => {
|
||||
setUploadedFile({
|
||||
isUploadedFileValid,
|
||||
uploadedFileError,
|
||||
uploadedSignatories: []
|
||||
});
|
||||
},
|
||||
[uploadedFileError, isUploadedFileValid]
|
||||
);
|
||||
|
||||
const _onChangeAddressMulti = useCallback(
|
||||
(items: string[]) => {
|
||||
resetFileUpload();
|
||||
setSignatories(items);
|
||||
},
|
||||
[resetFileUpload]
|
||||
);
|
||||
|
||||
const isValid = isNameValid && isThresholdValid;
|
||||
|
||||
return (
|
||||
@ -88,24 +160,74 @@ function Multisig ({ className = '', onClose, onStatusChange }: Props): React.Re
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns
|
||||
hint={
|
||||
<>
|
||||
<p>{t<string>('The signatories has the ability to create transactions using the multisig and approve transactions sent by others.Once the threshold is reached with approvals, the multisig transaction is enacted on-chain.')}</p>
|
||||
<p>{t<string>('Since the multisig function like any other account, once created it is available for selection anywhere accounts are used and needs to be funded before use.')}</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<InputAddressMulti
|
||||
available={availableSignatories}
|
||||
availableLabel={t<string>('available signatories')}
|
||||
help={t<string>('The addresses that are able to approve multisig transactions. You can select up to {{maxHelpers}} trusted addresses.', { replace: { maxHelpers: MAX_SIGNATORIES } })}
|
||||
maxCount={MAX_SIGNATORIES}
|
||||
onChange={setSignatories}
|
||||
value={signatories}
|
||||
valueLabel={t<string>('selected signatories')}
|
||||
<Modal.Columns>
|
||||
<Toggle
|
||||
className='signaturesFileToggle'
|
||||
label={t<string>('Upload JSON file with signatories')}
|
||||
onChange={setShowSignaturesUpload}
|
||||
value={showSignaturesUpload}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
{!showSignaturesUpload && (
|
||||
<Modal.Columns
|
||||
hint={
|
||||
<>
|
||||
<p>{t<string>('The signatories has the ability to create transactions using the multisig and approve transactions sent by others.Once the threshold is reached with approvals, the multisig transaction is enacted on-chain.')}</p>
|
||||
<p>{t<string>('Since the multisig function like any other account, once created it is available for selection anywhere accounts are used and needs to be funded before use.')}</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<InputAddressMulti
|
||||
available={availableSignatories}
|
||||
availableLabel={t<string>('available signatories')}
|
||||
help={t<string>('The addresses that are able to approve multisig transactions. You can select up to {{maxHelpers}} trusted addresses.', { replace: { maxHelpers: MAX_SIGNATORIES } })}
|
||||
maxCount={MAX_SIGNATORIES}
|
||||
onChange={_onChangeAddressMulti}
|
||||
value={signatories}
|
||||
valueLabel={t<string>('selected signatories')}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
)}
|
||||
{showSignaturesUpload && (
|
||||
<Modal.Columns hint={t<string>('Supply a JSON file with the list of signatories.')}>
|
||||
<InputFile
|
||||
accept={acceptedFormats}
|
||||
className='full'
|
||||
clearContent={!uploadedSignatories.length && isUploadedFileValid}
|
||||
help={t<string>('Select a JSON key file with the list of signatories.')}
|
||||
isError={!isUploadedFileValid}
|
||||
label={t<string>('upload signatories list')}
|
||||
onChange={_onChangeFile}
|
||||
withLabel
|
||||
/>
|
||||
{!!uploadedSignatories.length && (
|
||||
<Labelled
|
||||
label={t<string>('found signatories')}
|
||||
labelExtra={(
|
||||
<IconLink
|
||||
icon='sync'
|
||||
label={t<string>('Reset')}
|
||||
onClick={resetFileUpload}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div className='ui--Static ui dropdown selection'>
|
||||
{uploadedSignatories.map((address): React.ReactNode => (
|
||||
<div key={address}>
|
||||
<AddressMini
|
||||
value={address}
|
||||
withSidebar={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Labelled>
|
||||
)}
|
||||
{uploadedFileError && (
|
||||
<MarkError content={uploadedFileError} />
|
||||
)}
|
||||
</Modal.Columns>
|
||||
)}
|
||||
<Modal.Columns hint={t<string>('The threshold for approval should be less or equal to the number of signatories for this multisig.')}>
|
||||
<InputNumber
|
||||
help={t<string>('The threshold for this multisig')}
|
||||
@ -139,4 +261,9 @@ function Multisig ({ className = '', onClose, onStatusChange }: Props): React.Re
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Multisig);
|
||||
export default React.memo(styled(Multisig)`
|
||||
.signaturesFileToggle {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
`);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user