diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b20c8eba5..43d5a4ccd 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -410,15 +410,15 @@ "description": "$1 represents the cypto symbol to be purchased" }, "buyCryptoWithMoonPayDescription": { - "message": "MoonPay supports popular payment methods, including Visa, Mastercard, Apple / Google / Samsung Pay, and bank transfers in 146+ countries. Tokens deposit into your MetaMask account." + "message": "MoonPay supports popular payment methods, including Visa, Mastercard, Apple / Google / Samsung Pay, and bank transfers in 145+ countries. Tokens deposit into your MetaMask account." }, "buyCryptoWithTransak": { "message": "Buy $1 with Transak", "description": "$1 represents the cypto symbol to be purchased" }, "buyCryptoWithTransakDescription": { - "message": "Transak supports debit card and bank transfers (depending on location) in 59+ countries. $1 deposits into your MetaMask account.", - "description": "$1 represents the cypto symbol to be purchased" + "message": "Transak supports credit & debit cards, Apple Pay, MobiKwik, and bank transfers (depending on location) in 100+ countries. $1 deposits directly into your MetaMask account.", + "description": "$1 represents the crypto symbol to be purchased" }, "buyEth": { "message": "Buy ETH" @@ -2857,7 +2857,7 @@ "message": "Slow" }, "smartTransaction": { - "message": "Smart transaction" + "message": "Smart Transaction" }, "snapAccess": { "message": "$1 snap has access to:", @@ -3047,6 +3047,12 @@ "stxDescription": { "message": "MetaMask Swaps just got a whole lot smarter! Enabling Smart Transactions will allow MetaMask to programmatically optimize your Swap to help:" }, + "stxErrorNotEnoughFunds": { + "message": "Not enough funds for a smart transaction." + }, + "stxErrorUnavailable": { + "message": "Smart Transactions are temporarily unavailable." + }, "stxFailure": { "message": "Swap failed" }, @@ -3054,8 +3060,11 @@ "message": "Sudden market changes can cause failures. If the problem persists, please reach out to $1.", "description": "This message is shown to a user if their swap fails. The $1 will be replaced by support.metamask.io" }, - "stxFallbackToNormal": { - "message": "You can still swap using the normal method or wait for cheaper gas fees and less failures with smart transactions." + "stxFallbackPendingTx": { + "message": "Smart Transactions are temporarily unavailable because you have a pending transaction." + }, + "stxFallbackUnavailable": { + "message": "You can still swap your tokens even while Smart Transactions are unavailable." }, "stxPendingFinalizing": { "message": "Finalizing..." @@ -3082,8 +3091,11 @@ "stxTryRegular": { "message": "Try a regular swap." }, + "stxTryingToCancel": { + "message": "Trying to cancel your transaction..." + }, "stxUnavailable": { - "message": "Smart transactions temporarily unavailable" + "message": "Smart Transactions are disabled" }, "stxUnknown": { "message": "Status unknown" diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index 7c854a98d..7d1dd91a5 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -33,6 +33,7 @@ import { fetchSmartTransactionFees, estimateSmartTransactionsGas, cancelSmartTransaction, + getTransactions, } from '../../store/actions'; import { AWAITING_SIGNATURES_ROUTE, @@ -81,6 +82,7 @@ import { } from '../../../shared/constants/swaps'; import { TRANSACTION_TYPES, + TRANSACTION_STATUSES, SMART_TRANSACTION_STATUSES, } from '../../../shared/constants/transaction'; import { getGasFeeEstimates } from '../metamask/metamask'; @@ -199,9 +201,9 @@ const slice = createSlice({ state.customGas.fallBackPrice = action.payload; }, setCurrentSmartTransactionsError: (state, action) => { - const errorType = stxErrorTypes.includes(action.payload) + const errorType = Object.values(stxErrorTypes).includes(action.payload) ? action.payload - : stxErrorTypes[0]; + : stxErrorTypes.UNAVAILABLE; state.currentSmartTransactionsError = errorType; }, dismissCurrentSmartTransactionsErrorMessage: (state) => { @@ -554,12 +556,24 @@ export const fetchSwapsLivenessAndFeatureFlags = () => { let swapsLivenessForNetwork = { swapsFeatureIsLive: false, }; - const chainId = getCurrentChainId(getState()); + const state = getState(); + const chainId = getCurrentChainId(state); try { const swapsFeatureFlags = await fetchSwapsFeatureFlags(); await dispatch(setSwapsFeatureFlags(swapsFeatureFlags)); if (ALLOWED_SMART_TRANSACTIONS_CHAIN_IDS.includes(chainId)) { await dispatch(fetchSmartTransactionsLiveness()); + const pendingTransactions = await getTransactions({ + searchCriteria: { + status: TRANSACTION_STATUSES.PENDING, + from: state.metamask?.selectedAddress, + }, + }); + if (pendingTransactions?.length > 0) { + dispatch( + setCurrentSmartTransactionsError(stxErrorTypes.REGULAR_TX_PENDING), + ); + } } swapsLivenessForNetwork = getSwapsLivenessForNetwork( swapsFeatureFlags, @@ -820,6 +834,7 @@ export const signAndSendSwapsSmartTransaction = ({ unsignedTransaction, metaMetricsEvent, history, + additionalTrackingParams, }) => { return async (dispatch, getState) => { dispatch(setSwapsSTXSubmitLoading(true)); @@ -871,6 +886,7 @@ export const signAndSendSwapsSmartTransaction = ({ stx_enabled: smartTransactionsEnabled, current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, + ...additionalTrackingParams, }; metaMetricsEvent({ event: 'STX Swap Started', @@ -966,7 +982,11 @@ export const signAndSendSwapsSmartTransaction = ({ }; }; -export const signAndSendTransactions = (history, metaMetricsEvent) => { +export const signAndSendTransactions = ( + history, + metaMetricsEvent, + additionalTrackingParams, +) => { return async (dispatch, getState) => { const state = getState(); const chainId = getCurrentChainId(state); @@ -1079,6 +1099,9 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { }); const smartTransactionsOptInStatus = getSmartTransactionsOptInStatus(state); const smartTransactionsEnabled = getSmartTransactionsEnabled(state); + const currentSmartTransactionsEnabled = getCurrentSmartTransactionsEnabled( + state, + ); const swapMetaData = { token_from: sourceTokenInfo.symbol, token_from_amount: String(swapTokenValue), @@ -1105,7 +1128,9 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: getHardwareWalletType(state), stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, + ...additionalTrackingParams, }; if (networkAndAccountSupports1559) { swapMetaData.max_fee_per_gas = maxFeePerGas; diff --git a/ui/ducks/swaps/swaps.test.js b/ui/ducks/swaps/swaps.test.js index bf5454918..9808b0392 100644 --- a/ui/ducks/swaps/swaps.test.js +++ b/ui/ducks/swaps/swaps.test.js @@ -15,6 +15,7 @@ jest.mock('../../store/actions.js', () => ({ setSwapsLiveness: jest.fn(), setSwapsFeatureFlags: jest.fn(), fetchSmartTransactionsLiveness: jest.fn(), + getTransactions: jest.fn(), })); const providerState = { diff --git a/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js b/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js index a03db90a0..69db3a8cd 100644 --- a/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js +++ b/ui/pages/swaps/awaiting-signatures/awaiting-signatures.js @@ -11,6 +11,7 @@ import { prepareToLeaveSwaps, getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, + getCurrentSmartTransactionsEnabled, } from '../../../ducks/swaps/swaps'; import { isHardwareWallet, @@ -47,6 +48,9 @@ export default function AwaitingSignatures() { getSmartTransactionsOptInStatus, ); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); + const currentSmartTransactionsEnabled = useSelector( + getCurrentSmartTransactionsEnabled, + ); const needsTwoConfirmations = Boolean(approveTxParams); const awaitingSignaturesEvent = useNewMetricEvent({ @@ -62,6 +66,7 @@ export default function AwaitingSignatures() { is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, }, category: 'swaps', diff --git a/ui/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/pages/swaps/awaiting-swap/awaiting-swap.js index c7fff3358..2ace4ba5f 100644 --- a/ui/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/pages/swaps/awaiting-swap/awaiting-swap.js @@ -31,6 +31,7 @@ import { prepareToLeaveSwaps, getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, + getCurrentSmartTransactionsEnabled, getFromTokenInputValue, getMaxSlippage, setSwapsFromToken, @@ -113,6 +114,9 @@ export default function AwaitingSwap({ getSmartTransactionsOptInStatus, ); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); + const currentSmartTransactionsEnabled = useSelector( + getCurrentSmartTransactionsEnabled, + ); const sensitiveProperties = { token_from: sourceTokenInfo?.symbol, token_from_amount: fetchParams?.value, @@ -124,6 +128,7 @@ export default function AwaitingSwap({ is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, }; const quotesExpiredEvent = useNewMetricEvent({ diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index 6641255af..b9b9c794c 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -177,11 +177,11 @@ export default function BuildQuote({ const onCloseSmartTransactionsOptInPopover = (e) => { e?.preventDefault(); - setSmartTransactionsOptInStatus(false); + setSmartTransactionsOptInStatus(false, smartTransactionsOptInStatus); }; const onEnableSmartTransactionsClick = () => - setSmartTransactionsOptInStatus(true); + setSmartTransactionsOptInStatus(true, smartTransactionsOptInStatus); const fetchParamsFromToken = isSwapsDefaultTokenSymbol( sourceTokenInfo?.symbol, @@ -338,9 +338,7 @@ export default function BuildQuote({ null, // no holderAddress { blockExplorerUrl: - rpcPrefs.blockExplorerUrl ?? - SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ?? - null, + SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ?? null, }, ); diff --git a/ui/pages/swaps/build-quote/index.scss b/ui/pages/swaps/build-quote/index.scss index b38e83259..e062d492d 100644 --- a/ui/pages/swaps/build-quote/index.scss +++ b/ui/pages/swaps/build-quote/index.scss @@ -129,6 +129,13 @@ } } } + + &__input { + div { + border: 1px solid var(--color-border-default); + border-left: 0; + } + } } &__open-to-dropdown { diff --git a/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js b/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js index c4759b578..b76d86d80 100644 --- a/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js +++ b/ui/pages/swaps/dropdown-search-list/dropdown-search-list.js @@ -27,6 +27,7 @@ import { getURLHostName } from '../../../helpers/utils/util'; import { getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, + getCurrentSmartTransactionsEnabled, } from '../../../ducks/swaps/swaps'; export default function DropdownSearchList({ @@ -63,6 +64,9 @@ export default function DropdownSearchList({ getSmartTransactionsOptInStatus, ); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); + const currentSmartTransactionsEnabled = useSelector( + getCurrentSmartTransactionsEnabled, + ); const tokenImportedEvent = useNewMetricEvent({ event: 'Token Imported', @@ -73,6 +77,7 @@ export default function DropdownSearchList({ is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, }, category: 'swaps', diff --git a/ui/pages/swaps/index.js b/ui/pages/swaps/index.js index 06e9587a3..fd49f1c35 100644 --- a/ui/pages/swaps/index.js +++ b/ui/pages/swaps/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useContext } from 'react'; +import React, { useEffect, useRef, useContext, useState } from 'react'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { Switch, @@ -37,6 +37,7 @@ import { getPendingSmartTransactions, getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, + getCurrentSmartTransactionsEnabled, getCurrentSmartTransactionsError, dismissCurrentSmartTransactionsErrorMessage, getCurrentSmartTransactionsErrorMessageDismissed, @@ -84,6 +85,7 @@ import { fetchTopAssets, getSwapsTokensReceivedFromTxMeta, fetchAggregatorMetadata, + stxErrorTypes, } from './swaps.util'; import AwaitingSignatures from './awaiting-signatures'; import SmartTransactionStatus from './smart-transaction-status'; @@ -106,6 +108,7 @@ export default function Swap() { pathname === SMART_TRANSACTION_STATUS_ROUTE; const isViewQuoteRoute = pathname === VIEW_QUOTE_ROUTE; + const [currentStxErrorTracked, setCurrentStxErrorTracked] = useState(false); const fetchParams = useSelector(getFetchParams, isEqual); const { destinationTokenInfo = {} } = fetchParams?.metaData || {}; @@ -134,6 +137,9 @@ export default function Swap() { getSmartTransactionsOptInStatus, ); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); + const currentSmartTransactionsEnabled = useSelector( + getCurrentSmartTransactionsEnabled, + ); const currentSmartTransactionsError = useSelector( getCurrentSmartTransactionsError, ); @@ -244,6 +250,7 @@ export default function Swap() { is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, }, }); @@ -302,22 +309,26 @@ export default function Swap() { is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, stx_error: currentSmartTransactionsError, }, }); useEffect(() => { - if (currentSmartTransactionsError) { + if (currentSmartTransactionsError && !currentStxErrorTracked) { + setCurrentStxErrorTracked(true); errorStxEvent(); } - }, [errorStxEvent, currentSmartTransactionsError]); + }, [errorStxEvent, currentSmartTransactionsError, currentStxErrorTracked]); if (!isSwapsChain) { return ; } const isStxNotEnoughFundsError = - currentSmartTransactionsError === 'not_enough_funds'; + currentSmartTransactionsError === stxErrorTypes.NOT_ENOUGH_FUNDS; + const isStxRegularTxPendingError = + currentSmartTransactionsError === stxErrorTypes.REGULAR_TX_PENDING; return (
@@ -371,10 +382,20 @@ export default function Swap() {
) : (
-
+
) } @@ -383,16 +404,6 @@ export default function Swap() { ? 'swaps__error-message' : 'actionable-message--left-aligned actionable-message--warning swaps__error-message' } - primaryAction={ - isStxNotEnoughFundsError - ? null - : { - label: t('dismiss'), - onClick: () => - dispatch(dismissCurrentSmartTransactionsErrorMessage()), - } - } - withRightButton /> )} diff --git a/ui/pages/swaps/index.scss b/ui/pages/swaps/index.scss index ef601851c..ed0c06a33 100644 --- a/ui/pages/swaps/index.scss +++ b/ui/pages/swaps/index.scss @@ -120,4 +120,28 @@ padding-left: 24px; flex: 1; } + + &__notification-close-button { + background-color: transparent; + position: absolute; + right: 0; + top: 2px; + + &::after { + position: absolute; + content: '\00D7'; + font-size: 29px; + font-weight: 200; + color: var(--color-text-default); + background-color: transparent; + top: 0; + right: 12px; + cursor: pointer; + } + } + + &__notification-title { + font-weight: bold; + margin-right: 14px; + } } diff --git a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js index 32394b934..23456af32 100644 --- a/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js +++ b/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js @@ -11,6 +11,7 @@ import { getQuotesFetchStartTime, getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, + getCurrentSmartTransactionsEnabled, } from '../../../ducks/swaps/swaps'; import { isHardwareWallet, @@ -41,6 +42,9 @@ export default function LoadingSwapsQuotes({ getSmartTransactionsOptInStatus, ); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); + const currentSmartTransactionsEnabled = useSelector( + getCurrentSmartTransactionsEnabled, + ); const quotesRequestCancelledEventConfig = { event: 'Quotes Request Cancelled', category: 'swaps', @@ -55,6 +59,7 @@ export default function LoadingSwapsQuotes({ is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, stx_enabled: smartTransactionsEnabled, + current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, }, }; diff --git a/ui/pages/swaps/slippage-buttons/slippage-buttons.js b/ui/pages/swaps/slippage-buttons/slippage-buttons.js index dbd39a1d8..597709bd5 100644 --- a/ui/pages/swaps/slippage-buttons/slippage-buttons.js +++ b/ui/pages/swaps/slippage-buttons/slippage-buttons.js @@ -14,7 +14,7 @@ import { ALIGN_ITEMS, DISPLAY, } from '../../../helpers/constants/design-system'; -import { smartTransactionsErrorMessages } from '../swaps.util'; +import { getTranslatedStxErrorMessage } from '../swaps.util'; export default function SlippageButtons({ onSelect, @@ -208,8 +208,9 @@ export default function SlippageButtons({ {currentSmartTransactionsError ? ( ) : ( @@ -219,7 +220,7 @@ export default function SlippageButtons({ { - setSmartTransactionsOptInStatus(!value); + setSmartTransactionsOptInStatus(!value, value); }} offLabel={t('off')} onLabel={t('on')} diff --git a/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js b/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js index f14fdbb37..232c00639 100644 --- a/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js +++ b/ui/pages/swaps/slippage-buttons/slippage-buttons.test.js @@ -45,6 +45,6 @@ describe('SlippageButtons', () => { expect( document.querySelector('.slippage-buttons__button-group'), ).toMatchSnapshot(); - expect(getByText('Smart transaction')).toBeInTheDocument(); + expect(getByText('Smart Transaction')).toBeInTheDocument(); }); }); diff --git a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js index 0f12bf3ba..f76d616f5 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js @@ -184,6 +184,8 @@ export default function SmartTransactionStatus() { headerText = t('stxPendingFinalizing'); } else if (timeLeftForPendingStxInSec < 150) { headerText = t('stxPendingPrivatelySubmitting'); + } else if (cancelSwapLinkClicked) { + headerText = t('stxTryingToCancel'); } } if (smartTransactionStatus === SMART_TRANSACTION_STATUSES.SUCCESS) { @@ -192,7 +194,11 @@ export default function SmartTransactionStatus() { description = t('stxSuccessDescription', [destinationTokenInfo.symbol]); } icon = ; - } else if (smartTransactionStatus === 'cancelled_user_cancelled') { + } else if ( + smartTransactionStatus === 'cancelled_user_cancelled' || + latestSmartTransaction?.statusMetadata?.minedTx === + SMART_TRANSACTION_STATUSES.CANCELLED + ) { headerText = t('stxUserCancelled'); description = t('stxUserCancelledDescription'); icon = ; diff --git a/ui/pages/swaps/swaps.util.js b/ui/pages/swaps/swaps.util.js index 14b7e4b77..9c4f6b52d 100644 --- a/ui/pages/swaps/swaps.util.js +++ b/ui/pages/swaps/swaps.util.js @@ -55,6 +55,7 @@ const TOKEN_TRANSFER_LOG_TOPIC_HASH = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; const CACHE_REFRESH_FIVE_MINUTES = 300000; +const USD_CURRENCY_CODE = 'usd'; const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID }; @@ -514,12 +515,25 @@ export const getFeeForSmartTransaction = ({ conversionRate, numberOfDecimals: 2, }); + let feeInUsd; + if (currentCurrency === USD_CURRENCY_CODE) { + feeInUsd = rawNetworkFees; + } else { + feeInUsd = getValueFromWeiHex({ + value: feeInWeiHex, + toCurrency: USD_CURRENCY_CODE, + conversionRate, + numberOfDecimals: 2, + }); + } const formattedNetworkFee = formatCurrency(rawNetworkFees, currentCurrency); const chainCurrencySymbolToUse = nativeCurrencySymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol; return { + feeInUsd, feeInFiat: formattedNetworkFee, feeInEth: `${ethFee} ${chainCurrencySymbolToUse}`, + rawEthFee: ethFee, }; }; @@ -564,11 +578,24 @@ export function getRenderableNetworkFeesForQuote({ }); const formattedNetworkFee = formatCurrency(rawNetworkFees, currentCurrency); + let feeInUsd; + if (currentCurrency === USD_CURRENCY_CODE) { + feeInUsd = rawNetworkFees; + } else { + feeInUsd = getValueFromWeiHex({ + value: totalWeiCost, + toCurrency: USD_CURRENCY_CODE, + conversionRate, + numberOfDecimals: 2, + }); + } + const chainCurrencySymbolToUse = nativeCurrencySymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol; return { rawNetworkFees, + feeInUsd, rawEthFee: ethFee, feeInFiat: formattedNetworkFee, feeInEth: `${ethFee} ${chainCurrencySymbolToUse}`, @@ -903,18 +930,22 @@ export const showRemainingTimeInMinAndSec = (remainingTimeInSec) => { return `${minutes}:${seconds.toString().padStart(2, '0')}`; }; -export const stxErrorTypes = ['unavailable', 'not_enough_funds']; - -const smartTransactionsErrorMap = { - unavailable: 'Smart Transactions are temporarily unavailable.', - not_enough_funds: 'Not enough funds for a smart transaction.', +export const stxErrorTypes = { + UNAVAILABLE: 'unavailable', + NOT_ENOUGH_FUNDS: 'not_enough_funds', + REGULAR_TX_PENDING: 'regular_tx_pending', }; -export const smartTransactionsErrorMessages = (errorType) => { - return ( - smartTransactionsErrorMap[errorType] || - smartTransactionsErrorMap.unavailable - ); +export const getTranslatedStxErrorMessage = (errorType, t) => { + switch (errorType) { + case stxErrorTypes.UNAVAILABLE: + case stxErrorTypes.REGULAR_TX_PENDING: + return t('stxErrorUnavailable'); + case stxErrorTypes.NOT_ENOUGH_FUNDS: + return t('stxErrorNotEnoughFunds'); + default: + return t('stxErrorUnavailable'); + } }; export const parseSmartTransactionsError = (errorMessage) => { diff --git a/ui/pages/swaps/view-quote/view-quote.js b/ui/pages/swaps/view-quote/view-quote.js index 8785428ba..396dbc88e 100644 --- a/ui/pages/swaps/view-quote/view-quote.js +++ b/ui/pages/swaps/view-quote/view-quote.js @@ -205,40 +205,6 @@ export default function ViewQuote() { const swapsRefreshRates = useSelector(getSwapsRefreshStates); const unsignedTransaction = usedQuote.trade; - useEffect(() => { - if (currentSmartTransactionsEnabled && smartTransactionsOptInStatus) { - const unsignedTx = { - from: unsignedTransaction.from, - to: unsignedTransaction.to, - value: unsignedTransaction.value, - data: unsignedTransaction.data, - gas: unsignedTransaction.gas, - chainId, - }; - intervalId = setInterval(() => { - dispatch( - estimateSwapsSmartTransactionsGas(unsignedTx, approveTxParams), - ); - }, swapsRefreshRates.stxGetTransactionsRefreshTime); - dispatch(estimateSwapsSmartTransactionsGas(unsignedTx, approveTxParams)); - } else if (intervalId) { - clearInterval(intervalId); - } - return () => clearInterval(intervalId); - // eslint-disable-next-line - }, [ - dispatch, - currentSmartTransactionsEnabled, - smartTransactionsOptInStatus, - unsignedTransaction.data, - unsignedTransaction.from, - unsignedTransaction.value, - unsignedTransaction.gas, - unsignedTransaction.to, - chainId, - swapsRefreshRates.stxGetTransactionsRefreshTime, - ]); - let gasFeeInputs; if (networkAndAccountSupports1559) { // For Swaps we want to get 'high' estimations by default. @@ -252,6 +218,17 @@ export default function ViewQuote() { const fetchParamsSourceToken = fetchParams?.sourceToken; + const additionalTrackingParams = { + reg_tx_fee_in_usd: undefined, + reg_tx_fee_in_eth: undefined, + reg_tx_max_fee_in_usd: undefined, + reg_tx_max_fee_in_eth: undefined, + stx_fee_in_usd: undefined, + stx_fee_in_eth: undefined, + stx_max_fee_in_usd: undefined, + stx_max_fee_in_eth: undefined, + }; + const usedGasLimit = usedQuote?.gasEstimateWithRefund || `0x${decimalToHex(usedQuote?.averageGas || 0)}`; @@ -266,7 +243,7 @@ export default function ViewQuote() { const nonCustomMaxGasLimit = usedQuote?.gasEstimate ? usedGasLimitWithMultiplier : `0x${decimalToHex(usedQuote?.maxGas || 0)}`; - let maxGasLimit = customMaxGas || nonCustomMaxGasLimit; + const maxGasLimit = customMaxGas || nonCustomMaxGasLimit; let maxFeePerGas; let maxPriorityFeePerGas; @@ -289,17 +266,6 @@ export default function ViewQuote() { ); } - // Smart Transactions gas fees. - if ( - currentSmartTransactionsEnabled && - smartTransactionsOptInStatus && - smartTransactionEstimatedGas?.txData - ) { - maxGasLimit = `0x${decimalToHex( - smartTransactionEstimatedGas?.txData.gasLimit || 0, - )}`; - } - const gasTotalInWeiHex = calcGasTotal(maxGasLimit, maxFeePerGas || gasPrice); const { tokensWithBalances } = useTokenTracker(swapsTokens, true); @@ -374,7 +340,12 @@ export default function ViewQuote() { sourceTokenIconUrl, } = renderableDataForUsedQuote; - let { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote({ + let { + feeInFiat, + feeInEth, + rawEthFee, + feeInUsd, + } = getRenderableNetworkFeesForQuote({ tradeGas: usedGasLimit, approveGas, gasPrice: networkAndAccountSupports1559 @@ -388,6 +359,8 @@ export default function ViewQuote() { chainId, nativeCurrencySymbol, }); + additionalTrackingParams.reg_tx_fee_in_usd = Number(feeInUsd); + additionalTrackingParams.reg_tx_fee_in_eth = Number(rawEthFee); const renderableMaxFees = getRenderableNetworkFeesForQuote({ tradeGas: maxGasLimit, @@ -401,8 +374,15 @@ export default function ViewQuote() { chainId, nativeCurrencySymbol, }); - let { feeInFiat: maxFeeInFiat, feeInEth: maxFeeInEth } = renderableMaxFees; + let { + feeInFiat: maxFeeInFiat, + feeInEth: maxFeeInEth, + rawEthFee: maxRawEthFee, + feeInUsd: maxFeeInUsd, + } = renderableMaxFees; const { nonGasFee } = renderableMaxFees; + additionalTrackingParams.reg_tx_max_fee_in_usd = Number(maxFeeInUsd); + additionalTrackingParams.reg_tx_max_fee_in_eth = Number(maxRawEthFee); if ( currentSmartTransactionsEnabled && @@ -413,16 +393,22 @@ export default function ViewQuote() { smartTransactionEstimatedGas.txData.feeEstimate + (smartTransactionEstimatedGas.approvalTxData?.feeEstimate || 0); const stxMaxFeeInWeiDec = stxEstimatedFeeInWeiDec * 2; - ({ feeInFiat, feeInEth } = getFeeForSmartTransaction({ + ({ feeInFiat, feeInEth, rawEthFee, feeInUsd } = getFeeForSmartTransaction({ chainId, currentCurrency, conversionRate, nativeCurrencySymbol, feeInWeiDec: stxEstimatedFeeInWeiDec, })); + additionalTrackingParams.stx_fee_in_usd = Number(feeInUsd); + additionalTrackingParams.stx_fee_in_eth = Number(rawEthFee); + additionalTrackingParams.estimated_gas = + smartTransactionEstimatedGas.txData.gasLimit; ({ feeInFiat: maxFeeInFiat, feeInEth: maxFeeInEth, + rawEthFee: maxRawEthFee, + feeInUsd: maxFeeInUsd, } = getFeeForSmartTransaction({ chainId, currentCurrency, @@ -430,6 +416,8 @@ export default function ViewQuote() { nativeCurrencySymbol, feeInWeiDec: stxMaxFeeInWeiDec, })); + additionalTrackingParams.stx_max_fee_in_usd = Number(maxFeeInUsd); + additionalTrackingParams.stx_max_fee_in_eth = Number(maxRawEthFee); } const tokenCost = new BigNumber(usedQuote.sourceAmount); @@ -520,7 +508,7 @@ export default function ViewQuote() { available_quotes: numberOfQuotes, is_hardware_wallet: hardwareWalletUsed, hardware_wallet_type: hardwareWalletType, - stx_enabled: currentSmartTransactionsEnabled, + stx_enabled: smartTransactionsEnabled, current_stx_enabled: currentSmartTransactionsEnabled, stx_user_opt_in: smartTransactionsOptInStatus, }; @@ -782,6 +770,55 @@ export default function ViewQuote() { const isShowingWarning = showInsufficientWarning || shouldShowPriceDifferenceWarning; + const isSwapButtonDisabled = + submitClicked || + balanceError || + tokenBalanceUnavailable || + disableSubmissionDueToPriceWarning || + (networkAndAccountSupports1559 && baseAndPriorityFeePerGas === undefined) || + (!networkAndAccountSupports1559 && + (gasPrice === null || gasPrice === undefined)) || + (currentSmartTransactionsEnabled && currentSmartTransactionsError); + + useEffect(() => { + if ( + currentSmartTransactionsEnabled && + smartTransactionsOptInStatus && + !isSwapButtonDisabled + ) { + const unsignedTx = { + from: unsignedTransaction.from, + to: unsignedTransaction.to, + value: unsignedTransaction.value, + data: unsignedTransaction.data, + gas: unsignedTransaction.gas, + chainId, + }; + intervalId = setInterval(() => { + dispatch( + estimateSwapsSmartTransactionsGas(unsignedTx, approveTxParams), + ); + }, swapsRefreshRates.stxGetTransactionsRefreshTime); + dispatch(estimateSwapsSmartTransactionsGas(unsignedTx, approveTxParams)); + } else if (intervalId) { + clearInterval(intervalId); + } + return () => clearInterval(intervalId); + // eslint-disable-next-line + }, [ + dispatch, + currentSmartTransactionsEnabled, + smartTransactionsOptInStatus, + unsignedTransaction.data, + unsignedTransaction.from, + unsignedTransaction.value, + unsignedTransaction.gas, + unsignedTransaction.to, + chainId, + swapsRefreshRates.stxGetTransactionsRefreshTime, + isSwapButtonDisabled, + ]); + const onCloseEditGasPopover = () => { setShowEditGasPopover(false); }; @@ -967,10 +1004,17 @@ export default function ViewQuote() { unsignedTransaction, metaMetricsEvent, history, + additionalTrackingParams, }), ); } else { - dispatch(signAndSendTransactions(history, metaMetricsEvent)); + dispatch( + signAndSendTransactions( + history, + metaMetricsEvent, + additionalTrackingParams, + ), + ); } } else if (destinationToken.symbol === defaultSwapsToken.symbol) { history.push(DEFAULT_ROUTE); @@ -986,17 +1030,7 @@ export default function ViewQuote() { : t('swap') } hideCancel - disabled={ - submitClicked || - balanceError || - tokenBalanceUnavailable || - disableSubmissionDueToPriceWarning || - (networkAndAccountSupports1559 && - baseAndPriorityFeePerGas === undefined) || - (!networkAndAccountSupports1559 && - (gasPrice === null || gasPrice === undefined)) || - (currentSmartTransactionsEnabled && currentSmartTransactionsError) - } + disabled={isSwapButtonDisabled} className={isShowingWarning && 'view-quote__thin-swaps-footer'} showTopBorder /> diff --git a/ui/store/actions.js b/ui/store/actions.js index f6ec05595..a0f8b0e26 100644 --- a/ui/store/actions.js +++ b/ui/store/actions.js @@ -3230,7 +3230,10 @@ export async function setWeb3ShimUsageAlertDismissed(origin) { } // Smart Transactions Controller -export async function setSmartTransactionsOptInStatus(optInState) { +export async function setSmartTransactionsOptInStatus( + optInState, + prevOptInState, +) { trackMetaMetricsEvent({ event: 'STX OptIn', category: 'swaps', @@ -3238,6 +3241,7 @@ export async function setSmartTransactionsOptInStatus(optInState) { stx_enabled: true, current_stx_enabled: true, stx_user_opt_in: optInState, + stx_prev_user_opt_in: prevOptInState, }, }); await promisifiedBackground.setSmartTransactionsOptInStatus(optInState);