packages/page-parachains/src/Proposals/Propose.tsx
// Copyright 2017-2024 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useCallback, useState } from 'react';
import { Button, Input, InputAddress, InputBalance, InputFile, InputNumber, InputWasm, MarkWarning, Modal, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { BN, BN_TEN, BN_THOUSAND, BN_ZERO, compactAddLength } from '@polkadot/util';
import { useTranslation } from '../translate.js';
interface Props {
className?: string;
onClose: () => void;
}
interface CodeState {
isWasmValid: boolean;
wasm: Uint8Array | null;
}
interface ValidatorProps {
address: string;
index: number;
setAddress: (index: number, value: string) => void;
t: (key: string, options?: { replace: Record<string, unknown> }) => string;
}
function Validator ({ address, index, setAddress, t }: ValidatorProps): React.ReactElement<ValidatorProps> {
const _setAddress = useCallback(
(value: string | null) => value && setAddress(index, value),
[index, setAddress]
);
return (
<InputAddress
defaultValue={address}
label={t('validator {{index}}', { replace: { index: index + 1 } })}
onChange={_setAddress}
/>
);
}
function Propose ({ className, onClose }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [accountId, setAccountId] = useState<string | null>(null);
const [name, setName] = useState('');
const [paraId, setParaId] = useState<BN | undefined>();
const [balance, setBalance] = useState<BN | undefined>(() => BN_THOUSAND.mul(BN_TEN.pow(new BN(api.registry.chainDecimals[0]))));
const [validators, setValidators] = useState<string[]>(['']);
const [{ isWasmValid, wasm }, setWasm] = useState<CodeState>({ isWasmValid: false, wasm: null });
const [genesisState, setGenesisState] = useState<Uint8Array | null>(null);
const _setGenesisState = useCallback(
(data: Uint8Array) => setGenesisState(compactAddLength(data)),
[]
);
const _setWasm = useCallback(
(wasm: Uint8Array, isWasmValid: boolean) => setWasm({ isWasmValid, wasm }),
[]
);
const _setAddress = useCallback(
(index: number, address: string) =>
setValidators((v) => v.map((v, i) => i === index ? address : v)),
[]
);
const _addValidator = useCallback(
() => setValidators((v) => [...v, '']),
[]
);
const _delValidator = useCallback(
() => setValidators((v) => [...v.slice(0, v.length - 1)]),
[]
);
const isNameValid = name.length >= 3;
const isValDuplicate = validators.some((a, ai) => validators.some((b, bi) => ai !== bi && a === b));
return (
<Modal
className={className}
header={t('Propose parachain')}
onClose={onClose}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t('This account will be associated with the parachain and pay the deposit.')}>
<InputAddress
label={t('propose from')}
onChange={setAccountId}
type='account'
value={accountId}
/>
</Modal.Columns>
<Modal.Columns hint={t('The name for this parachain, the id and the allocated/requested balance.')}>
<Input
autoFocus
isError={!isNameValid}
label={t('parachain name')}
onChange={setName}
/>
<InputNumber
isZeroable={false}
label={t('requested id')}
onChange={setParaId}
/>
<InputBalance
defaultValue={balance}
label={t('initial balance')}
onChange={setBalance}
/>
</Modal.Columns>
<Modal.Columns hint={t('The WASM validation function as well as the genesis state for this parachain.')}>
<InputWasm
isError={!isWasmValid}
label={t('validation code')}
onChange={_setWasm}
placeholder={wasm && !isWasmValid && t('The code is not recognized as being in valid WASM format')}
/>
<InputFile
isError={!genesisState}
label={t('genesis state')}
onChange={_setGenesisState}
/>
</Modal.Columns>
<Modal.Columns hint={t('The validators for this parachain. At least one is required and where multiple is supplied, they need to be unique.')}>
{validators.map((address, index) => (
<Validator
address={address}
index={index}
key={index}
setAddress={_setAddress}
t={t}
/>
))}
{!validators.length && (
<MarkWarning content={t('You need to supply at last one running validator for your parachain alongside this request.')} />
)}
{isValDuplicate && (
<MarkWarning content={t('You have duplicated validator entries, ensure each is unique.')} />
)}
<Button.Group>
<Button
icon='plus'
label={t('Add validator')}
onClick={_addValidator}
/>
<Button
icon='minus'
isDisabled={validators.length === 0}
label={t('Remove validator')}
onClick={_delValidator}
/>
</Button.Group>
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
icon='plus'
isDisabled={!isWasmValid || !genesisState || !isNameValid || !validators.length || !paraId?.gt(BN_ZERO)}
onStart={onClose}
params={[paraId, name, wasm, genesisState, validators, balance]}
tx={api.tx.proposeParachain?.proposeParachain}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(Propose);