From 2bd7127433f31738e81f5f99d9eae90aa2124872 Mon Sep 17 00:00:00 2001 From: Daniel <80175477+dan437@users.noreply.github.com> Date: Mon, 9 May 2022 18:48:14 +0200 Subject: [PATCH] Swaps / STX improvements (#14622) --- app/scripts/controllers/swaps.js | 46 +++++++++++-------- app/scripts/controllers/swaps.test.js | 9 +++- app/scripts/controllers/transactions/index.js | 35 ++++++++++++++ shared/constants/smartTransactions.js | 5 ++ ui/ducks/swaps/swaps.js | 8 ++-- .../smart-transaction-status.js | 16 +++---- ui/pages/swaps/swaps.util.js | 6 ++- ui/pages/swaps/view-quote/view-quote.js | 17 +++++-- ui/selectors/selectors.js | 2 +- 9 files changed, 104 insertions(+), 40 deletions(-) create mode 100644 shared/constants/smartTransactions.js diff --git a/app/scripts/controllers/swaps.js b/app/scripts/controllers/swaps.js index e4df14746..f19da6ff1 100644 --- a/app/scripts/controllers/swaps.js +++ b/app/scripts/controllers/swaps.js @@ -19,6 +19,11 @@ import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, } from '../../../shared/constants/swaps'; import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas'; +import { + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + FALLBACK_SMART_TRANSACTIONS_DEADLINE, + FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, +} from '../../../shared/constants/smartTransactions'; import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'; @@ -41,8 +46,6 @@ const POLL_COUNT_LIMIT = 3; // If for any reason the MetaSwap API fails to provide a refresh time, // provide a reasonable fallback to avoid further errors const FALLBACK_QUOTE_REFRESH_TIME = MINUTE; -const FALLBACK_SMART_TRANSACTION_REFRESH_TIME = SECOND * 10; -const FALLBACK_SMART_TRANSACTIONS_DEADLINE = 180; function calculateGasEstimateWithRefund( maxGas = MAX_GAS_LIMIT, @@ -86,8 +89,9 @@ const initialState = { saveFetchedQuotes: false, swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME, swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME, - swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTION_REFRESH_TIME, - swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTION_REFRESH_TIME, + swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, swapsFeatureFlags: {}, }, }; @@ -129,13 +133,13 @@ export default class SwapsController { }); } - async fetchSwapsRefreshRates(chainId) { + async fetchSwapsNetworkConfig(chainId) { const response = await fetchWithCache( getBaseApi('network', chainId), { method: 'GET' }, { cacheRefreshTime: 600000 }, ); - const { refreshRates } = response || {}; + const { refreshRates, parameters = {} } = response || {}; if ( !refreshRates || typeof refreshRates.quotes !== 'number' || @@ -152,35 +156,39 @@ export default class SwapsController { stxGetTransactions: refreshRates.stxGetTransactions * 1000, stxBatchStatus: refreshRates.stxBatchStatus * 1000, stxStatusDeadline: refreshRates.stxStatusDeadline, + stxMaxFeeMultiplier: parameters.stxMaxFeeMultiplier, }; } - // Sets the refresh rate for quote updates from the MetaSwap API - async _setSwapsRefreshRates() { + // Sets the network config from the MetaSwap API. + async _setSwapsNetworkConfig() { const chainId = this._getCurrentChainId(); - let swapsRefreshRates; + let swapsNetworkConfig; try { - swapsRefreshRates = await this.fetchSwapsRefreshRates(chainId); + swapsNetworkConfig = await this.fetchSwapsNetworkConfig(chainId); } catch (e) { - console.error('Request for swaps quote refresh time failed: ', e); + console.error('Request for Swaps network config failed: ', e); } const { swapsState: latestSwapsState } = this.store.getState(); this.store.updateState({ swapsState: { ...latestSwapsState, swapsQuoteRefreshTime: - swapsRefreshRates?.quotes || FALLBACK_QUOTE_REFRESH_TIME, + swapsNetworkConfig?.quotes || FALLBACK_QUOTE_REFRESH_TIME, swapsQuotePrefetchingRefreshTime: - swapsRefreshRates?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME, + swapsNetworkConfig?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME, swapsStxGetTransactionsRefreshTime: - swapsRefreshRates?.stxGetTransactions || - FALLBACK_SMART_TRANSACTION_REFRESH_TIME, + swapsNetworkConfig?.stxGetTransactions || + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, swapsStxBatchStatusRefreshTime: - swapsRefreshRates?.stxBatchStatus || - FALLBACK_SMART_TRANSACTION_REFRESH_TIME, + swapsNetworkConfig?.stxBatchStatus || + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, swapsStxStatusDeadline: - swapsRefreshRates?.stxStatusDeadline || + swapsNetworkConfig?.stxStatusDeadline || FALLBACK_SMART_TRANSACTIONS_DEADLINE, + swapsStxMaxFeeMultiplier: + swapsNetworkConfig?.stxMaxFeeMultiplier || + FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, }, }); } @@ -253,7 +261,7 @@ export default class SwapsController { this._fetchTradesInfo(fetchParams, { ...fetchParamsMetaData, }), - this._setSwapsRefreshRates(), + this._setSwapsNetworkConfig(), ]); const { diff --git a/app/scripts/controllers/swaps.test.js b/app/scripts/controllers/swaps.test.js index 3a13bf942..f8f580c4d 100644 --- a/app/scripts/controllers/swaps.test.js +++ b/app/scripts/controllers/swaps.test.js @@ -13,6 +13,10 @@ import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps'; import { createTestProviderTools } from '../../../test/stub/provider'; import { SECOND } from '../../../shared/constants/time'; import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas'; +import { + FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, +} from '../../../shared/constants/smartTransactions'; import SwapsController, { utils } from './swaps'; import { NETWORK_EVENTS } from './network'; @@ -134,8 +138,9 @@ const EMPTY_INIT_STATE = { swapsFeatureFlags: {}, swapsQuoteRefreshTime: 60000, swapsQuotePrefetchingRefreshTime: 60000, - swapsStxBatchStatusRefreshTime: 10000, - swapsStxGetTransactionsRefreshTime: 10000, + swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME, + swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER, swapsUserFeeLevel: '', saveFetchedQuotes: false, }, diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index e5d50375f..4e06c2951 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -18,10 +18,12 @@ import { getChainType, } from '../../lib/util'; import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/helpers/constants/error-keys'; +import { calcGasTotal } from '../../../../ui/pages/send/send.utils'; import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/pages/swaps/swaps.util'; import { hexWEIToDecGWEI, decimalToHex, + hexWEIToDecETH, } from '../../../../ui/helpers/utils/conversions.util'; import { TRANSACTION_STATUSES, @@ -1877,6 +1879,30 @@ export default class TransactionController extends EventEmitter { this.memStore.updateState({ unapprovedTxs, currentNetworkTxList }); } + _calculateTransactionsCost(txMeta, approvalTxMeta) { + let approvalGasCost = '0x0'; + if (approvalTxMeta?.txReceipt) { + approvalGasCost = calcGasTotal( + approvalTxMeta.txReceipt.gasUsed, + approvalTxMeta.txReceipt.effectiveGasPrice, + ); + } + const tradeGasCost = calcGasTotal( + txMeta.txReceipt.gasUsed, + txMeta.txReceipt.effectiveGasPrice, + ); + const tradeAndApprovalGasCost = new BigNumber(tradeGasCost, 16) + .plus(approvalGasCost, 16) + .toString(16); + return { + approvalGasCostInEth: Number(hexWEIToDecETH(approvalGasCost)), + tradeGasCostInEth: Number(hexWEIToDecETH(tradeGasCost)), + tradeAndApprovalGasCostInEth: Number( + hexWEIToDecETH(tradeAndApprovalGasCost), + ), + }; + } + _trackSwapsMetrics(txMeta, approvalTxMeta) { if (this._getParticipateInMetrics() && txMeta.swapMetaData) { if (txMeta.txReceipt.status === '0x0') { @@ -1911,6 +1937,11 @@ export default class TransactionController extends EventEmitter { .round(2)}%` : null; + const transactionsCost = this._calculateTransactionsCost( + txMeta, + approvalTxMeta, + ); + this._trackMetaMetricsEvent({ event: 'Swap Completed', category: EVENT.CATEGORIES.SWAPS, @@ -1919,6 +1950,10 @@ export default class TransactionController extends EventEmitter { token_to_amount_received: tokensReceived, quote_vs_executionRatio: quoteVsExecutionRatio, estimated_vs_used_gasRatio: estimatedVsUsedGasRatio, + approval_gas_cost_in_eth: transactionsCost.approvalGasCostInEth, + trade_gas_cost_in_eth: transactionsCost.tradeGasCostInEth, + trade_and_approval_gas_cost_in_eth: + transactionsCost.tradeAndApprovalGasCostInEth, }, }); } diff --git a/shared/constants/smartTransactions.js b/shared/constants/smartTransactions.js new file mode 100644 index 000000000..a77d993c7 --- /dev/null +++ b/shared/constants/smartTransactions.js @@ -0,0 +1,5 @@ +import { SECOND } from './time'; + +export const FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME = SECOND * 10; +export const FALLBACK_SMART_TRANSACTIONS_DEADLINE = 180; +export const FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER = 2; diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index a7d5e8ad9..fc75375ba 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -445,13 +445,14 @@ export const getSmartTransactionEstimatedGas = (state) => { return state.metamask.smartTransactionsState?.estimatedGas; }; -export const getSwapsRefreshStates = (state) => { +export const getSwapsNetworkConfig = (state) => { const { swapsQuoteRefreshTime, swapsQuotePrefetchingRefreshTime, swapsStxGetTransactionsRefreshTime, swapsStxBatchStatusRefreshTime, swapsStxStatusDeadline, + swapsStxMaxFeeMultiplier, } = state.metamask.swapsState; return { quoteRefreshTime: swapsQuoteRefreshTime, @@ -459,6 +460,7 @@ export const getSwapsRefreshStates = (state) => { stxGetTransactionsRefreshTime: swapsStxGetTransactionsRefreshTime, stxBatchStatusRefreshTime: swapsStxBatchStatusRefreshTime, stxStatusDeadline: swapsStxStatusDeadline, + stxMaxFeeMultiplier: swapsStxMaxFeeMultiplier, }; }; @@ -855,12 +857,12 @@ export const signAndSendSwapsSmartTransaction = ({ const { metaData, value: swapTokenValue, slippage } = fetchParams; const { sourceTokenInfo = {}, destinationTokenInfo = {} } = metaData; const usedQuote = getUsedQuote(state); - const swapsRefreshStates = getSwapsRefreshStates(state); + const swapsNetworkConfig = getSwapsNetworkConfig(state); const chainId = getCurrentChainId(state); dispatch( setSmartTransactionsRefreshInterval( - swapsRefreshStates?.stxBatchStatusRefreshTime, + swapsNetworkConfig?.stxBatchStatusRefreshTime, ), ); 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 23858e728..84c7a0fe8 100644 --- a/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js +++ b/ui/pages/swaps/smart-transaction-status/smart-transaction-status.js @@ -12,7 +12,7 @@ import { getSmartTransactionsOptInStatus, getSmartTransactionsEnabled, getCurrentSmartTransactionsEnabled, - getSwapsRefreshStates, + getSwapsNetworkConfig, cancelSwapsSmartTransaction, } from '../../../ducks/swaps/swaps'; import { @@ -71,7 +71,7 @@ export default function SmartTransactionStatus() { const smartTransactionsOptInStatus = useSelector( getSmartTransactionsOptInStatus, ); - const swapsRefreshRates = useSelector(getSwapsRefreshStates); + const swapsNetworkConfig = useSelector(getSwapsNetworkConfig); const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); const currentSmartTransactionsEnabled = useSelector( getCurrentSmartTransactionsEnabled, @@ -89,7 +89,7 @@ export default function SmartTransactionStatus() { } const [timeLeftForPendingStxInSec, setTimeLeftForPendingStxInSec] = useState( - swapsRefreshRates.stxStatusDeadline, + swapsNetworkConfig.stxStatusDeadline, ); const sensitiveProperties = { @@ -139,13 +139,13 @@ export default function SmartTransactionStatus() { const secondsAfterStxSubmission = Math.round( (Date.now() - latestSmartTransaction.time) / 1000, ); - if (secondsAfterStxSubmission > swapsRefreshRates.stxStatusDeadline) { + if (secondsAfterStxSubmission > swapsNetworkConfig.stxStatusDeadline) { setTimeLeftForPendingStxInSec(0); clearInterval(intervalId); return; } setTimeLeftForPendingStxInSec( - swapsRefreshRates.stxStatusDeadline - secondsAfterStxSubmission, + swapsNetworkConfig.stxStatusDeadline - secondsAfterStxSubmission, ); }; intervalId = setInterval(calculateRemainingTime, 1000); @@ -158,7 +158,7 @@ export default function SmartTransactionStatus() { isSmartTransactionPending, latestSmartTransactionUuid, latestSmartTransaction.time, - swapsRefreshRates.stxStatusDeadline, + swapsNetworkConfig.stxStatusDeadline, ]); useEffect(() => { @@ -358,8 +358,8 @@ export default function SmartTransactionStatus() { className="smart-transaction-status__loading-bar" style={{ width: `${ - (100 / swapsRefreshRates.stxStatusDeadline) * - (swapsRefreshRates.stxStatusDeadline - + (100 / swapsNetworkConfig.stxStatusDeadline) * + (swapsNetworkConfig.stxStatusDeadline - timeLeftForPendingStxInSec) }%`, }} diff --git a/ui/pages/swaps/swaps.util.js b/ui/pages/swaps/swaps.util.js index a34da5a0a..c0993b63f 100644 --- a/ui/pages/swaps/swaps.util.js +++ b/ui/pages/swaps/swaps.util.js @@ -500,6 +500,7 @@ export const getFeeForSmartTransaction = ({ chainId, currentCurrency, conversionRate, + USDConversionRate, nativeCurrencySymbol, feeInWeiDec, }) => { @@ -522,7 +523,7 @@ export const getFeeForSmartTransaction = ({ feeInUsd = getValueFromWeiHex({ value: feeInWeiHex, toCurrency: USD_CURRENCY_CODE, - conversionRate, + conversionRate: USDConversionRate, numberOfDecimals: 2, }); } @@ -543,6 +544,7 @@ export function getRenderableNetworkFeesForQuote({ gasPrice, currentCurrency, conversionRate, + USDConversionRate, tradeValue, sourceSymbol, sourceAmount, @@ -585,7 +587,7 @@ export function getRenderableNetworkFeesForQuote({ feeInUsd = getValueFromWeiHex({ value: totalWeiCost, toCurrency: USD_CURRENCY_CODE, - conversionRate, + conversionRate: USDConversionRate, numberOfDecimals: 2, }); } diff --git a/ui/pages/swaps/view-quote/view-quote.js b/ui/pages/swaps/view-quote/view-quote.js index ccdea22e7..a067e5dea 100644 --- a/ui/pages/swaps/view-quote/view-quote.js +++ b/ui/pages/swaps/view-quote/view-quote.js @@ -43,7 +43,7 @@ import { getReviewSwapClickedTimestamp, getSmartTransactionsOptInStatus, signAndSendSwapsSmartTransaction, - getSwapsRefreshStates, + getSwapsNetworkConfig, getSmartTransactionsEnabled, getCurrentSmartTransactionsError, getCurrentSmartTransactionsErrorMessageDismissed, @@ -62,6 +62,7 @@ import { getHardwareWalletType, checkNetworkAndAccountSupports1559, getEIP1559V2Enabled, + getUSDConversionRate, } from '../../../selectors'; import { getNativeCurrency, getTokens } from '../../../ducks/metamask/metamask'; @@ -171,6 +172,7 @@ export default function ViewQuote() { const memoizedTokenConversionRates = useEqualityCheck(tokenConversionRates); const { balance: ethBalance } = useSelector(getSelectedAccount, shallowEqual); const conversionRate = useSelector(conversionRateSelector); + const USDConversionRate = useSelector(getUSDConversionRate); const currentCurrency = useSelector(getCurrentCurrency); const swapsTokens = useSelector(getTokens, isEqual); const networkAndAccountSupports1559 = useSelector( @@ -209,7 +211,7 @@ export default function ViewQuote() { const smartTransactionEstimatedGas = useSelector( getSmartTransactionEstimatedGas, ); - const swapsRefreshRates = useSelector(getSwapsRefreshStates); + const swapsNetworkConfig = useSelector(getSwapsNetworkConfig); const unsignedTransaction = usedQuote.trade; let gasFeeInputs; @@ -360,6 +362,7 @@ export default function ViewQuote() { : gasPrice, currentCurrency, conversionRate, + USDConversionRate, tradeValue, sourceSymbol: sourceTokenSymbol, sourceAmount: usedQuote.sourceAmount, @@ -375,6 +378,7 @@ export default function ViewQuote() { gasPrice: maxFeePerGas || gasPrice, currentCurrency, conversionRate, + USDConversionRate, tradeValue, sourceSymbol: sourceTokenSymbol, sourceAmount: usedQuote.sourceAmount, @@ -399,11 +403,13 @@ export default function ViewQuote() { const stxEstimatedFeeInWeiDec = smartTransactionEstimatedGas.txData.feeEstimate + (smartTransactionEstimatedGas.approvalTxData?.feeEstimate || 0); - const stxMaxFeeInWeiDec = stxEstimatedFeeInWeiDec * 2; + const stxMaxFeeInWeiDec = + stxEstimatedFeeInWeiDec * swapsNetworkConfig.stxMaxFeeMultiplier; ({ feeInFiat, feeInEth, rawEthFee, feeInUsd } = getFeeForSmartTransaction({ chainId, currentCurrency, conversionRate, + USDConversionRate, nativeCurrencySymbol, feeInWeiDec: stxEstimatedFeeInWeiDec, })); @@ -420,6 +426,7 @@ export default function ViewQuote() { chainId, currentCurrency, conversionRate, + USDConversionRate, nativeCurrencySymbol, feeInWeiDec: stxMaxFeeInWeiDec, })); @@ -835,7 +842,7 @@ export default function ViewQuote() { dispatch( estimateSwapsSmartTransactionsGas(unsignedTx, approveTxParams), ); - }, swapsRefreshRates.stxGetTransactionsRefreshTime); + }, swapsNetworkConfig.stxGetTransactionsRefreshTime); dispatch(estimateSwapsSmartTransactionsGas(unsignedTx, approveTxParams)); } else if (intervalId) { clearInterval(intervalId); @@ -852,7 +859,7 @@ export default function ViewQuote() { unsignedTransaction.gas, unsignedTransaction.to, chainId, - swapsRefreshRates.stxGetTransactionsRefreshTime, + swapsNetworkConfig.stxGetTransactionsRefreshTime, isSwapButtonDisabled, ]); diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 244545d0e..dd91e9cfb 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -671,7 +671,7 @@ export function getSwapsDefaultToken(state) { export function getIsSwapsChain(state) { const chainId = getCurrentChainId(state); const isNotDevelopment = - process.env.METAMASK_ENVIRONMENT !== 'development' || + process.env.METAMASK_ENVIRONMENT !== 'development' && process.env.METAMASK_ENVIRONMENT !== 'testing'; return isNotDevelopment ? ALLOWED_PROD_SWAPS_CHAIN_IDS.includes(chainId)