// Copyright 2017-2026 @pezkuwi/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { DeriveBalancesAll } from '@pezkuwi/api-derive/types'; import type { AccountInfoWithProviders, AccountInfoWithRefCount } from '@pezkuwi/types/interfaces'; import type { KeyringJson$Meta } from '@pezkuwi/ui-keyring/types'; import type { BN } from '@pezkuwi/util'; import React, { useEffect, useState } from 'react'; import { checkAddress } from '@pezkuwi/phishing'; import { useApi, useCall } from '@pezkuwi/react-hooks'; import { Available } from '@pezkuwi/react-query'; import { settings } from '@pezkuwi/ui-settings'; import { BN_HUNDRED, BN_ZERO, isFunction, nextTick } from '@pezkuwi/util'; import InputAddress from '../InputAddress/index.js'; import InputBalance from '../InputBalance.js'; import MarkError from '../MarkError.js'; import MarkWarning from '../MarkWarning.js'; import Modal from '../Modal/index.js'; import { styled } from '../styled.js'; import Toggle from '../Toggle.js'; import { useTranslation } from '../translate.js'; import TxButton from '../TxButton.js'; import { getAddressMeta } from '../util/getAddressMeta.js'; interface Props { className?: string; onClose: () => void; recipientId?: string; senderId?: string; } function isRefcount (accountInfo: AccountInfoWithProviders | AccountInfoWithRefCount): accountInfo is AccountInfoWithRefCount { return !!(accountInfo as AccountInfoWithRefCount).refcount; } async function checkPhishing (_senderId: string | null, recipientId: string | null): Promise<[string | null, string | null]> { return [ // not being checked atm // senderId // ? await checkAddress(senderId) // : null, null, recipientId ? await checkAddress(recipientId) : null ]; } function Transfer ({ className = '', onClose, recipientId: propRecipientId, senderId: propSenderId }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const [amount, setAmount] = useState(BN_ZERO); const [hasAvailable] = useState(true); const [isProtected, setIsProtected] = useState(true); const [isAll, setIsAll] = useState(false); const [senderIdMeta, setSenderIdMeta] = useState(); const [[maxTransfer, noFees], setMaxTransfer] = useState<[BN | null, boolean]>([null, false]); const [recipientId, setRecipientId] = useState(null); const [senderId, setSenderId] = useState(null); const [[, recipientPhish], setPhishing] = useState<[string | null, string | null]>([null, null]); const balances = useCall(api.derive.balances?.all, [propSenderId || senderId]); const accountInfo = useCall(api.query.system.account, [propSenderId || senderId]); useEffect((): void => { const fromId = propSenderId || senderId; const toId = propRecipientId || recipientId; fromId && setSenderIdMeta(getAddressMeta(fromId)); if (balances && balances.accountId?.eq(fromId) && fromId && toId && api.call.transactionPaymentApi && api.tx.balances) { nextTick(async (): Promise => { try { const extrinsic = (api.tx.balances.transferAllowDeath || api.tx.balances.transfer)(toId, (balances.transferable || balances.availableBalance)); const { partialFee } = await extrinsic.paymentInfo(fromId); const adjFee = partialFee.muln(110).div(BN_HUNDRED); const maxTransfer = (balances.transferable || balances.availableBalance).sub(adjFee); setMaxTransfer( api.consts.balances && maxTransfer.gt(api.consts.balances.existentialDeposit) ? [maxTransfer, false] : [null, true] ); } catch (error) { console.error(error); } }); } else { setMaxTransfer([null, false]); } }, [api, balances, propRecipientId, propSenderId, recipientId, senderId]); useEffect((): void => { checkPhishing(propSenderId || senderId, propRecipientId || recipientId) .then(setPhishing) .catch(console.error); }, [propRecipientId, propSenderId, recipientId, senderId]); const noReference = accountInfo ? isRefcount(accountInfo) ? accountInfo.refcount.isZero() : accountInfo.consumers.isZero() : true; const canToggleAll = !isProtected && balances && balances.accountId?.eq(propSenderId || senderId) && maxTransfer && noReference; return (
} onChange={setSenderId} type='account' /> } onChange={setRecipientId} type='allPlus' /> {recipientPhish && ( )} {canToggleAll && isAll ? ( ) : ( <> ) } {isFunction(api.tx.balances?.transferKeepAlive) && ( )} {canToggleAll && ( )} {senderIdMeta && senderIdMeta.isHardware && ( )} {!isProtected && !noReference && ( )} {noFees && ( )}
); } const StyledModal = styled(Modal)` .balance { margin-bottom: 0.5rem; text-align: right; padding-right: 1rem; .label { opacity: 0.7; } } label.with-help { flex-basis: 10rem; } .typeToggle { text-align: right; } .typeToggle+.typeToggle { margin-top: 0.375rem; } `; export default React.memo(Transfer);