diff --git a/packages/page-staking-async/src/Actions/Account/BondExtra.tsx b/packages/page-staking-async/src/Actions/Account/BondExtra.tsx new file mode 100644 index 000000000000..945cc01aa870 --- /dev/null +++ b/packages/page-staking-async/src/Actions/Account/BondExtra.tsx @@ -0,0 +1,109 @@ +// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ApiPromise } from '@polkadot/api'; +import type { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types'; +import type { AmountValidateState } from '../types.js'; + +import React, { useMemo, useState } from 'react'; + +import { InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components'; +import { useApi, useCall } from '@polkadot/react-hooks'; +import { BalanceFree } from '@polkadot/react-query'; +import { BN, BN_ZERO } from '@polkadot/util'; + +import { useTranslation } from '../../translate.js'; +import ValidateAmount from './InputValidateAmount.js'; + +interface Props { + controllerId: string | null; + onClose: () => void; + stakingInfo?: DeriveStakingAccount; + stashId: string; +} + +function calcBalance (api: ApiPromise, stakingInfo?: DeriveStakingAccount, stashBalance?: DeriveBalancesAll): BN | null { + if (stakingInfo?.stakingLedger && stashBalance) { + const sumUnlocking = (stakingInfo.unlocking || []).reduce((acc, { value }) => acc.iadd(value), new BN(0)); + const redeemable = stakingInfo.redeemable || BN_ZERO; + const available = stashBalance.freeBalance.sub(stakingInfo.stakingLedger.active?.unwrap() || BN_ZERO).sub(sumUnlocking).sub(redeemable); + + return available.gt(api.consts.balances.existentialDeposit) + ? available.sub(api.consts.balances.existentialDeposit) + : BN_ZERO; + } + + return null; +} + +function BondExtra ({ controllerId, onClose, stakingInfo, stashId }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const [amountError, setAmountError] = useState(null); + const [maxAdditional, setMaxAdditional] = useState(); + const stashBalance = useCall(api.derive.balances?.all, [stashId]); + const currentAmount = useMemo( + () => stakingInfo?.stakingLedger?.active?.unwrap(), + [stakingInfo] + ); + + const startBalance = useMemo( + () => calcBalance(api, stakingInfo, stashBalance), + [api, stakingInfo, stashBalance] + ); + + return ( + + + + + + {startBalance && ( + + {t('balance')}} + params={stashId} + /> + } + onChange={setMaxAdditional} + /> + + + )} + + + + + + ); +} + +export default React.memo(BondExtra); diff --git a/packages/page-staking-async/src/Actions/Account/InjectKeys.tsx b/packages/page-staking-async/src/Actions/Account/InjectKeys.tsx new file mode 100644 index 000000000000..1d9cd0430156 --- /dev/null +++ b/packages/page-staking-async/src/Actions/Account/InjectKeys.tsx @@ -0,0 +1,137 @@ +// Copyright 2017-2025 @polkadot/app-staking-async authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { KeypairType } from '@polkadot/util-crypto/types'; + +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { Button, Dropdown, Input, MarkWarning, Modal } from '@polkadot/react-components'; +import { useQueue } from '@polkadot/react-hooks'; +import { keyring } from '@polkadot/ui-keyring'; +import { assert, u8aToHex } from '@polkadot/util'; +import { keyExtractSuri, mnemonicValidate } from '@polkadot/util-crypto'; + +import { useTranslation } from '../../translate.js'; + +interface Props { + onClose: () => void; +} + +const CRYPTO_MAP: Record = { + aura: ['ed25519', 'sr25519'], + babe: ['sr25519'], + gran: ['ed25519'], + imon: ['ed25519', 'sr25519'], + para: ['sr25519'] +}; + +const EMPTY_KEY = '0x'; + +function InjectKeys ({ onClose }: Props): React.ReactElement | null { + const { t } = useTranslation(); + const { queueRpc } = useQueue(); + + // this needs to align with what is set as the first value in `type` + const [crypto, setCrypto] = useState('sr25519'); + const [publicKey, setPublicKey] = useState(EMPTY_KEY); + const [suri, setSuri] = useState(''); + const [keyType, setKeyType] = useState('babe'); + + const keyTypeOptRef = useRef([ + { text: t('Aura'), value: 'aura' }, + { text: t('Babe'), value: 'babe' }, + { text: t('Grandpa'), value: 'gran' }, + { text: t('I\'m Online'), value: 'imon' }, + { text: t('Parachains'), value: 'para' } + ]); + + useEffect((): void => { + setCrypto(CRYPTO_MAP[keyType][0]); + }, [keyType]); + + useEffect((): void => { + try { + const { phrase } = keyExtractSuri(suri); + + assert(mnemonicValidate(phrase), 'Invalid mnemonic phrase'); + + setPublicKey(u8aToHex(keyring.createFromUri(suri, {}, crypto).publicKey)); + } catch { + setPublicKey(EMPTY_KEY); + } + }, [crypto, suri]); + + const _onSubmit = useCallback( + (): void => queueRpc({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + rpc: { method: 'insertKey', section: 'author' } as any, + values: [keyType, suri, publicKey] + }), + [keyType, publicKey, queueRpc, suri] + ); + + const _cryptoOptions = useMemo( + () => CRYPTO_MAP[keyType].map((value): { text: string; value: KeypairType } => ({ + text: value === 'ed25519' + ? t('ed25519, Edwards') + : t('sr15519, Schnorrkel'), + value + })), + [keyType, t] + ); + + return ( + + + + + + + + + + + + + + + + + + +