From 6119fda80a4f09d0f5f822169fba4e9a8d121178 Mon Sep 17 00:00:00 2001 From: nguyentiendung Date: Wed, 29 May 2024 17:23:51 +0700 Subject: [PATCH 1/7] [Issue-64] Update Send Fund UX --- .../Popup/Transaction/variants/SendFund.tsx | 102 ++++++------------ 1 file changed, 33 insertions(+), 69 deletions(-) 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..724df72f1a0 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -8,13 +8,13 @@ import { _getAssetDecimals, _getOriginChainOfAsset, _getTokenMinAmount, _isAsset 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 { AccountSelector, AddressInput, AlertBox, AlertModal, AmountInput, BitcoinFeeSelector, 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 { 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'; @@ -22,7 +22,7 @@ import { Button, Form, Icon, Number } 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 { PaperPlaneTilt } from 'phosphor-react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; @@ -52,10 +52,6 @@ function getTokenItems ( 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); @@ -94,9 +90,9 @@ function getTokenItems ( return; } - if (!checkValidBetweenAddressTypeAndChain(addressType, chainAsset.originChain)) { - return; - } + // if (!checkValidBetweenAddressTypeAndChain(addressType, chainAsset.originChain)) { + // return; + // } if (isSetMultiChainAssetSlug) { if (chainAsset.multiChainAsset === tokenGroupSlug) { @@ -128,34 +124,6 @@ function getTokenItems ( }); } -function getTokenAvailableDestinations (tokenSlug: string, xcmRefMap: Record, chainInfoMap: Record): ChainItemType[] { - if (!tokenSlug) { - return []; - } - - const result: ChainItemType[] = []; - const originChain = chainInfoMap[_getOriginChainOfAsset(tokenSlug)]; - - // Firstly, push the originChain of token - result.push({ - name: originChain.name, - slug: originChain.slug - }); - - Object.values(xcmRefMap).forEach((xcmRef) => { - if (xcmRef.srcAsset === tokenSlug) { - const destinationChain = chainInfoMap[xcmRef.destChain]; - - result.push({ - name: destinationChain.name, - slug: destinationChain.slug - }); - } - }); - - return result; -} - const hiddenFields: Array = ['chain', 'fromProxyId']; const validateFields: Array = ['value', 'to']; const alertModalId = 'confirmation-alert-modal'; @@ -187,7 +155,7 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { 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); @@ -206,10 +174,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { 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; @@ -504,8 +468,16 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const addressBookFilter = useCallback((addressJson: AbstractAddressJson): boolean => { const addressType = getKeypairTypeByAddress(addressJson.address); + 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 (chainValue === 'ethereum') { + return 'ethereum'.includes(addressType) && addressJson.address !== fromValue; + } + return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(addressType); - }, []); + }, [chainValue, fromValue]); // TODO: Need to review // Auto fill logic @@ -605,8 +577,16 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const accountType = account.type || getKeypairTypeByAddress(account.address); + if (chainValue === 'bitcoin') { + return 'bitcoin-84'.includes(accountType); + } else if (chainValue === 'bitcoinTestnet') { + return 'bittest-84'.includes(accountType); + } else if (chainValue === 'ethereum') { + return 'ethereum'.includes(accountType); + } + return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(accountType); - }, [fromProxyId]); + }, [chainValue, fromProxyId]); useEffect(() => { const bnTransferAmount = new BigN(transferAmountValue || '0'); @@ -634,15 +614,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { onFinish={onSubmit} onValuesChange={onValuesChange} > - - - -
=> { tooltip={t('Select token')} /> - - - - - -
+ + + From f1a8840c214916e3a6cc39da15abf262cfa138a0 Mon Sep 17 00:00:00 2001 From: nguyentiendung Date: Thu, 30 May 2024 15:47:39 +0700 Subject: [PATCH 2/7] [Issue-64] Update Send Fund UX --- .../src/Popup/Home/Tokens/DetailList.tsx | 3 +- .../Popup/Transaction/variants/SendFund.tsx | 141 ++++++++++-------- 2 files changed, 83 insertions(+), 61 deletions(-) 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 724df72f1a0..c93811b4de0 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -1,15 +1,15 @@ // 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, HiddenInput, TokenItemType, TokenSelector } from '@subwallet/extension-koni-ui/components'; -import { BITCOIN_CHAINS, SUPPORT_CHAINS } from '@subwallet/extension-koni-ui/constants'; +import { BITCOIN_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 { cancelSubscription, makeCrossChainTransfer, makeTransfer, subscribeMaxTransfer } from '@subwallet/extension-koni-ui/messaging'; import { FreeBalance } from '@subwallet/extension-koni-ui/Popup/Transaction/parts'; @@ -17,7 +17,6 @@ import { RootState } from '@subwallet/extension-koni-ui/stores'; 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 { Rule } from '@subwallet/react-ui/es/form'; import BigN from 'bignumber.js'; @@ -34,27 +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[] { const isSetTokenSlug = !!tokenGroupSlug && !!assetRegistry[tokenGroupSlug]; const isSetMultiChainAssetSlug = !!tokenGroupSlug && !!multiChainAssetMap[tokenGroupSlug]; - const addressType = getKeypairTypeByAddress(address); if (tokenGroupSlug) { if (!(isSetTokenSlug || isSetMultiChainAssetSlug)) { @@ -66,10 +64,6 @@ function getTokenItems ( if (isSetTokenSlug && chainAsset) { const { name, originChain, slug, symbol } = chainAsset; - if (!checkValidBetweenAddressTypeAndChain(addressType, originChain)) { - return []; - } - return [ { name, @@ -84,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, @@ -103,28 +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') { + const aChain = a.originChain.toLowerCase(); + const bChain = b.originChain.toLowerCase(); + + if (aChain === 'bitcoin') { + return -1; + } + + if (bChain === 'bitcoin') { return 1; - } else { - return a.originChain.localeCompare(b.originChain); } + + if (aChain === 'bitcointestnet') { + return -1; + } + + if (bChain === 'bitcointestnet') { + return 1; + } + + if (aChain === 'ethereum' && a.name.toLowerCase() === 'ethereum') { + return -1; + } + + if (bChain === 'ethereum' && b.name.toLowerCase() === 'ethereum') { + return 1; + } + + 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'; @@ -153,7 +166,6 @@ 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 } = useSelector((root) => root.assetRegistry); const { accounts } = useSelector((state: RootState) => state.accountState); @@ -169,7 +181,7 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { 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]); @@ -202,12 +214,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); @@ -295,13 +307,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); @@ -468,16 +473,18 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const addressBookFilter = useCallback((addressJson: AbstractAddressJson): boolean => { const addressType = getKeypairTypeByAddress(addressJson.address); + 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 (chainValue === 'ethereum') { + } else if (!!chainInfo && _isPureEvmChain(chainInfo)) { return 'ethereum'.includes(addressType) && addressJson.address !== fromValue; } - return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(addressType); - }, [chainValue, fromValue]); + return false; + }, [chainInfoMap, chainValue, fromValue]); // TODO: Need to review // Auto fill logic @@ -576,17 +583,22 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } const accountType = account.type || getKeypairTypeByAddress(account.address); + const chainInfo = chainInfoMap[chainValue]; if (chainValue === 'bitcoin') { return 'bitcoin-84'.includes(accountType); } else if (chainValue === 'bitcoinTestnet') { return 'bittest-84'.includes(accountType); - } else if (chainValue === 'ethereum') { + } else if (!!chainInfo && _isPureEvmChain(chainInfo)) { return 'ethereum'.includes(accountType); } - return ['ethereum', 'bitcoin-84', 'bittest-84'].includes(accountType); - }, [chainValue, fromProxyId]); + return false; + }, [chainInfoMap, chainValue, fromProxyId]); + + const accountList = useMemo(() => { + return accounts.filter(accountsFilter); + }, [accounts, accountsFilter]); useEffect(() => { const bnTransferAmount = new BigN(transferAmountValue || '0'); @@ -597,6 +609,14 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } }, [transferInfo, transferAmountValue]); + useEffect(() => { + if (accountList.length === 1) { + form.setFieldsValue({ + from: accountList?.[0]?.address || '' + }); + } + }, [accountList, form]); + useRestoreTransaction(form); useInitValidateTransaction(validateFields, form, defaultData); @@ -629,7 +649,8 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { name={'from'} > From 5cd04909609121543351d540c175771e77a79185 Mon Sep 17 00:00:00 2001 From: AnhMTV Date: Fri, 31 May 2024 14:55:26 +0700 Subject: [PATCH 3/7] [Issue-102] Re-enable evm token in getAddress list --- .../src/hooks/screen/home/useReceiveQR.ts | 124 +++++++++--------- 1 file changed, 64 insertions(+), 60 deletions(-) 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) { From 7a0fc1b59d6f26fe9606c9fdeb47e8394db669f3 Mon Sep 17 00:00:00 2001 From: nguyentiendung Date: Fri, 31 May 2024 16:18:57 +0700 Subject: [PATCH 4/7] [Issue-64] Improve UI for Send Fund screen --- .../Popup/Transaction/variants/SendFund.tsx | 157 +++++++++++------- 1 file changed, 97 insertions(+), 60 deletions(-) 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 c93811b4de0..59999d02bbe 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -10,18 +10,18 @@ import { BitcoinFeeDetail, ResponseSubscribeTransfer, TransactionFee } from '@su import { BN_ZERO, detectTranslate } from '@subwallet/extension-base/utils'; 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, useGetNativeTokenBasicInfo, useHandleSubmitTransaction, useInitValidateTransaction, useNotification, usePreCheckAction, useRestoreTransaction, useSelector, useSetCurrentPage, useTransactionContext, useWatchTransaction } from '@subwallet/extension-koni-ui/hooks'; +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 { 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 { 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 { PaperPlaneTilt } from 'phosphor-react'; +import { ArrowRight, PaperPlaneTilt } from 'phosphor-react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; @@ -173,7 +173,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { 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({}); @@ -231,8 +230,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); @@ -258,7 +255,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(); } @@ -271,7 +270,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) { @@ -307,6 +306,10 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } } + if (part.asset) { + form.setFields([{ name: 'to', value: '' }]); + } + if (part.destChain) { setForceUpdateMaxValue(isTransferAll ? {} : undefined); @@ -645,42 +648,53 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { /> - - - - - + + + + + - + + + + + => { - + />} { BITCOIN_CHAINS.includes(chainValue) && !!transferInfo && !!assetValue && ( @@ -724,24 +738,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { ) } - { - chainValue === 'ethereum' && !!transferInfo && !isFetchingInfo && ( -
-
- Estimated fee:  -
- -
- -
-
- ) - } - { chainValue !== destChainValue && (
@@ -814,6 +810,47 @@ const SendFund = styled(_SendFund)(({ theme }) => { } }, + '.__form-to-value': { + '.ant-input-wrapper': { + position: 'relative', + paddingTop: 10, + paddingBottom: 12 + }, + '.ant-input-suffix': { + position: 'absolute', + top: -32, + right: 6 + }, + '.__address': { + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingLeft: 0 + }, + '.__overlay': { + paddingRight: 0, + flexDirection: 'column', + alignItems: 'flex-start', + paddingTop: 12 + } + }, + '.__form-from-item': { + '.__selected-item': { + flexDirection: 'column' + }, + '.ant-select-modal-input-wrapper': { + minHeight: 66 + }, + '.__selected-item-address': { + paddingLeft: 0, + overflow: 'hidden', + textOverflow: 'ellipsis', + 'white-space': 'nowrap' + } + }, + '.form-row.from-to-value': { + gap: 2 + }, + '.__free-balance-block + .__bitcoin-fee-selector': { marginTop: token.marginSM }, From 11e59fbc5daa7316828251c88dabdc7f4301f677 Mon Sep 17 00:00:00 2001 From: nguyentiendung Date: Fri, 31 May 2024 16:30:35 +0700 Subject: [PATCH 5/7] [Issue-64] refactor code --- .../src/Popup/Transaction/variants/SendFund.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 59999d02bbe..d76a389eb75 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -651,12 +651,12 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { -
+
=> { { gap: 8 }, - '.middle-item': { + '.middle-icon': { marginBottom: token.marginSM }, @@ -810,7 +810,7 @@ const SendFund = styled(_SendFund)(({ theme }) => { } }, - '.__form-to-value': { + '.__receiver-field': { '.ant-input-wrapper': { position: 'relative', paddingTop: 10, @@ -833,7 +833,7 @@ const SendFund = styled(_SendFund)(({ theme }) => { paddingTop: 12 } }, - '.__form-from-item': { + '.__sender-field': { '.__selected-item': { flexDirection: 'column' }, @@ -847,7 +847,7 @@ const SendFund = styled(_SendFund)(({ theme }) => { 'white-space': 'nowrap' } }, - '.form-row.from-to-value': { + '.form-row.sender-receiver-row': { gap: 2 }, From 03b615c9a7e0057c84c277983adbd646bd902f6e Mon Sep 17 00:00:00 2001 From: nguyentiendung Date: Fri, 31 May 2024 19:31:10 +0700 Subject: [PATCH 6/7] [Issue-64] Improve UX for send fund screen --- .../Popup/Transaction/variants/SendFund.tsx | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) 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 d76a389eb75..82554f3dfd6 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -653,13 +653,13 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => {
@@ -667,6 +667,7 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { className={'middle-icon'} phosphorIcon={ArrowRight} size={'xs'} + weight={'fill'} /> => { allowDomain={true} chain={destChainValue} fitNetwork={true} - label={t('Send to')} + label={t('To')} networkGenesisHash={destChainGenesisHash} placeholder={t('Account address')} saveAddress={true} @@ -818,8 +819,21 @@ const SendFund = styled(_SendFund)(({ theme }) => { }, '.ant-input-suffix': { position: 'absolute', - top: -32, - right: 6 + 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', @@ -830,7 +844,10 @@ const SendFund = styled(_SendFund)(({ theme }) => { paddingRight: 0, flexDirection: 'column', alignItems: 'flex-start', - paddingTop: 12 + justifyContent: 'center' + }, + '.__name': { + maxWidth: 124 } }, '.__sender-field': { @@ -840,6 +857,14 @@ const SendFund = styled(_SendFund)(({ theme }) => { '.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', From 39492d5c721d75060cc1d0b2497506ea1118a64d Mon Sep 17 00:00:00 2001 From: Dung Nguyen Date: Sat, 1 Jun 2024 21:35:12 +0700 Subject: [PATCH 7/7] [Issue-64] Improve auto format address from, to of sendfund screen --- .../Popup/Transaction/variants/SendFund.tsx | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) 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 82554f3dfd6..61d81f7a61d 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/SendFund.tsx @@ -22,7 +22,7 @@ import { Rule } from '@subwallet/react-ui/es/form'; import BigN from 'bignumber.js'; import CN from 'classnames'; import { ArrowRight, PaperPlaneTilt } from 'phosphor-react'; -import React, { useCallback, useEffect, useMemo, useState } from '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'; @@ -175,6 +175,8 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { const checkAction = usePreCheckAction(fromValue, true, detectTranslate('The account you are using is {{accountTitle}}, you cannot send assets with it')); const [feeResetTrigger, setFeeResetTrigger] = useState({}); + const assetRef = useRef(''); + const proxyIdRef = useRef(''); // @ts-ignore const hideMaxButton = useMemo(() => { @@ -306,10 +308,6 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { } } - if (part.asset) { - form.setFields([{ name: 'to', value: '' }]); - } - if (part.destChain) { setForceUpdateMaxValue(isTransferAll ? {} : undefined); @@ -614,11 +612,43 @@ const _SendFund = ({ className = '' }: Props): React.ReactElement => { 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, form]); + }, [accountList, accounts, assetValue, form, fromProxyId, fromValue, toValue]); useRestoreTransaction(form); useInitValidateTransaction(validateFields, form, defaultData); @@ -870,6 +900,10 @@ const SendFund = styled(_SendFund)(({ theme }) => { overflow: 'hidden', textOverflow: 'ellipsis', 'white-space': 'nowrap' + }, + '.ant-select-modal-input-placeholder': { + color: token.colorTextTertiary, + fontWeight: 300 } }, '.form-row.sender-receiver-row': {