From 3244ff153eaf236169c8ae3954a6474d468a74b0 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Thu, 6 Jun 2024 09:48:26 -0400 Subject: [PATCH 01/24] Fixes #24915 --- ui/components/multichain/pages/send/send.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/components/multichain/pages/send/send.js b/ui/components/multichain/pages/send/send.js index 5eef64f986ec..c47a22e6ef69 100644 --- a/ui/components/multichain/pages/send/send.js +++ b/ui/components/multichain/pages/send/send.js @@ -281,6 +281,8 @@ export const SendPage = () => { [dispatch], ); + const tooltipTitle = isSwapAndSend ? t('sendSwapSubmissionWarning') : ''; + return (
{ {sendStage === SEND_STAGES.EDIT ? t('reject') : t('cancel')} Date: Thu, 6 Jun 2024 17:39:23 -0400 Subject: [PATCH 02/24] refactor: check for insufficient tokens last --- ui/ducks/send/send.js | 70 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 0ba18ed8b08e..3619f6034c62 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -1448,41 +1448,6 @@ const slice = createSlice({ const amountValue = new Numeric(draftTransaction.amount.value, 16); switch (true) { - // set error to INSUFFICIENT_FUNDS_FOR_GAS_ERROR if the account balance is lower - // than the total price of the transaction inclusive of gas fees. - case draftTransaction.sendAsset.type === AssetType.native && - !isBalanceSufficient({ - amount: draftTransaction.amount.value, - balance: draftTransaction.sendAsset.balance, - gasTotal: draftTransaction.gas.gasTotal ?? '0x0', - }): { - const isInsufficientWithoutGas = !isBalanceSufficient({ - amount: draftTransaction.amount.value, - balance: draftTransaction.sendAsset.balance, - gasTotal: '0x0', // assume gas is free - }); - - draftTransaction.amount.error = isInsufficientWithoutGas - ? INSUFFICIENT_FUNDS_ERROR - : INSUFFICIENT_FUNDS_FOR_GAS_ERROR; - if (draftTransaction.status !== SEND_STATUSES.INVALID) { - slice.caseReducers.validateSendState(state); - } - break; - } - // set error to INSUFFICIENT_TOKENS_ERROR if the token balance is lower - // than the amount of token the user is attempting to send. - case draftTransaction.sendAsset.type === AssetType.token && - !isTokenBalanceSufficient({ - tokenBalance: draftTransaction.sendAsset.balance ?? '0x0', - amount: draftTransaction.amount.value, - decimals: draftTransaction.sendAsset.details.decimals, - }): - draftTransaction.amount.error = INSUFFICIENT_TOKENS_ERROR; - if (draftTransaction.status !== SEND_STATUSES.INVALID) { - slice.caseReducers.validateSendState(state); - } - break; // INSUFFICIENT_TOKENS_ERROR if the user is attempting to transfer ERC1155 but has 0 amount selected // prevents the user from transferring 0 tokens case draftTransaction.sendAsset.type === AssetType.NFT && @@ -1518,6 +1483,41 @@ const slice = createSlice({ slice.caseReducers.validateSendState(state); } break; + // set error to INSUFFICIENT_FUNDS_FOR_GAS_ERROR if the account balance is lower + // than the total price of the transaction inclusive of gas fees. + case draftTransaction.sendAsset.type === AssetType.native && + !isBalanceSufficient({ + amount: draftTransaction.amount.value, + balance: draftTransaction.sendAsset.balance, + gasTotal: draftTransaction.gas.gasTotal ?? '0x0', + }): { + const isInsufficientWithoutGas = !isBalanceSufficient({ + amount: draftTransaction.amount.value, + balance: draftTransaction.sendAsset.balance, + gasTotal: '0x0', // assume gas is free + }); + + draftTransaction.amount.error = isInsufficientWithoutGas + ? INSUFFICIENT_FUNDS_ERROR + : INSUFFICIENT_FUNDS_FOR_GAS_ERROR; + if (draftTransaction.status !== SEND_STATUSES.INVALID) { + slice.caseReducers.validateSendState(state); + } + break; + } + // set error to INSUFFICIENT_TOKENS_ERROR if the token balance is lower + // than the amount of token the user is attempting to send. + case draftTransaction.sendAsset.type === AssetType.token && + !isTokenBalanceSufficient({ + tokenBalance: draftTransaction.sendAsset.balance ?? '0x0', + amount: draftTransaction.amount.value, + decimals: draftTransaction.sendAsset.details.decimals, + }): + draftTransaction.amount.error = INSUFFICIENT_TOKENS_ERROR; + if (draftTransaction.status !== SEND_STATUSES.INVALID) { + slice.caseReducers.validateSendState(state); + } + break; // If none of the above are true, set error to null default: draftTransaction.amount.error = null; From a492bb54aae04df286143cf4d3ca69b3274aa4a3 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Thu, 6 Jun 2024 17:40:10 -0400 Subject: [PATCH 03/24] chore: do not block send tx when balance is unavailable --- ui/ducks/send/send.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 3619f6034c62..6d204737b0d5 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -1641,10 +1641,20 @@ const slice = createSlice({ if (draftTransaction) { const isSwapAndSend = getIsDraftSwapAndSend(draftTransaction); + const getIsIgnorableAmountError = () => + [ + INSUFFICIENT_TOKENS_ERROR, + INSUFFICIENT_FUNDS_ERROR, + INSUFFICIENT_FUNDS_FOR_GAS_ERROR, + ].includes(draftTransaction.amount.error) && + !draftTransaction.sendAsset.balance; + const { quotes } = draftTransaction; const bestQuote = quotes ? calculateBestQuote(quotes) : undefined; switch (true) { - case Boolean(draftTransaction.amount.error): + case Boolean( + draftTransaction.amount.error && !getIsIgnorableAmountError(), + ): slice.caseReducers.addHistoryEntry(state, { payload: `Amount is in error ${draftTransaction.amount.error}`, }); From 142171a744d6c9cd0ff8f72d81d601c15b254042 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Thu, 6 Jun 2024 18:21:54 -0400 Subject: [PATCH 04/24] chore: ensure token parity with home page --- .../asset-picker-modal/asset-picker-modal.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index 1a5e4822b1fa..b95137bd80fb 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -37,6 +37,7 @@ import { AssetType } from '../../../../../shared/constants/transaction'; import { useNftsCollections } from '../../../../hooks/useNftsCollections'; import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; import { + getAllTokens, getCurrentChainId, getCurrentCurrency, getNativeCurrencyImage, @@ -49,7 +50,6 @@ import { import { getConversionRate, getNativeCurrency, - getTokens, } from '../../../../ducks/metamask/metamask'; import { useTokenTracker } from '../../../../hooks/useTokenTracker'; import { getTopAssets } from '../../../../ducks/swaps/swaps'; @@ -153,7 +153,10 @@ export function AssetPickerModal({ const shouldHideZeroBalanceTokens = useSelector( getShouldHideZeroBalanceTokens, ); - const tokens = useSelector(getTokens, isEqual); + + const detectedTokens = useSelector(getAllTokens); + const tokens = detectedTokens?.[chainId]?.[selectedAddress] ?? []; + const { tokensWithBalances } = useTokenTracker({ tokens, address: selectedAddress, From 86c5fcebb271f4cb88b1f6433c0888677c0b7f55 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 00:20:44 -0400 Subject: [PATCH 05/24] fix: use case-insensitive address compare fixes 24922 --- ui/ducks/send/helpers.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/ducks/send/helpers.js b/ui/ducks/send/helpers.js index a706512f5eb5..43582c6e3504 100644 --- a/ui/ducks/send/helpers.js +++ b/ui/ducks/send/helpers.js @@ -32,6 +32,7 @@ import { fetchTokenExchangeRates } from '../../helpers/utils/util'; import { hexToDecimal } from '../../../shared/modules/conversion.utils'; import { EtherDenomination } from '../../../shared/constants/common'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP } from '../../../shared/constants/swaps'; +import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; export async function estimateGasLimitForSend({ selectedAddress, @@ -422,8 +423,8 @@ export async function getERC20Balance(token, accountAddress) { * @returns {boolean} true if the draft transaction is a swap and send */ export function getIsDraftSwapAndSend(draftTransaction) { - return ( - draftTransaction?.sendAsset?.details?.address !== - draftTransaction?.receiveAsset?.details?.address + return !isEqualCaseInsensitive( + draftTransaction?.sendAsset?.details?.address || '', + draftTransaction?.receiveAsset?.details?.address || '', ); } From 53753ddc7ba9b02e43f9afd8887d93c8f118a9f7 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 03:45:34 -0400 Subject: [PATCH 06/24] chore: add nft detect banner to modal fixes 25015 --- .../asset-picker-modal/asset-picker-modal.tsx | 73 ++++++++++++------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index b95137bd80fb..377f7af3ca47 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -40,12 +40,14 @@ import { getAllTokens, getCurrentChainId, getCurrentCurrency, + getIsMainnet, getNativeCurrencyImage, getSelectedAccountCachedBalance, getSelectedInternalAccount, getShouldHideZeroBalanceTokens, getTokenExchangeRates, getTokenList, + getUseNftDetection, } from '../../../../selectors'; import { getConversionRate, @@ -61,6 +63,7 @@ import { } from '../../../../../shared/constants/metametrics'; import { MetaMetricsContext } from '../../../../contexts/metametrics'; import { getSendAnalyticProperties } from '../../../../ducks/send'; +import NFTsDetectionNoticeNFTsTab from '../../../app/nfts-detection-notice-nfts-tab/nfts-detection-notice-nfts-tab'; import { Asset, Collection, Token } from './types'; import AssetList from './AssetList'; @@ -154,6 +157,9 @@ export function AssetPickerModal({ getShouldHideZeroBalanceTokens, ); + const useNftDetection = useSelector(getUseNftDetection); + const isMainnet = useSelector(getIsMainnet); + const detectedTokens = useSelector(getAllTokens); const tokens = detectedTokens?.[chainId]?.[selectedAddress] ?? []; @@ -368,40 +374,51 @@ export function AssetPickerModal({ /> ) : ( - - - - + <> + {isMainnet && !useNftDetection && ( + + + + )} - + + + - {t('noNFTs')} - - - {t('learnMoreUpperCase')} - + + {t('noNFTs')} + + + {t('learnMoreUpperCase')} + + - + )} } From ed69dcccc6341f73bef4d47443e28383d8ed8f14 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 04:11:16 -0400 Subject: [PATCH 07/24] refactor: check for token balance before native --- ui/ducks/send/send.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 6d204737b0d5..062bc160872d 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -1483,6 +1483,19 @@ const slice = createSlice({ slice.caseReducers.validateSendState(state); } break; + // set error to INSUFFICIENT_TOKENS_ERROR if the token balance is lower + // than the amount of token the user is attempting to send. + case draftTransaction.sendAsset.type === AssetType.token && + !isTokenBalanceSufficient({ + tokenBalance: draftTransaction.sendAsset.balance ?? '0x0', + amount: draftTransaction.amount.value, + decimals: draftTransaction.sendAsset.details.decimals, + }): + draftTransaction.amount.error = INSUFFICIENT_TOKENS_ERROR; + if (draftTransaction.status !== SEND_STATUSES.INVALID) { + slice.caseReducers.validateSendState(state); + } + break; // set error to INSUFFICIENT_FUNDS_FOR_GAS_ERROR if the account balance is lower // than the total price of the transaction inclusive of gas fees. case draftTransaction.sendAsset.type === AssetType.native && @@ -1505,19 +1518,6 @@ const slice = createSlice({ } break; } - // set error to INSUFFICIENT_TOKENS_ERROR if the token balance is lower - // than the amount of token the user is attempting to send. - case draftTransaction.sendAsset.type === AssetType.token && - !isTokenBalanceSufficient({ - tokenBalance: draftTransaction.sendAsset.balance ?? '0x0', - amount: draftTransaction.amount.value, - decimals: draftTransaction.sendAsset.details.decimals, - }): - draftTransaction.amount.error = INSUFFICIENT_TOKENS_ERROR; - if (draftTransaction.status !== SEND_STATUSES.INVALID) { - slice.caseReducers.validateSendState(state); - } - break; // If none of the above are true, set error to null default: draftTransaction.amount.error = null; From 7ef44a41992ee3850b497afb31508848a732cb6c Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 06:23:05 -0400 Subject: [PATCH 08/24] fix: show low gas error for erc20 --- ui/ducks/send/send.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 062bc160872d..2f2129464520 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -1498,17 +1498,21 @@ const slice = createSlice({ break; // set error to INSUFFICIENT_FUNDS_FOR_GAS_ERROR if the account balance is lower // than the total price of the transaction inclusive of gas fees. - case draftTransaction.sendAsset.type === AssetType.native && - !isBalanceSufficient({ - amount: draftTransaction.amount.value, - balance: draftTransaction.sendAsset.balance, - gasTotal: draftTransaction.gas.gasTotal ?? '0x0', - }): { - const isInsufficientWithoutGas = !isBalanceSufficient({ - amount: draftTransaction.amount.value, - balance: draftTransaction.sendAsset.balance, - gasTotal: '0x0', // assume gas is free - }); + case !isBalanceSufficient({ + amount: + draftTransaction.sendAsset.type === AssetType.native + ? draftTransaction.amount.value + : undefined, + balance: state.selectedAccount.balance, + gasTotal: draftTransaction.gas.gasTotal ?? '0x0', + }): { + const isInsufficientWithoutGas = + draftTransaction.sendAsset.type === AssetType.native && + !isBalanceSufficient({ + amount: draftTransaction.amount.value, + balance: draftTransaction.sendAsset.balance, + gasTotal: '0x0', // assume gas is free + }); draftTransaction.amount.error = isInsufficientWithoutGas ? INSUFFICIENT_FUNDS_ERROR From dbc99ce5ceb6e866a0e33d6310115896f3c98f7d Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 06:24:11 -0400 Subject: [PATCH 09/24] chore: add gas validation for swap+send fixes 25133 --- ui/ducks/send/send.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 2f2129464520..c4e8841b369a 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -1653,8 +1653,15 @@ const slice = createSlice({ ].includes(draftTransaction.amount.error) && !draftTransaction.sendAsset.balance; - const { quotes } = draftTransaction; + const { quotes, gas } = draftTransaction; const bestQuote = quotes ? calculateBestQuote(quotes) : undefined; + + const derivedGasPrice = + hexToDecimal(gas?.gasTotal || '0x0') > 0 && + hexToDecimal(gas?.gasLimit || '0x0') > 0 + ? new Numeric(gas.gasTotal, 16).divide(gas.gasLimit, 16).toString() + : undefined; + switch (true) { case Boolean( draftTransaction.amount.error && !getIsIgnorableAmountError(), @@ -1786,6 +1793,24 @@ const slice = createSlice({ }); draftTransaction.status = SEND_STATUSES.INVALID; break; + case bestQuote && + !isBalanceSufficient({ + amount: + draftTransaction.sendAsset.type === AssetType.native + ? draftTransaction.sendAsset.balance + : undefined, + balance: state.selectedAccount.balance, + gasTotal: calcGasTotal( + bestQuote?.gasParams?.maxGas ?? '0x0', + derivedGasPrice ?? '0x0', + ), + }): { + if (!draftTransaction.amount.error) { + draftTransaction.amount.error = INSUFFICIENT_FUNDS_FOR_GAS_ERROR; + } + draftTransaction.status = SEND_STATUSES.INVALID; + break; + } case isSwapAndSend && !bestQuote: slice.caseReducers.addHistoryEntry(state, { payload: `No swap and send quote available`, From 66bd50d44b95f49f8c947cea5fb536538000e325 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 11:57:20 -0400 Subject: [PATCH 10/24] fix: fix typo --- ui/ducks/send/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index c4e8841b369a..21b02eef1565 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -1797,7 +1797,7 @@ const slice = createSlice({ !isBalanceSufficient({ amount: draftTransaction.sendAsset.type === AssetType.native - ? draftTransaction.sendAsset.balance + ? draftTransaction.amount.value : undefined, balance: state.selectedAccount.balance, gasTotal: calcGasTotal( From bd21e26843e155dc6a79b3d4a475054251fa2221 Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 13:40:36 -0400 Subject: [PATCH 11/24] fix: show end of mutatable input fixes 24505 --- ui/components/ui/unit-input/index.scss | 4 ++++ ui/components/ui/unit-input/unit-input.component.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ui/components/ui/unit-input/index.scss b/ui/components/ui/unit-input/index.scss index 4eec35699412..b95e349757f6 100644 --- a/ui/components/ui/unit-input/index.scss +++ b/ui/components/ui/unit-input/index.scss @@ -48,6 +48,10 @@ text-overflow: ellipsis; height: 16px; outline: none; + + &:not(:focus):not(:disabled) { + text-overflow: clip; + } } &__input-container { diff --git a/ui/components/ui/unit-input/unit-input.component.js b/ui/components/ui/unit-input/unit-input.component.js index e916cf5e2e3f..167567d7f6a7 100644 --- a/ui/components/ui/unit-input/unit-input.component.js +++ b/ui/components/ui/unit-input/unit-input.component.js @@ -86,6 +86,10 @@ export default class UnitInput extends PureComponent { }; handleInputBlur = ({ target: { value } }) => { + setTimeout(() => { + this.unitInput.scrollLeft = this.unitInput.scrollWidth; + }, 0); + if (value === '') { this.setState({ ...this.state, From b932cbefcc0db27967125603efee7b754047a12f Mon Sep 17 00:00:00 2001 From: Bilal Zahory Date: Fri, 7 Jun 2024 14:23:37 -0400 Subject: [PATCH 12/24] chore: update search placeholder copies --- app/_locales/en/messages.json | 4 ++-- .../asset-picker-modal/asset-picker-modal.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3404345958bf..13a7091a03bc 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4350,8 +4350,8 @@ "searchAccounts": { "message": "Search accounts" }, - "searchTokenOrNFT": { - "message": "Search token or NFT" + "searchNfts": { + "message": "Search NFTs" }, "searchTokens": { "message": "Search tokens" diff --git a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx index 377f7af3ca47..6069482dabdd 100644 --- a/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx +++ b/ui/components/multichain/asset-picker-amount/asset-picker-modal/asset-picker-modal.tsx @@ -257,11 +257,11 @@ export function AssetPickerModal({ ]); const Search = useCallback( - () => ( + ({ isNFTSearch = false }: { isNFTSearch?: boolean }) => ( setSearchQuery(e.target.value)} error={false} @@ -363,7 +363,7 @@ export function AssetPickerModal({ > {hasAnyNfts ? ( - + Date: Fri, 7 Jun 2024 14:32:40 -0400 Subject: [PATCH 13/24] test: update snapshot --- .../multichain/pages/send/__snapshots__/send.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap index d20b3201493b..05839490b466 100644 --- a/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap +++ b/ui/components/multichain/pages/send/__snapshots__/send.test.js.snap @@ -540,7 +540,7 @@ exports[`SendPage render and initialization should render correctly even when a