diff --git a/packages/extension-koni-ui/src/Popup/Home/Tokens/DetailList.tsx b/packages/extension-koni-ui/src/Popup/Home/Tokens/DetailList.tsx index 17c20892bd2..b5dd4120a36 100644 --- a/packages/extension-koni-ui/src/Popup/Home/Tokens/DetailList.tsx +++ b/packages/extension-koni-ui/src/Popup/Home/Tokens/DetailList.tsx @@ -245,11 +245,12 @@ function Component (): React.ReactElement { setStorage({ ...DEFAULT_TRANSFER_PARAMS, + defaultSlug: tokenGroupSlug || '', fromProxyId }); navigate('/transaction/send-fund'); }, - [currentAccountProxy, navigate, setStorage] + [currentAccountProxy, navigate, setStorage, tokenGroupSlug] ); const onOpenBuyTokens = useCallback(() => { diff --git a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx index 26058892c08..61d81f7a61d 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -1,29 +1,28 @@ // Copyright 2019-2022 @polkadot/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { _AssetRef, _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from '@subwallet/chain-list/types'; +import { _AssetType, _ChainAsset, _ChainInfo, _MultiChainAsset } from '@subwallet/chain-list/types'; import { ExtrinsicType, NotificationType } from '@subwallet/extension-base/background/KoniTypes'; import { AbstractAddressJson, AccountJson } from '@subwallet/extension-base/background/types'; -import { _getAssetDecimals, _getOriginChainOfAsset, _getTokenMinAmount, _isAssetFungibleToken, _isChainEvmCompatible, _isNativeToken, _isTokenTransferredByEvm } from '@subwallet/extension-base/services/chain-service/utils'; +import { _getAssetDecimals, _getTokenMinAmount, _isNativeToken, _isPureBitcoinChain, _isPureEvmChain, _isTokenTransferredByEvm } from '@subwallet/extension-base/services/chain-service/utils'; import { SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types'; import { BitcoinFeeDetail, ResponseSubscribeTransfer, TransactionFee } from '@subwallet/extension-base/types'; import { BN_ZERO, detectTranslate } from '@subwallet/extension-base/utils'; -import { AccountSelector, AddressInput, AlertBox, AlertModal, AmountInput, BitcoinFeeSelector, ChainSelector, HiddenInput, TokenItemType, TokenSelector } from '@subwallet/extension-koni-ui/components'; -import { BITCOIN_CHAINS, SUPPORT_CHAINS } from '@subwallet/extension-koni-ui/constants'; -import { useAlert, useFetchChainAssetInfo, useGetChainPrefixBySlug, useGetNativeTokenBasicInfo, useHandleSubmitTransaction, useInitValidateTransaction, useNotification, usePreCheckAction, useRestoreTransaction, useSelector, useSetCurrentPage, useTransactionContext, useWatchTransaction } from '@subwallet/extension-koni-ui/hooks'; +import { AccountSelector, AddressInput, AlertBox, AlertModal, AmountInput, BitcoinFeeSelector, HiddenInput, TokenItemType, TokenSelector } from '@subwallet/extension-koni-ui/components'; +import { BITCOIN_CHAINS } from '@subwallet/extension-koni-ui/constants'; +import { useAlert, useFetchChainAssetInfo, useGetChainPrefixBySlug, useHandleSubmitTransaction, useInitValidateTransaction, useNotification, usePreCheckAction, useRestoreTransaction, useSelector, useSetCurrentPage, useTransactionContext, useWatchTransaction } from '@subwallet/extension-koni-ui/hooks'; import { cancelSubscription, makeCrossChainTransfer, makeTransfer, subscribeMaxTransfer } from '@subwallet/extension-koni-ui/messaging'; import { FreeBalance } from '@subwallet/extension-koni-ui/Popup/Transaction/parts'; import { RootState } from '@subwallet/extension-koni-ui/stores'; -import { ChainItemType, FormCallbacks, Theme, ThemeProps, TransferParams } from '@subwallet/extension-koni-ui/types'; +import { FormCallbacks, Theme, ThemeProps, TransferParams } from '@subwallet/extension-koni-ui/types'; import { findAccountByAddress, formatBalance, noop, reformatAddress } from '@subwallet/extension-koni-ui/utils'; import { getKeypairTypeByAddress } from '@subwallet/keyring'; -import { KeypairType } from '@subwallet/keyring/types'; -import { Button, Form, Icon, Number } from '@subwallet/react-ui'; +import { Button, Form, Icon } from '@subwallet/react-ui'; import { Rule } from '@subwallet/react-ui/es/form'; import BigN from 'bignumber.js'; import CN from 'classnames'; -import { PaperPlaneRight, PaperPlaneTilt } from 'phosphor-react'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { ArrowRight, PaperPlaneTilt } from 'phosphor-react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { useIsFirstRender } from 'usehooks-ts'; @@ -34,31 +33,26 @@ import { TransactionContent, TransactionFooter } from '../parts'; type Props = ThemeProps; -function checkValidBetweenAddressTypeAndChain (addressType: KeypairType, chain: string): boolean { - if (chain === 'bitcoin') { - return ['bitcoin-44', 'bitcoin-84'].includes(addressType); - } - - if (chain === 'bitcoinTestnet') { - return ['bittest-44', 'bittest-84'].includes(addressType); - } - - return chain === 'ethereum' && addressType === 'ethereum'; -} +// function checkValidBetweenAddressTypeAndChain (addressType: KeypairType, chain: string): boolean { +// if (chain === 'bitcoin') { +// return ['bitcoin-44', 'bitcoin-84'].includes(addressType); +// } +// +// if (chain === 'bitcoinTestnet') { +// return ['bittest-44', 'bittest-84'].includes(addressType); +// } +// +// return chain === 'ethereum' && addressType === 'ethereum'; +// } function getTokenItems ( - address: string, + chainInfoMap: Record, assetRegistry: Record, multiChainAssetMap: Record, tokenGroupSlug?: string // is ether a token slug or a multiChainAsset slug ): TokenItemType[] { - if (!address) { - return []; - } - const isSetTokenSlug = !!tokenGroupSlug && !!assetRegistry[tokenGroupSlug]; const isSetMultiChainAssetSlug = !!tokenGroupSlug && !!multiChainAssetMap[tokenGroupSlug]; - const addressType = getKeypairTypeByAddress(address); if (tokenGroupSlug) { if (!(isSetTokenSlug || isSetMultiChainAssetSlug)) { @@ -70,10 +64,6 @@ function getTokenItems ( if (isSetTokenSlug && chainAsset) { const { name, originChain, slug, symbol } = chainAsset; - if (!checkValidBetweenAddressTypeAndChain(addressType, originChain)) { - return []; - } - return [ { name, @@ -88,18 +78,23 @@ function getTokenItems ( const items: TokenItemType[] = []; Object.values(assetRegistry).forEach((chainAsset) => { - const isTokenFungible = _isAssetFungibleToken(chainAsset); + const chainInfo = chainInfoMap[chainAsset.originChain]; - if (!(isTokenFungible && _isNativeToken(chainAsset) && SUPPORT_CHAINS.includes(chainAsset.originChain))) { + if (chainAsset.assetType === 'RUNE' || chainAsset.assetType === 'BRC20') { return; } - if (!checkValidBetweenAddressTypeAndChain(addressType, chainAsset.originChain)) { - return; - } - - if (isSetMultiChainAssetSlug) { - if (chainAsset.multiChainAsset === tokenGroupSlug) { + if (_isPureBitcoinChain(chainInfo) || _isPureEvmChain(chainInfo)) { + if (isSetMultiChainAssetSlug) { + if (chainAsset.multiChainAsset === tokenGroupSlug) { + items.push({ + name: chainAsset.name, + slug: chainAsset.slug, + symbol: chainAsset.symbol, + originChain: chainAsset.originChain + }); + } + } else { items.push({ name: chainAsset.name, slug: chainAsset.slug, @@ -107,56 +102,42 @@ function getTokenItems ( originChain: chainAsset.originChain }); } - } else { - items.push({ - name: chainAsset.name, - slug: chainAsset.slug, - symbol: chainAsset.symbol, - originChain: chainAsset.originChain - }); } }); return items.sort((a, b) => { - if (a.originChain.toLowerCase() === 'bitcoin' || a.originChain.toLowerCase() === 'bitcoinTestnet') { - return -1; // Place 'bitcoin' or 'BitcoinTestnet' at the top - } else if (b.originChain.toLowerCase() === 'bitcoin' || b.originChain.toLowerCase() === 'bitcoinTestnet') { - return 1; - } else { - return a.originChain.localeCompare(b.originChain); + const aChain = a.originChain.toLowerCase(); + const bChain = b.originChain.toLowerCase(); + + if (aChain === 'bitcoin') { + return -1; } - }); -} -function getTokenAvailableDestinations (tokenSlug: string, xcmRefMap: Record, chainInfoMap: Record): ChainItemType[] { - if (!tokenSlug) { - return []; - } + if (bChain === 'bitcoin') { + return 1; + } - const result: ChainItemType[] = []; - const originChain = chainInfoMap[_getOriginChainOfAsset(tokenSlug)]; + if (aChain === 'bitcointestnet') { + return -1; + } - // Firstly, push the originChain of token - result.push({ - name: originChain.name, - slug: originChain.slug - }); + if (bChain === 'bitcointestnet') { + return 1; + } - Object.values(xcmRefMap).forEach((xcmRef) => { - if (xcmRef.srcAsset === tokenSlug) { - const destinationChain = chainInfoMap[xcmRef.destChain]; + if (aChain === 'ethereum' && a.name.toLowerCase() === 'ethereum') { + return -1; + } - result.push({ - name: destinationChain.name, - slug: destinationChain.slug - }); + if (bChain === 'ethereum' && b.name.toLowerCase() === 'ethereum') { + return 1; } - }); - return result; + return a.originChain.localeCompare(b.originChain); + }); } -const hiddenFields: Array = ['chain', 'fromProxyId']; +const hiddenFields: Array = ['chain', 'fromProxyId', 'destChain']; const validateFields: Array = ['value', 'to']; const alertModalId = 'confirmation-alert-modal'; @@ -185,31 +166,27 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const assetInfo = useFetchChainAssetInfo(assetValue); const { alertProps, closeAlert, openAlert } = useAlert(alertModalId); - const { chainInfoMap, chainStatusMap } = useSelector((root) => root.chainStore); - const { assetRegistry, multiChainAssetMap, xcmRefMap } = useSelector((root) => root.assetRegistry); + const { assetRegistry, multiChainAssetMap } = useSelector((root) => root.assetRegistry); const { accounts } = useSelector((state: RootState) => state.accountState); const destChainNetworkPrefix = useGetChainPrefixBySlug(destChainValue); const destChainGenesisHash = chainInfoMap[destChainValue]?.substrateInfo?.genesisHash || ''; const checkAction = usePreCheckAction(fromValue, true, detectTranslate('The account you are using is {{accountTitle}}, you cannot send assets with it')); - const nativeTokenBasicInfo = useGetNativeTokenBasicInfo(chainValue); const [feeResetTrigger, setFeeResetTrigger] = useState({}); + const assetRef = useRef(''); + const proxyIdRef = useRef(''); // @ts-ignore const hideMaxButton = useMemo(() => { const chainInfo = chainInfoMap[chainValue]; - return !!chainInfo && !!assetInfo && _isChainEvmCompatible(chainInfo) && destChainValue === chainValue && _isNativeToken(assetInfo); + return !!chainInfo && !!assetInfo && _isPureEvmChain(chainInfo) && destChainValue === chainValue && _isNativeToken(assetInfo); }, [chainInfoMap, chainValue, destChainValue, assetInfo]); const chainStatus = useMemo(() => chainStatusMap[chainValue]?.connectionStatus, [chainValue, chainStatusMap]); - const destChainItems = useMemo(() => { - return getTokenAvailableDestinations(assetValue, xcmRefMap, chainInfoMap); - }, [chainInfoMap, assetValue, xcmRefMap]); - const currentChainAsset = useMemo(() => { const _asset = isFirstRender ? defaultData.asset : assetValue; @@ -238,12 +215,12 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const tokenItems = useMemo(() => { return getTokenItems( - fromValue, + chainInfoMap, assetRegistry, multiChainAssetMap, sendFundSlug ); - }, [assetRegistry, fromValue, multiChainAssetMap, sendFundSlug]); + }, [assetRegistry, chainInfoMap, multiChainAssetMap, sendFundSlug]); const [loading, setLoading] = useState(false); const [isTransferAll, setIsTransferAll] = useState(false); @@ -255,8 +232,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const [transferInfo, setTransferInfo] = useState(); const [transactionFeeInfo, setTransactionFeeInfo] = useState(undefined); - const estimatedFee = useMemo((): string => transferInfo?.feeOptions.estimatedFee || '0', [transferInfo]); - const handleTransferAll = useCallback((value: boolean) => { setForceUpdateMaxValue({}); setIsTransferAll(value); @@ -282,7 +257,9 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const addressType = getKeypairTypeByAddress(_recipientAddress); - if (addressType === 'ethereum' && chain === 'ethereum') { + const chainInfo = chainInfoMap[chainValue]; + + if (addressType === 'ethereum' && _isPureEvmChain(chainInfo)) { return Promise.resolve(); } @@ -295,7 +272,7 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } return Promise.reject(t('Invalid recipient address')); - }, [form, t]); + }, [chainInfoMap, chainValue, form, t]); const validateAmount = useCallback((rule: Rule, amount: string): Promise => { if (!amount) { @@ -331,13 +308,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } } - if (part.from) { - setForceUpdateMaxValue(undefined); - form.resetFields(['asset']); - // Because cache data, so next data may be same with default data - form.setFields([{ name: 'asset', value: '' }]); - } - if (part.destChain) { setForceUpdateMaxValue(isTransferAll ? {} : undefined); @@ -504,8 +474,18 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const addressBookFilter = useCallback((addressJson: AbstractAddressJson): boolean => { const addressType = getKeypairTypeByAddress(addressJson.address); - return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(addressType); - }, []); + const chainInfo = chainInfoMap[chainValue]; + + if (chainValue === 'bitcoin') { + return 'bitcoin-84'.includes(addressType) && addressJson.address !== fromValue; + } else if (chainValue === 'bitcoinTestnet') { + return 'bittest-84'.includes(addressType) && addressJson.address !== fromValue; + } else if (!!chainInfo && _isPureEvmChain(chainInfo)) { + return 'ethereum'.includes(addressType) && addressJson.address !== fromValue; + } + + return false; + }, [chainInfoMap, chainValue, fromValue]); // TODO: Need to review // Auto fill logic @@ -604,9 +584,22 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } const accountType = account.type || getKeypairTypeByAddress(account.address); + const chainInfo = chainInfoMap[chainValue]; - return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(accountType); - }, [fromProxyId]); + if (chainValue === 'bitcoin') { + return 'bitcoin-84'.includes(accountType); + } else if (chainValue === 'bitcoinTestnet') { + return 'bittest-84'.includes(accountType); + } else if (!!chainInfo && _isPureEvmChain(chainInfo)) { + return 'ethereum'.includes(accountType); + } + + return false; + }, [chainInfoMap, chainValue, fromProxyId]); + + const accountList = useMemo(() => { + return accounts.filter(accountsFilter); + }, [accounts, accountsFilter]); useEffect(() => { const bnTransferAmount = new BigN(transferAmountValue || '0'); @@ -617,6 +610,46 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } }, [transferInfo, transferAmountValue]); + useEffect(() => { + if (accountList.length === 1) { + const addressType = accountList?.[0]?.type; + + form.setFieldsValue({ + from: accountList?.[0]?.address || '' + }); + + const oldToAccount = accounts.find((item) => item.address === toValue); + + if (oldToAccount?.type !== addressType) { + const newToAccount = accounts.find((item) => item.proxyId === oldToAccount?.proxyId && item.type === addressType); + + form.setFieldsValue({ + to: newToAccount?.address || '' + }); + } + } + + if (accountList.length > 1 && !!fromValue && !!assetValue && assetRef.current !== assetValue) { + assetRef.current = assetValue; + const currentFromAccount = accountList.find((item) => (item.address === fromValue) || (item.proxyId === proxyIdRef.current)); + + proxyIdRef.current = currentFromAccount?.proxyId; + + form.setFieldsValue({ + from: currentFromAccount?.address || '' + }); + + if (toValue) { + const currentToAccount = accounts.find((item) => item.address === toValue); + const newToAccount = accountList.find((item) => item.proxyId === currentToAccount?.proxyId); + + form.setFieldsValue({ + to: newToAccount?.address || '' + }); + } + } + }, [accountList, accounts, assetValue, form, fromProxyId, fromValue, toValue]); + useRestoreTransaction(form); useInitValidateTransaction(validateFields, form, defaultData); @@ -634,15 +667,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { onFinish={onSubmit} onValuesChange={onValuesChange} > - - - -
=> { tooltip={t('Select token')} /> +
+ + + +
+ + + - - +
- - - - - - => { - + />} { BITCOIN_CHAINS.includes(chainValue) && !!transferInfo && !!assetValue && ( @@ -739,24 +769,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { ) } - { - chainValue === 'ethereum' && !!transferInfo && !isFetchingInfo && ( -
-
- Estimated fee:  -
- -
- -
-
- ) - } - { chainValue !== destChainValue && (
@@ -813,7 +825,7 @@ const SendFund = styled(_SendFund)(({ theme }) => { gap: 8 }, - '.middle-item': { + '.middle-icon': { marginBottom: token.marginSM }, @@ -829,6 +841,75 @@ const SendFund = styled(_SendFund)(({ theme }) => { } }, + '.__receiver-field': { + '.ant-input-wrapper': { + position: 'relative', + paddingTop: 10, + paddingBottom: 12 + }, + '.ant-input-suffix': { + position: 'absolute', + top: -18, + right: 16, + height: 24 + }, + '.ant-input-suffix .anticon': { + fontSize: `${token.fontSizeLG}px !important` + }, + '.ant-input-suffix .ant-btn': { + minWidth: 24 + }, + '.ant-input-status-icon': { + display: 'none' + }, + '.ant-input': { + borderBottomWidth: 0 + }, + '.__address': { + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingLeft: 0 + }, + '.__overlay': { + paddingRight: 0, + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'center' + }, + '.__name': { + maxWidth: 124 + } + }, + '.__sender-field': { + '.__selected-item': { + flexDirection: 'column' + }, + '.ant-select-modal-input-wrapper': { + minHeight: 66 + }, + '.ant-select-modal-input-label': { + position: 'relative' + }, + '.ant-select-modal-input-suffix': { + position: 'absolute', + top: 8, + right: 6 + }, + '.__selected-item-address': { + paddingLeft: 0, + overflow: 'hidden', + textOverflow: 'ellipsis', + 'white-space': 'nowrap' + }, + '.ant-select-modal-input-placeholder': { + color: token.colorTextTertiary, + fontWeight: 300 + } + }, + '.form-row.sender-receiver-row': { + gap: 2 + }, + '.__free-balance-block + .__bitcoin-fee-selector': { marginTop: token.marginSM }, diff --git a/packages/extension-koni-ui/src/hooks/screen/home/useReceiveQR.ts b/packages/extension-koni-ui/src/hooks/screen/home/useReceiveQR.ts index 7db1d32ab4b..b0248ed9c75 100644 --- a/packages/extension-koni-ui/src/hooks/screen/home/useReceiveQR.ts +++ b/packages/extension-koni-ui/src/hooks/screen/home/useReceiveQR.ts @@ -5,9 +5,8 @@ import type { KeypairType } from '@subwallet/keyring/types'; import { _ChainAsset } from '@subwallet/chain-list/types'; import { AccountJson, AccountProxy } from '@subwallet/extension-base/background/types'; -import { _getMultiChainAsset, _isAssetFungibleToken, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils'; +import { _getMultiChainAsset, _isAssetFungibleToken, _isChainEvmCompatible } from '@subwallet/extension-base/services/chain-service/utils'; import { AccountSelectorModalId } from '@subwallet/extension-koni-ui/components/Modal/AccountSelectorModal'; -import { SUPPORT_CHAINS } from '@subwallet/extension-koni-ui/constants'; import { RECEIVE_QR_MODAL, RECEIVE_TOKEN_SELECTOR_MODAL } from '@subwallet/extension-koni-ui/constants/modal'; import { useChainAssets } from '@subwallet/extension-koni-ui/hooks/assets'; import { RootState } from '@subwallet/extension-koni-ui/stores'; @@ -24,69 +23,13 @@ type ReceiveSelectedResult = { selectedNetwork?: string; }; -function getTokenSelectorItem (asset: _ChainAsset, accountProxy: AccountProxy): ReceiveTokenItemType | null { - if (!_isAssetFungibleToken(asset) || !SUPPORT_CHAINS.includes(asset.originChain)) { - return null; - } - - let targetAccount: AccountJson | undefined; - - for (const account of accountProxy.accounts) { - const accountType = getKeypairTypeByAddress(account.address); - - if ((accountType === 'ethereum' && asset.originChain === 'ethereum' && _isNativeToken(asset)) || - (accountType === 'bitcoin-84' && asset.originChain === 'bitcoin') || - (accountType === 'bitcoin-86' && asset.originChain === 'bitcoin' && asset.metadata?.runeId) || - (accountType === 'bittest-84' && asset.originChain === 'bitcoinTestnet')) { - targetAccount = { - ...account, - type: accountType - }; - - break; - } - } - - if (!targetAccount) { - return null; - } - - const isRune = !!asset.metadata?.runeId; - const order = (() => { - if (isRune) { - return 2; - } - - if (asset.originChain === 'bitcoin') { - return 1; - } - - if (asset.originChain === 'bitcoinTestnet') { - return 3; - } - - if (asset.originChain === 'ethereum') { - return 4; - } - - return 99; - })(); - - return { - ...asset, - address: targetAccount.address, - addressType: targetAccount.type as KeypairType, - isRune, - order - }; -} - export default function useReceiveQR (tokenGroupSlug?: string) { const { activeModal, inactiveModal } = useContext(ModalContext); const isAllAccount = useSelector((state: RootState) => state.accountState.isAllAccount); const accountProxies = useSelector((state: RootState) => state.accountState.accountProxies); const currentAccountProxy = useSelector((state: RootState) => state.accountState.currentAccountProxy); const assetRegistryMap = useChainAssets().getChainAssetRegistry(); + const chainInfoMap = useSelector((state: RootState) => state.chainStore.chainInfoMap); const [tokenSelectorItems, setTokenSelectorItems] = useState([]); const [{ selectedAccountProxyAddress, selectedAccountProxyId, selectedNetwork }, setReceiveSelectedResult] = useState( { selectedAccountProxyId: isAllAccount ? undefined : currentAccountProxy?.proxyId } @@ -100,6 +43,67 @@ export default function useReceiveQR (tokenGroupSlug?: string) { return accountProxies.filter((ap) => !checkIsAccountAll(ap.proxyId)); }, [isAllAccount, accountProxies]); + const evmChains = useMemo(() => { + return Object.values(chainInfoMap).filter((chain) => _isChainEvmCompatible(chain)).map((chain) => chain.slug); + }, [chainInfoMap]); + + const getTokenSelectorItem = useCallback((asset: _ChainAsset, accountProxy: AccountProxy) => { + if (!_isAssetFungibleToken(asset)) { + return null; + } + + let targetAccount: AccountJson | undefined; + + for (const account of accountProxy.accounts) { + const accountType = getKeypairTypeByAddress(account.address); + + if ((accountType === 'ethereum' && evmChains.includes(asset.originChain)) || + (accountType === 'bitcoin-84' && asset.originChain === 'bitcoin') || + (accountType === 'bitcoin-86' && asset.originChain === 'bitcoin' && asset.metadata?.runeId) || + (accountType === 'bittest-84' && asset.originChain === 'bitcoinTestnet')) { + targetAccount = { + ...account, + type: accountType + }; + + break; + } + } + + if (!targetAccount) { + return null; + } + + const isRune = !!asset.metadata?.runeId; + const order = (() => { + if (isRune) { + return 2; + } + + if (asset.originChain === 'bitcoin') { + return 1; + } + + if (asset.originChain === 'bitcoinTestnet') { + return 3; + } + + if (asset.originChain === 'ethereum') { + return 4; + } + + return 99; + })(); + + return { + ...asset, + address: targetAccount.address, + addressType: targetAccount.type as KeypairType, + isRune, + order + }; + }, [evmChains]); + const getTokenSelectorItems = useCallback((accountProxy: AccountProxy) => { // if tokenGroupSlug is token slug if (tokenGroupSlug && assetRegistryMap[tokenGroupSlug]) { @@ -139,7 +143,7 @@ export default function useReceiveQR (tokenGroupSlug?: string) { result.sort((a, b) => a.order - b.order); return result; - }, [tokenGroupSlug, assetRegistryMap]); + }, [tokenGroupSlug, assetRegistryMap, getTokenSelectorItem]); const onOpenReceive = useCallback(() => { if (!currentAccountProxy) {