/* eslint-disable no-mixed-spaces-and-tabs */ import React, { useState, useEffect, useCallback, useRef } from 'react'; import EditGasFee1559 from '../EditGasFee1559'; import { connect } from 'react-redux'; import { CANCEL_RATE, SPEED_UP_RATE, GAS_ESTIMATE_TYPES, } from '@metamask/controllers'; import { hexToBN, fromWei, renderFromWei } from '../../../util/number'; import BigNumber from 'bignumber.js'; import { getTicker, parseTransactionEIP1559 } from '../../../util/transactions'; import AppConstants from '../../../core/AppConstants'; import Engine from '../../../core/Engine'; import { strings } from '../../../../locales/i18n'; /** * View that renders a list of transactions for a specific asset */ interface Props { /** * Map of accounts to information objects including balances */ accounts: any; /** * ETH to current currency conversion rate */ conversionRate: number; /** * Currency code of the currently-active currency */ currentCurrency: string; /** * Object containing token exchange rates in the format address => exchangeRate */ contractExchangeRates: any; /** * Chain Id */ chainId: string; /** * ETH or fiat, depending on user setting */ primaryCurrency: string; /** * Gas fee estimates returned by the gas fee controller */ gasFeeEstimates: any; /** * Estimate type returned by the gas fee controller, can be market-fee, legacy or eth_gasPrice */ gasEstimateType: string; /** * A string that represents the selected address */ selectedAddress: string; /** * A bool indicates whether tx is speed up/cancel */ isCancel: boolean; /** * Current provider ticker */ ticker: string; /** * The max fee and max priorty fee selected tx */ existingGas: any; /** * Native currency set by user */ nativeCurrency: string; /** * Gas object used to get suggestedGasLimit */ gas: any; /** * Function that cancels the tx update */ onCancel: () => void; /** * Function that performs the rest of the tx update */ onSave: (tx: any) => void; } interface UpdateTx1559Options { /** * The legacy calculated max priorty fee used in subcomponent for threshold warning messages */ maxPriortyFeeThreshold: BigNumber; /** * The legacy calculated max fee used in subcomponent for threshold warning messages */ maxFeeThreshold: BigNumber; /** * Boolean to indicate to sumcomponent if the view should display only advanced settings */ showAdvanced: boolean; /** * Boolean to indicate if this is a cancel tx update */ isCancel: boolean; } const UpdateEIP1559Tx = ({ gas, accounts, selectedAddress, ticker, existingGas, gasFeeEstimates, gasEstimateType, contractExchangeRates, primaryCurrency, currentCurrency, nativeCurrency, conversionRate, isCancel, chainId, onCancel, onSave, }: Props) => { const [EIP1559TransactionData, setEIP1559TransactionData] = useState({}); const [animateOnGasChange, setAnimateOnGasChange] = useState(false); const [gasSelected, setGasSelected] = useState( AppConstants.GAS_OPTIONS.MEDIUM, ); const stopUpdateGas = useRef(false); /** * Flag to only display high gas selection option if the legacy is higher then low/med */ const onlyDisplayHigh = useRef(false); /** * Options */ const updateTx1559Options = useRef(); const pollToken = useRef(undefined); const firstTime = useRef(true); useEffect(() => { if (animateOnGasChange) setAnimateOnGasChange(false); }, [animateOnGasChange]); useEffect(() => { const { GasFeeController }: any = Engine.context; const startGasEstimatePolling = async () => { pollToken.current = await GasFeeController.getGasFeeEstimatesAndStartPolling( pollToken.current, ); }; startGasEstimatePolling(); return () => { GasFeeController.stopPolling(pollToken.current); }; }, []); const isMaxFeePerGasMoreThanLegacy = useCallback( (maxFeePerGas: BigNumber) => { const newDecMaxFeePerGas = new BigNumber(existingGas.maxFeePerGas).times( new BigNumber(isCancel ? CANCEL_RATE : SPEED_UP_RATE), ); return { result: maxFeePerGas.gte(newDecMaxFeePerGas), value: newDecMaxFeePerGas, }; }, [existingGas.maxFeePerGas, isCancel], ); const isMaxPriorityFeePerGasMoreThanLegacy = useCallback( (maxPriorityFeePerGas: BigNumber) => { const newDecMaxPriorityFeePerGas = new BigNumber( existingGas.maxPriorityFeePerGas, ).times(new BigNumber(isCancel ? CANCEL_RATE : SPEED_UP_RATE)); return { result: maxPriorityFeePerGas.gte(newDecMaxPriorityFeePerGas), value: newDecMaxPriorityFeePerGas, }; }, [existingGas.maxPriorityFeePerGas, isCancel], ); const validateAmount = useCallback( (updateTx) => { let error; const updateTxCost: any = hexToBN(`0x${updateTx.totalMaxHex}`); const accountBalance: any = hexToBN(accounts[selectedAddress].balance); const isMaxFeePerGasMoreThanLegacyResult = isMaxFeePerGasMoreThanLegacy( new BigNumber(updateTx.suggestedMaxFeePerGas), ); const isMaxPriorityFeePerGasMoreThanLegacyResult = isMaxPriorityFeePerGasMoreThanLegacy( new BigNumber(updateTx.suggestedMaxPriorityFeePerGas), ); if (accountBalance.lt(updateTxCost)) { const amount = renderFromWei(updateTxCost.sub(accountBalance)); const tokenSymbol = getTicker(ticker); error = strings('transaction.insufficient_amount', { amount, tokenSymbol, }); } else if (!isMaxFeePerGasMoreThanLegacyResult.result) { error = isCancel ? strings('edit_gas_fee_eip1559.max_fee_cancel_low', { cancel_value: isMaxFeePerGasMoreThanLegacyResult.value, }) : strings('edit_gas_fee_eip1559.max_fee_speed_up_low', { speed_up_floor_value: isMaxFeePerGasMoreThanLegacyResult.value, }); } else if (!isMaxPriorityFeePerGasMoreThanLegacyResult.result) { error = isCancel ? strings('edit_gas_fee_eip1559.max_priority_fee_cancel_low', { cancel_value: isMaxPriorityFeePerGasMoreThanLegacyResult.value, }) : strings('edit_gas_fee_eip1559.max_priority_fee_speed_up_low', { speed_up_floor_value: isMaxPriorityFeePerGasMoreThanLegacyResult.value, }); } return error; }, [ accounts, selectedAddress, isMaxFeePerGasMoreThanLegacy, isMaxPriorityFeePerGasMoreThanLegacy, ticker, isCancel, ], ); const parseTransactionDataEIP1559 = useCallback( (gasFee) => { const parsedTransactionEIP1559: any = parseTransactionEIP1559( { contractExchangeRates, swapsParams: undefined, conversionRate, currentCurrency, nativeCurrency, selectedGasFee: { ...gasFee, estimatedBaseFee: gasFeeEstimates.estimatedBaseFee, }, gasFeeEstimates, }, { onlyGas: true }, ); parsedTransactionEIP1559.error = validateAmount(parsedTransactionEIP1559); return parsedTransactionEIP1559; }, [ contractExchangeRates, conversionRate, currentCurrency, gasFeeEstimates, nativeCurrency, validateAmount, ], ); useEffect(() => { if (stopUpdateGas.current) return; if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) { const suggestedGasLimit = fromWei(gas, 'wei'); let updateTxEstimates = gasFeeEstimates[gasSelected]; if (firstTime.current) { const newDecMaxFeePerGas = new BigNumber( existingGas.maxFeePerGas, ).times(new BigNumber(isCancel ? CANCEL_RATE : SPEED_UP_RATE)); const newDecMaxPriorityFeePerGas = new BigNumber( existingGas.maxPriorityFeePerGas, ).times(new BigNumber(isCancel ? CANCEL_RATE : SPEED_UP_RATE)); //Check to see if default SPEED_UP_RATE/CANCEL_RATE is greater than current market medium value if ( !isMaxFeePerGasMoreThanLegacy( new BigNumber(gasFeeEstimates.medium.suggestedMaxPriorityFeePerGas), ).result || !isMaxPriorityFeePerGasMoreThanLegacy( new BigNumber(gasFeeEstimates.medium.suggestedMaxFeePerGas), ).result ) { updateTx1559Options.current = { maxPriortyFeeThreshold: newDecMaxPriorityFeePerGas, maxFeeThreshold: newDecMaxFeePerGas, showAdvanced: true, isCancel, }; updateTxEstimates = { selectedOption: undefined, suggestedMaxFeePerGas: newDecMaxFeePerGas, suggestedMaxPriorityFeePerGas: newDecMaxPriorityFeePerGas, }; onlyDisplayHigh.current = true; //Disable polling stopUpdateGas.current = true; setGasSelected(''); } else { updateTx1559Options.current = { maxPriortyFeeThreshold: gasFeeEstimates.medium.suggestedMaxPriorityFeePerGas, maxFeeThreshold: gasFeeEstimates.medium.suggestedMaxFeePerGas, showAdvanced: false, isCancel, }; setAnimateOnGasChange(true); } } const parsedTransactionEIP1559: any = parseTransactionDataEIP1559({ ...updateTxEstimates, suggestedGasLimit, selectedOption: gasSelected, }); firstTime.current = false; setEIP1559TransactionData(parsedTransactionEIP1559); } }, [ existingGas.maxFeePerGas, existingGas.maxPriorityFeePerGas, gasEstimateType, gasFeeEstimates, gasSelected, parseTransactionDataEIP1559, isCancel, gas, isMaxFeePerGasMoreThanLegacy, isMaxPriorityFeePerGasMoreThanLegacy, ]); const calculate1559TempGasFee = (gasValues: any, selected: string) => { if (selected && gas) { gasValues.suggestedGasLimit = fromWei(gas, 'wei'); } setEIP1559TransactionData( parseTransactionDataEIP1559({ ...gasValues, selectedOption: selected }), ); stopUpdateGas.current = !selected; setGasSelected(selected); }; const getGasAnalyticsParams = () => ({ chain_id: chainId, gas_estimate_type: gasEstimateType, gas_mode: gasSelected ? 'Basic' : 'Advanced', speed_set: gasSelected || undefined, }); return ( onSave(EIP1559TransactionData)} error={EIP1559TransactionData.error} ignoreOptions={ onlyDisplayHigh.current ? [AppConstants.GAS_OPTIONS.LOW, AppConstants.GAS_OPTIONS.MEDIUM] : [AppConstants.GAS_OPTIONS.LOW] } updateOption={updateTx1559Options.current} analyticsParams={getGasAnalyticsParams()} view={isCancel ? 'Transactions (Cancel)' : 'Transactions (Speed Up)'} animateOnChange={animateOnGasChange} /> ); }; const mapStateToProps = (state: any) => ({ accounts: state.engine.backgroundState.AccountTrackerController.accounts, selectedAddress: state.engine.backgroundState.PreferencesController.selectedAddress, ticker: state.engine.backgroundState.NetworkController.provider.ticker, gasFeeEstimates: state.engine.backgroundState.GasFeeController.gasFeeEstimates, gasEstimateType: state.engine.backgroundState.GasFeeController.gasEstimateType, contractExchangeRates: state.engine.backgroundState.TokenRatesController.contractExchangeRates, currentCurrency: state.engine.backgroundState.CurrencyRateController.currentCurrency, nativeCurrency: state.engine.backgroundState.CurrencyRateController.nativeCurrency, conversionRate: state.engine.backgroundState.CurrencyRateController.conversionRate, primaryCurrency: state.settings.primaryCurrency, chainId: state.engine.backgroundState.NetworkController.provider.chainId, }); export default connect(mapStateToProps)(UpdateEIP1559Tx);