mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
EIP-1559 and Rinkeby Testnet support in Swaps (#11635)
This commit is contained in:
parent
cbfde8a080
commit
714170c7b8
@ -651,7 +651,7 @@
|
||||
"message": "High"
|
||||
},
|
||||
"editGasLimitOutOfBounds": {
|
||||
"message": "Gas limit must be greater than 20999 and less than 7920027"
|
||||
"message": "Gas limit must be at least $1"
|
||||
},
|
||||
"editGasLimitTooltip": {
|
||||
"message": "Gas limit is the maximum units of gas you are willing to use. Units of gas are a multiplier to “Max priority fee” and “Max fee”."
|
||||
@ -1337,6 +1337,9 @@
|
||||
"networkNamePolygon": {
|
||||
"message": "Polygon"
|
||||
},
|
||||
"networkNameRinkeby": {
|
||||
"message": "Rinkeby"
|
||||
},
|
||||
"networkNameTestnet": {
|
||||
"message": "Testnet"
|
||||
},
|
||||
@ -2146,9 +2149,19 @@
|
||||
"message": "The swap of $1 to $2",
|
||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||
},
|
||||
"swapGasFeesDetails": {
|
||||
"message": "Gas fees are estimated and will fluctuate based on network traffic and transaction complexity."
|
||||
},
|
||||
"swapGasFeesLearnMore": {
|
||||
"message": "Learn more about gas fees"
|
||||
},
|
||||
"swapGasFeesSplit": {
|
||||
"message": "Gas fees on the previous screen are split between these two transactions."
|
||||
},
|
||||
"swapGasFeesSummary": {
|
||||
"message": "Gas fees are paid to crypto miners who process transactions on the $1 network. MetaMask does not profit from gas fees.",
|
||||
"description": "$1 is the selected network, e.g. Ethereum or BSC"
|
||||
},
|
||||
"swapHighSlippageWarning": {
|
||||
"message": "Slippage amount is very high."
|
||||
},
|
||||
|
@ -6,7 +6,11 @@ import { mapValues, cloneDeep } from 'lodash';
|
||||
import abi from 'human-standard-token-abi';
|
||||
import { calcTokenAmount } from '../../../ui/helpers/utils/token-util';
|
||||
import { calcGasTotal } from '../../../ui/pages/send/send.utils';
|
||||
import { conversionUtil } from '../../../shared/modules/conversion.utils';
|
||||
import {
|
||||
conversionUtil,
|
||||
decGWEIToHexWEI,
|
||||
addCurrencies,
|
||||
} from '../../../shared/modules/conversion.utils';
|
||||
import {
|
||||
DEFAULT_ERC20_APPROVE_GAS,
|
||||
QUOTES_EXPIRED_ERROR,
|
||||
@ -14,6 +18,7 @@ import {
|
||||
SWAPS_FETCH_ORDER_CONFLICT,
|
||||
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
|
||||
import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils';
|
||||
|
||||
@ -66,6 +71,8 @@ const initialState = {
|
||||
quotesLastFetched: null,
|
||||
customMaxGas: '',
|
||||
customGasPrice: null,
|
||||
customMaxFeePerGas: null,
|
||||
customMaxPriorityFeePerGas: null,
|
||||
selectedAggId: null,
|
||||
customApproveTxData: '',
|
||||
errorKey: '',
|
||||
@ -87,6 +94,7 @@ export default class SwapsController {
|
||||
fetchTradesInfo = defaultFetchTradesInfo,
|
||||
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
|
||||
getCurrentChainId,
|
||||
getEIP1559GasFeeEstimates,
|
||||
}) {
|
||||
this.store = new ObservableStore({
|
||||
swapsState: { ...initialState.swapsState },
|
||||
@ -95,6 +103,7 @@ export default class SwapsController {
|
||||
this._fetchTradesInfo = fetchTradesInfo;
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
|
||||
this._getCurrentChainId = getCurrentChainId;
|
||||
this._getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates;
|
||||
|
||||
this.getBufferedGasLimit = getBufferedGasLimit;
|
||||
this.tokenRatesStore = tokenRatesStore;
|
||||
@ -440,6 +449,23 @@ export default class SwapsController {
|
||||
});
|
||||
}
|
||||
|
||||
setSwapsTxMaxFeePerGas(maxFeePerGas) {
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, customMaxFeePerGas: maxFeePerGas },
|
||||
});
|
||||
}
|
||||
|
||||
setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas) {
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
swapsState: {
|
||||
...swapsState,
|
||||
customMaxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setSwapsTxGasLimit(gasLimit) {
|
||||
const { swapsState } = this.store.getState();
|
||||
this.store.updateState({
|
||||
@ -494,16 +520,11 @@ export default class SwapsController {
|
||||
clearTimeout(this.pollingTimeout);
|
||||
}
|
||||
|
||||
async _getEthersGasPrice() {
|
||||
const ethersGasPrice = await this.ethersProvider.getGasPrice();
|
||||
return ethersGasPrice.toHexString();
|
||||
}
|
||||
|
||||
async _findTopQuoteAndCalculateSavings(quotes = {}) {
|
||||
const tokenConversionRates = this.tokenRatesStore.getState()
|
||||
.contractExchangeRates;
|
||||
const {
|
||||
swapsState: { customGasPrice },
|
||||
swapsState: { customGasPrice, customMaxPriorityFeePerGas },
|
||||
} = this.store.getState();
|
||||
const chainId = this._getCurrentChainId();
|
||||
|
||||
@ -514,7 +535,36 @@ export default class SwapsController {
|
||||
|
||||
const newQuotes = cloneDeep(quotes);
|
||||
|
||||
const usedGasPrice = customGasPrice || (await this._getEthersGasPrice());
|
||||
const {
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
} = await this._getEIP1559GasFeeEstimates();
|
||||
|
||||
let usedGasPrice = '0x0';
|
||||
|
||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
||||
const {
|
||||
high: { suggestedMaxPriorityFeePerGas },
|
||||
estimatedBaseFee,
|
||||
} = gasFeeEstimates;
|
||||
|
||||
usedGasPrice = addCurrencies(
|
||||
customMaxPriorityFeePerGas || // Is already in hex WEI.
|
||||
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
|
||||
decGWEIToHexWEI(estimatedBaseFee),
|
||||
{
|
||||
aBase: 16,
|
||||
bBase: 16,
|
||||
toNumericBase: 'hex',
|
||||
numberOfDecimals: 6,
|
||||
},
|
||||
);
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY) {
|
||||
usedGasPrice = customGasPrice || decGWEIToHexWEI(gasFeeEstimates.high);
|
||||
} else if (gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE) {
|
||||
usedGasPrice =
|
||||
customGasPrice || decGWEIToHexWEI(gasFeeEstimates.gasPrice);
|
||||
}
|
||||
|
||||
let topAggId = null;
|
||||
let overallValueOfBestQuoteForSorting = null;
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
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 SwapsController, { utils } from './swaps';
|
||||
import { NETWORK_EVENTS } from './network';
|
||||
|
||||
@ -120,7 +121,9 @@ const EMPTY_INIT_STATE = {
|
||||
tradeTxId: null,
|
||||
approveTxId: null,
|
||||
quotesLastFetched: null,
|
||||
customMaxFeePerGas: null,
|
||||
customMaxGas: '',
|
||||
customMaxPriorityFeePerGas: null,
|
||||
customGasPrice: null,
|
||||
selectedAggId: null,
|
||||
customApproveTxData: '',
|
||||
@ -138,6 +141,14 @@ const fetchTradesInfoStub = sandbox.stub();
|
||||
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
|
||||
const getCurrentChainIdStub = sandbox.stub();
|
||||
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
|
||||
const getEIP1559GasFeeEstimatesStub = sandbox.stub(() => {
|
||||
return {
|
||||
gasFeeEstimates: {
|
||||
high: '150',
|
||||
},
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||
};
|
||||
});
|
||||
|
||||
describe('SwapsController', function () {
|
||||
let provider;
|
||||
@ -152,6 +163,7 @@ describe('SwapsController', function () {
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
||||
getCurrentChainId: getCurrentChainIdStub,
|
||||
getEIP1559GasFeeEstimates: getEIP1559GasFeeEstimatesStub,
|
||||
});
|
||||
};
|
||||
|
||||
@ -687,8 +699,8 @@ describe('SwapsController', function () {
|
||||
total: '5.4949494949494949495',
|
||||
medianMetaMaskFee: '0.44444444444444444444',
|
||||
},
|
||||
ethFee: '33554432',
|
||||
overallValueOfQuote: '-33554382',
|
||||
ethFee: '5.033165',
|
||||
overallValueOfQuote: '44.966835',
|
||||
metaMaskFeeInEth: '0.5050505050505050505',
|
||||
ethValueOfTokens: '50',
|
||||
});
|
||||
|
@ -1200,10 +1200,12 @@ export default class TransactionController extends EventEmitter {
|
||||
txMeta.chainId,
|
||||
);
|
||||
|
||||
const quoteVsExecutionRatio = `${new BigNumber(tokensReceived, 10)
|
||||
.div(txMeta.swapMetaData.token_to_amount, 10)
|
||||
.times(100)
|
||||
.round(2)}%`;
|
||||
const quoteVsExecutionRatio = tokensReceived
|
||||
? `${new BigNumber(tokensReceived, 10)
|
||||
.div(txMeta.swapMetaData.token_to_amount, 10)
|
||||
.times(100)
|
||||
.round(2)}%`
|
||||
: null;
|
||||
|
||||
const estimatedVsUsedGasRatio = `${new BigNumber(
|
||||
txMeta.txReceipt.gasUsed,
|
||||
|
@ -498,6 +498,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
getCurrentChainId: this.networkController.getCurrentChainId.bind(
|
||||
this.networkController,
|
||||
),
|
||||
getEIP1559GasFeeEstimates: this.gasFeeController.fetchGasFeeEstimates.bind(
|
||||
this.gasFeeController,
|
||||
),
|
||||
});
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
@ -1042,6 +1045,14 @@ export default class MetamaskController extends EventEmitter {
|
||||
swapsController.setSwapsTxGasLimit,
|
||||
swapsController,
|
||||
),
|
||||
setSwapsTxMaxFeePerGas: nodeify(
|
||||
swapsController.setSwapsTxMaxFeePerGas,
|
||||
swapsController,
|
||||
),
|
||||
setSwapsTxMaxFeePriorityPerGas: nodeify(
|
||||
swapsController.setSwapsTxMaxFeePriorityPerGas,
|
||||
swapsController,
|
||||
),
|
||||
safeRefetchQuotes: nodeify(
|
||||
swapsController.safeRefetchQuotes,
|
||||
swapsController,
|
||||
|
@ -37,4 +37,5 @@ export const EDIT_GAS_MODES = {
|
||||
SPEED_UP: 'speed-up',
|
||||
CANCEL: 'cancel',
|
||||
MODIFY_IN_PLACE: 'modify-in-place',
|
||||
SWAPS: 'swaps',
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
POLYGON_CHAIN_ID,
|
||||
MATIC_SYMBOL,
|
||||
MATIC_TOKEN_IMAGE_URL,
|
||||
RINKEBY_CHAIN_ID,
|
||||
} from './network';
|
||||
|
||||
export const QUOTES_EXPIRED_ERROR = 'quotes-expired';
|
||||
@ -54,6 +55,14 @@ export const TEST_ETH_SWAPS_TOKEN_OBJECT = {
|
||||
iconUrl: TEST_ETH_TOKEN_IMAGE_URL,
|
||||
};
|
||||
|
||||
export const RINKEBY_SWAPS_TOKEN_OBJECT = {
|
||||
symbol: ETH_SYMBOL,
|
||||
name: 'Ether',
|
||||
address: DEFAULT_TOKEN_ADDRESS,
|
||||
decimals: 18,
|
||||
iconUrl: TEST_ETH_TOKEN_IMAGE_URL,
|
||||
};
|
||||
|
||||
// A gas value for ERC20 approve calls that should be sufficient for all ERC20 approve implementations
|
||||
export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0';
|
||||
|
||||
@ -78,6 +87,7 @@ const SWAPS_TESTNET_HOST = 'https://metaswap-api.airswap-dev.codefi.network';
|
||||
|
||||
const BSC_DEFAULT_BLOCK_EXPLORER_URL = 'https://bscscan.com/';
|
||||
const MAINNET_DEFAULT_BLOCK_EXPLORER_URL = 'https://etherscan.io/';
|
||||
const RINKEBY_DEFAULT_BLOCK_EXPLORER_URL = 'https://rinkeby.etherscan.io/';
|
||||
const POLYGON_DEFAULT_BLOCK_EXPLORER_URL = 'https://polygonscan.com/';
|
||||
|
||||
export const ALLOWED_SWAPS_CHAIN_IDS = {
|
||||
@ -85,6 +95,7 @@ export const ALLOWED_SWAPS_CHAIN_IDS = {
|
||||
[SWAPS_TESTNET_CHAIN_ID]: true,
|
||||
[BSC_CHAIN_ID]: true,
|
||||
[POLYGON_CHAIN_ID]: true,
|
||||
[RINKEBY_CHAIN_ID]: true,
|
||||
};
|
||||
|
||||
// This is mapping for v1 URLs and will be removed once we migrate to v2.
|
||||
@ -92,6 +103,7 @@ export const METASWAP_CHAINID_API_HOST_MAP = {
|
||||
[MAINNET_CHAIN_ID]: METASWAP_ETH_API_HOST,
|
||||
[SWAPS_TESTNET_CHAIN_ID]: SWAPS_TESTNET_HOST,
|
||||
[BSC_CHAIN_ID]: METASWAP_BSC_API_HOST,
|
||||
[RINKEBY_CHAIN_ID]: SWAPS_TESTNET_HOST,
|
||||
};
|
||||
|
||||
export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = {
|
||||
@ -99,6 +111,7 @@ export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = {
|
||||
[SWAPS_TESTNET_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS,
|
||||
[BSC_CHAIN_ID]: BSC_CONTRACT_ADDRESS,
|
||||
[POLYGON_CHAIN_ID]: POLYGON_CONTRACT_ADDRESS,
|
||||
[RINKEBY_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS,
|
||||
};
|
||||
|
||||
export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
|
||||
@ -106,14 +119,17 @@ export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = {
|
||||
[SWAPS_TESTNET_CHAIN_ID]: TEST_ETH_SWAPS_TOKEN_OBJECT,
|
||||
[BSC_CHAIN_ID]: BNB_SWAPS_TOKEN_OBJECT,
|
||||
[POLYGON_CHAIN_ID]: MATIC_SWAPS_TOKEN_OBJECT,
|
||||
[RINKEBY_CHAIN_ID]: RINKEBY_SWAPS_TOKEN_OBJECT,
|
||||
};
|
||||
|
||||
export const SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP = {
|
||||
[BSC_CHAIN_ID]: BSC_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
[MAINNET_CHAIN_ID]: MAINNET_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
[POLYGON_CHAIN_ID]: POLYGON_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
[RINKEBY_CHAIN_ID]: RINKEBY_DEFAULT_BLOCK_EXPLORER_URL,
|
||||
};
|
||||
|
||||
export const ETHEREUM = 'ethereum';
|
||||
export const POLYGON = 'polygon';
|
||||
export const BSC = 'bsc';
|
||||
export const RINKEBY = 'rinkeby';
|
||||
|
@ -268,6 +268,15 @@ const toNegative = (n, options = {}) => {
|
||||
return multiplyCurrencies(n, -1, options);
|
||||
};
|
||||
|
||||
export function decGWEIToHexWEI(decGWEI) {
|
||||
return conversionUtil(decGWEI, {
|
||||
fromNumericBase: 'dec',
|
||||
toNumericBase: 'hex',
|
||||
fromDenomination: 'GWEI',
|
||||
toDenomination: 'WEI',
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
conversionUtil,
|
||||
addCurrencies,
|
||||
|
@ -217,7 +217,7 @@ export const createSwapsMockStore = () => {
|
||||
selectedAggId: 'TEST_AGG_2',
|
||||
customApproveTxData: '',
|
||||
errorKey: '',
|
||||
topAggId: null,
|
||||
topAggId: 'TEST_AGG_BEST',
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
|
@ -79,3 +79,27 @@ export const createFeatureFlagsResponse = () => {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createGasFeeEstimatesForFeeMarket = () => {
|
||||
return {
|
||||
low: {
|
||||
minWaitTimeEstimate: 180000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 0,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
};
|
||||
|
@ -33,6 +33,7 @@ export default function AdvancedGasControls({
|
||||
maxPriorityFeeFiat,
|
||||
maxFeeFiat,
|
||||
gasErrors,
|
||||
minimumGasLimit = 21000,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
@ -65,7 +66,7 @@ export default function AdvancedGasControls({
|
||||
titleText={t('gasLimit')}
|
||||
error={
|
||||
gasErrors?.gasLimit
|
||||
? getGasFormErrorText(gasErrors.gasLimit, t)
|
||||
? getGasFormErrorText(gasErrors.gasLimit, t, { minimumGasLimit })
|
||||
: null
|
||||
}
|
||||
onChange={setGasLimit}
|
||||
@ -231,4 +232,5 @@ AdvancedGasControls.propTypes = {
|
||||
maxPriorityFeeFiat: PropTypes.string,
|
||||
maxFeeFiat: PropTypes.string,
|
||||
gasErrors: PropTypes.object,
|
||||
minimumGasLimit: PropTypes.number,
|
||||
};
|
||||
|
@ -60,6 +60,7 @@ export default function EditGasDisplay({
|
||||
warning,
|
||||
gasErrors,
|
||||
onManualChange,
|
||||
minimumGasLimit,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
@ -218,6 +219,7 @@ export default function EditGasDisplay({
|
||||
maxFeeFiat={maxFeePerGasFiat}
|
||||
gasErrors={gasErrors}
|
||||
onManualChange={onManualChange}
|
||||
minimumGasLimit={minimumGasLimit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -266,4 +268,5 @@ EditGasDisplay.propTypes = {
|
||||
transaction: PropTypes.object,
|
||||
gasErrors: PropTypes.object,
|
||||
onManualChange: PropTypes.func,
|
||||
minimumGasLimit: PropTypes.number,
|
||||
};
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
hexToDecimal,
|
||||
} from '../../../helpers/utils/conversions.util';
|
||||
|
||||
import Popover from '../../ui/popover';
|
||||
@ -27,6 +28,7 @@ import {
|
||||
hideModal,
|
||||
hideSidebar,
|
||||
updateTransaction,
|
||||
updateCustomSwapsEIP1559GasParams,
|
||||
} from '../../../store/actions';
|
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
|
||||
@ -38,6 +40,7 @@ export default function EditGasPopover({
|
||||
transaction,
|
||||
mode,
|
||||
onClose,
|
||||
minimumGasLimit,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
const dispatch = useDispatch();
|
||||
@ -56,6 +59,8 @@ export default function EditGasPopover({
|
||||
setDappSuggestedGasFeeAcknowledged,
|
||||
] = useState(false);
|
||||
|
||||
const minimumGasLimitDec = hexToDecimal(minimumGasLimit);
|
||||
|
||||
const {
|
||||
maxPriorityFeePerGas,
|
||||
setMaxPriorityFeePerGas,
|
||||
@ -78,7 +83,7 @@ export default function EditGasPopover({
|
||||
hasGasErrors,
|
||||
gasErrors,
|
||||
onManualChange,
|
||||
} = useGasFeeInputs(defaultEstimateToUse, transaction);
|
||||
} = useGasFeeInputs(defaultEstimateToUse, transaction, minimumGasLimit, mode);
|
||||
|
||||
const [showAdvancedForm, setShowAdvancedForm] = useState(
|
||||
!estimateToUse || hasGasErrors,
|
||||
@ -137,6 +142,12 @@ export default function EditGasPopover({
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case EDIT_GAS_MODES.SWAPS:
|
||||
// This popover component should only be used for the "FEE_MARKET" type in Swaps.
|
||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
||||
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -227,6 +238,7 @@ export default function EditGasPopover({
|
||||
hasGasErrors={hasGasErrors}
|
||||
gasErrors={gasErrors}
|
||||
onManualChange={onManualChange}
|
||||
minimumGasLimit={minimumGasLimitDec}
|
||||
{...editGasDisplayProps}
|
||||
/>
|
||||
</>
|
||||
@ -244,4 +256,5 @@ EditGasPopover.propTypes = {
|
||||
transaction: PropTypes.object,
|
||||
mode: PropTypes.oneOf(Object.values(EDIT_GAS_MODES)),
|
||||
defaultEstimateToUse: PropTypes.string,
|
||||
minimumGasLimit: PropTypes.string,
|
||||
};
|
||||
|
@ -22,14 +22,14 @@ export default function TransactionDetailItem({
|
||||
<Typography
|
||||
color={detailTitleColor}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className="transaction-detail-item__title"
|
||||
>
|
||||
{detailTitle}
|
||||
</Typography>
|
||||
{detailText && (
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className="transaction-detail-item__detail-text"
|
||||
color={COLORS.UI4}
|
||||
>
|
||||
@ -39,20 +39,24 @@ export default function TransactionDetailItem({
|
||||
<Typography
|
||||
color={COLORS.BLACK}
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className="transaction-detail-item__total"
|
||||
>
|
||||
{detailTotal}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="transaction-detail-item__row">
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className="transaction-detail-item__subtitle"
|
||||
color={COLORS.UI4}
|
||||
>
|
||||
{subTitle}
|
||||
</Typography>
|
||||
{React.isValidElement(subTitle) ? (
|
||||
<div className="transaction-detail-item__subtitle">{subTitle}</div>
|
||||
) : (
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className="transaction-detail-item__subtitle"
|
||||
color={COLORS.UI4}
|
||||
>
|
||||
{subTitle}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
|
@ -24,6 +24,10 @@
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid $ui-3;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
|
@ -285,7 +285,7 @@ export function getUnapprovedTxs(state) {
|
||||
}
|
||||
|
||||
export function isEIP1559Network(state) {
|
||||
return state.metamask.networkDetails.EIPS[1559] === true;
|
||||
return state.metamask.networkDetails?.EIPS[1559] === true;
|
||||
}
|
||||
|
||||
export function getGasEstimateType(state) {
|
||||
|
@ -45,6 +45,7 @@ import {
|
||||
getValueFromWeiHex,
|
||||
decGWEIToHexWEI,
|
||||
hexWEIToDecGWEI,
|
||||
addHexes,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import { conversionLessThan } from '../../../shared/modules/conversion.utils';
|
||||
import { calcTokenAmount } from '../../helpers/utils/token-util';
|
||||
@ -65,6 +66,7 @@ import {
|
||||
SWAPS_FETCH_ORDER_CONFLICT,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import { TRANSACTION_TYPES } from '../../../shared/constants/transaction';
|
||||
import { isEIP1559Network, getGasFeeEstimates } from '../metamask/metamask';
|
||||
|
||||
const GAS_PRICES_LOADING_STATES = {
|
||||
INITIAL: 'INITIAL',
|
||||
@ -242,6 +244,12 @@ export const getCustomSwapsGas = (state) =>
|
||||
export const getCustomSwapsGasPrice = (state) =>
|
||||
state.metamask.swapsState.customGasPrice;
|
||||
|
||||
export const getCustomMaxFeePerGas = (state) =>
|
||||
state.metamask.swapsState.customMaxFeePerGas;
|
||||
|
||||
export const getCustomMaxPriorityFeePerGas = (state) =>
|
||||
state.metamask.swapsState.customMaxPriorityFeePerGas;
|
||||
|
||||
export const getFetchParams = (state) => state.metamask.swapsState.fetchParams;
|
||||
|
||||
export const getQuotes = (state) => state.metamask.swapsState.quotes;
|
||||
@ -503,6 +511,7 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
|
||||
const hardwareWalletUsed = isHardwareWallet(state);
|
||||
const hardwareWalletType = getHardwareWalletType(state);
|
||||
const EIP1559Network = isEIP1559Network(state);
|
||||
metaMetricsEvent({
|
||||
event: 'Quotes Requested',
|
||||
category: 'swaps',
|
||||
@ -544,7 +553,9 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
),
|
||||
);
|
||||
|
||||
const gasPriceFetchPromise = dispatch(fetchAndSetSwapsGasPriceInfo());
|
||||
const gasPriceFetchPromise = EIP1559Network
|
||||
? null // For EIP 1559 we can get gas prices via "useGasFeeEstimates".
|
||||
: dispatch(fetchAndSetSwapsGasPriceInfo());
|
||||
|
||||
const [[fetchedQuotes, selectedAggId]] = await Promise.all([
|
||||
fetchAndSetQuotesPromise,
|
||||
@ -616,6 +627,7 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
const state = getState();
|
||||
const chainId = getCurrentChainId(state);
|
||||
const hardwareWalletUsed = isHardwareWallet(state);
|
||||
const EIP1559Network = isEIP1559Network(state);
|
||||
let swapsLivenessForNetwork = {
|
||||
swapsFeatureIsLive: false,
|
||||
useNewSwapsApi: false,
|
||||
@ -637,6 +649,8 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
}
|
||||
|
||||
const customSwapsGas = getCustomSwapsGas(state);
|
||||
const customMaxFeePerGas = getCustomMaxFeePerGas(state);
|
||||
const customMaxPriorityFeePerGas = getCustomMaxPriorityFeePerGas(state);
|
||||
const fetchParams = getFetchParams(state);
|
||||
const { metaData, value: swapTokenValue, slippage } = fetchParams;
|
||||
const { sourceTokenInfo = {}, destinationTokenInfo = {} } = metaData;
|
||||
@ -649,6 +663,26 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
|
||||
const { fast: fastGasEstimate } = getSwapGasPriceEstimateData(state);
|
||||
|
||||
let maxFeePerGas;
|
||||
let maxPriorityFeePerGas;
|
||||
let baseAndPriorityFeePerGas;
|
||||
|
||||
if (EIP1559Network) {
|
||||
const {
|
||||
high: { suggestedMaxFeePerGas, suggestedMaxPriorityFeePerGas },
|
||||
estimatedBaseFee = '0',
|
||||
} = getGasFeeEstimates(state);
|
||||
maxFeePerGas =
|
||||
customMaxFeePerGas || decGWEIToHexWEI(suggestedMaxFeePerGas);
|
||||
maxPriorityFeePerGas =
|
||||
customMaxPriorityFeePerGas ||
|
||||
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas);
|
||||
baseAndPriorityFeePerGas = addHexes(
|
||||
decGWEIToHexWEI(estimatedBaseFee),
|
||||
maxPriorityFeePerGas,
|
||||
);
|
||||
}
|
||||
|
||||
const usedQuote = getUsedQuote(state);
|
||||
const usedTradeTxParams = usedQuote.trade;
|
||||
|
||||
@ -668,7 +702,13 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
|
||||
const usedGasPrice = getUsedSwapsGasPrice(state);
|
||||
usedTradeTxParams.gas = maxGasLimit;
|
||||
usedTradeTxParams.gasPrice = usedGasPrice;
|
||||
if (EIP1559Network) {
|
||||
usedTradeTxParams.maxFeePerGas = maxFeePerGas;
|
||||
usedTradeTxParams.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
||||
delete usedTradeTxParams.gasPrice;
|
||||
} else {
|
||||
usedTradeTxParams.gasPrice = usedGasPrice;
|
||||
}
|
||||
|
||||
const usdConversionRate = getUSDConversionRate(state);
|
||||
const destinationValue = calcTokenAmount(
|
||||
@ -682,7 +722,10 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
.plus(usedQuote.approvalNeeded?.gas || '0x0', 16)
|
||||
.toString(16);
|
||||
const gasEstimateTotalInUSD = getValueFromWeiHex({
|
||||
value: calcGasTotal(totalGasLimitEstimate, usedGasPrice),
|
||||
value: calcGasTotal(
|
||||
totalGasLimitEstimate,
|
||||
EIP1559Network ? baseAndPriorityFeePerGas : usedGasPrice,
|
||||
),
|
||||
toCurrency: 'usd',
|
||||
conversionRate: usdConversionRate,
|
||||
numberOfDecimals: 6,
|
||||
@ -714,6 +757,11 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
is_hardware_wallet: hardwareWalletUsed,
|
||||
hardware_wallet_type: getHardwareWalletType(state),
|
||||
};
|
||||
if (EIP1559Network) {
|
||||
swapMetaData.max_fee_per_gas = maxFeePerGas;
|
||||
swapMetaData.max_priority_fee_per_gas = maxPriorityFeePerGas;
|
||||
swapMetaData.base_and_priority_fee_per_gas = baseAndPriorityFeePerGas;
|
||||
}
|
||||
|
||||
metaMetricsEvent({
|
||||
event: 'Swap Started',
|
||||
@ -744,6 +792,11 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
}
|
||||
|
||||
if (approveTxParams) {
|
||||
if (EIP1559Network) {
|
||||
approveTxParams.maxFeePerGas = maxFeePerGas;
|
||||
approveTxParams.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
||||
delete approveTxParams.gasPrice;
|
||||
}
|
||||
const approveTxMeta = await dispatch(
|
||||
addUnapprovedTransaction(
|
||||
{ ...approveTxParams, amount: '0x0' },
|
||||
|
@ -1,6 +1,6 @@
|
||||
import nock from 'nock';
|
||||
|
||||
import { MOCKS } from '../../../test/jest';
|
||||
import { MOCKS, createSwapsMockStore } from '../../../test/jest';
|
||||
import { setSwapsLiveness } from '../../store/actions';
|
||||
import { setStorageItem } from '../../helpers/utils/storage-helpers';
|
||||
import * as swaps from './swaps';
|
||||
@ -164,4 +164,82 @@ describe('Ducks - Swaps', () => {
|
||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomSwapsGas', () => {
|
||||
it('returns "customMaxGas"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const customMaxGas = '29000';
|
||||
state.metamask.swapsState.customMaxGas = customMaxGas;
|
||||
expect(swaps.getCustomSwapsGas(state)).toBe(customMaxGas);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomMaxFeePerGas', () => {
|
||||
it('returns "customMaxFeePerGas"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const customMaxFeePerGas = '20';
|
||||
state.metamask.swapsState.customMaxFeePerGas = customMaxFeePerGas;
|
||||
expect(swaps.getCustomMaxFeePerGas(state)).toBe(customMaxFeePerGas);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustomMaxPriorityFeePerGas', () => {
|
||||
it('returns "customMaxPriorityFeePerGas"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const customMaxPriorityFeePerGas = '3';
|
||||
state.metamask.swapsState.customMaxPriorityFeePerGas = customMaxPriorityFeePerGas;
|
||||
expect(swaps.getCustomMaxPriorityFeePerGas(state)).toBe(
|
||||
customMaxPriorityFeePerGas,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSwapsFeatureIsLive', () => {
|
||||
it('returns true for "swapsFeatureIsLive"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const swapsFeatureIsLive = true;
|
||||
state.metamask.swapsState.swapsFeatureIsLive = swapsFeatureIsLive;
|
||||
expect(swaps.getSwapsFeatureIsLive(state)).toBe(swapsFeatureIsLive);
|
||||
});
|
||||
|
||||
it('returns false for "swapsFeatureIsLive"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const swapsFeatureIsLive = false;
|
||||
state.metamask.swapsState.swapsFeatureIsLive = swapsFeatureIsLive;
|
||||
expect(swaps.getSwapsFeatureIsLive(state)).toBe(swapsFeatureIsLive);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUseNewSwapsApi', () => {
|
||||
it('returns true for "useNewSwapsApi"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const useNewSwapsApi = true;
|
||||
state.metamask.swapsState.useNewSwapsApi = useNewSwapsApi;
|
||||
expect(swaps.getUseNewSwapsApi(state)).toBe(useNewSwapsApi);
|
||||
});
|
||||
|
||||
it('returns false for "useNewSwapsApi"', () => {
|
||||
const state = createSwapsMockStore();
|
||||
const useNewSwapsApi = false;
|
||||
state.metamask.swapsState.useNewSwapsApi = useNewSwapsApi;
|
||||
expect(swaps.getUseNewSwapsApi(state)).toBe(useNewSwapsApi);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUsedQuote', () => {
|
||||
it('returns selected quote', () => {
|
||||
const state = createSwapsMockStore();
|
||||
expect(swaps.getUsedQuote(state)).toMatchObject(
|
||||
state.metamask.swapsState.quotes.TEST_AGG_2,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns best quote', () => {
|
||||
const state = createSwapsMockStore();
|
||||
state.metamask.swapsState.selectedAggId = null;
|
||||
expect(swaps.getUsedQuote(state)).toMatchObject(
|
||||
state.metamask.swapsState.quotes.TEST_AGG_BEST,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,10 +7,10 @@ export const GAS_FORM_ERRORS = {
|
||||
MAX_FEE_HIGH_WARNING: 'editGasMaxFeeHigh',
|
||||
};
|
||||
|
||||
export function getGasFormErrorText(type, t) {
|
||||
export function getGasFormErrorText(type, t, { minimumGasLimit } = {}) {
|
||||
switch (type) {
|
||||
case GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS:
|
||||
return t('editGasLimitOutOfBounds');
|
||||
return t('editGasLimitOutOfBounds', [minimumGasLimit]);
|
||||
case GAS_FORM_ERRORS.MAX_PRIORITY_FEE_TOO_LOW:
|
||||
return t('editGasMaxPriorityFeeLow');
|
||||
case GAS_FORM_ERRORS.MAX_FEE_TOO_LOW:
|
||||
|
@ -3,8 +3,15 @@ import { useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { findKey } from 'lodash';
|
||||
|
||||
import { GAS_ESTIMATE_TYPES } from '../../shared/constants/gas';
|
||||
import { multiplyCurrencies } from '../../shared/modules/conversion.utils';
|
||||
import {
|
||||
GAS_ESTIMATE_TYPES,
|
||||
EDIT_GAS_MODES,
|
||||
GAS_LIMITS,
|
||||
} from '../../shared/constants/gas';
|
||||
import {
|
||||
multiplyCurrencies,
|
||||
conversionLessThan,
|
||||
} from '../../shared/modules/conversion.utils';
|
||||
import {
|
||||
getMaximumGasTotalInHexWei,
|
||||
getMinimumGasTotalInHexWei,
|
||||
@ -152,7 +159,12 @@ function getMatchingEstimateFromGasFees(
|
||||
* './useGasFeeEstimates'
|
||||
* ).GasEstimates} - gas fee input state and the GasFeeEstimates object
|
||||
*/
|
||||
export function useGasFeeInputs(defaultEstimateToUse = 'medium', transaction) {
|
||||
export function useGasFeeInputs(
|
||||
defaultEstimateToUse = 'medium',
|
||||
transaction,
|
||||
minimumGasLimit,
|
||||
editGasMode,
|
||||
) {
|
||||
// We need to know whether to show fiat conversions or not, so that we can
|
||||
// default our fiat values to empty strings if showing fiat is not wanted or
|
||||
// possible.
|
||||
@ -257,9 +269,11 @@ export function useGasFeeInputs(defaultEstimateToUse = 'medium', transaction) {
|
||||
// conditionally set to the appropriate fields to compute the minimum
|
||||
// and maximum cost of a transaction given the current estimates or selected
|
||||
// gas fees.
|
||||
|
||||
const gasSettings = {
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
};
|
||||
|
||||
if (gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET) {
|
||||
gasSettings.maxFeePerGas = decGWEIToHexWEI(maxFeePerGasToUse);
|
||||
gasSettings.maxPriorityFeePerGas = decGWEIToHexWEI(
|
||||
@ -276,8 +290,18 @@ export function useGasFeeInputs(defaultEstimateToUse = 'medium', transaction) {
|
||||
|
||||
// The maximum amount this transaction will cost
|
||||
const maximumCostInHexWei = getMaximumGasTotalInHexWei(gasSettings);
|
||||
|
||||
// If in swaps, we want to calculate the minimum gas fee differently than the max
|
||||
const minGasSettings = {};
|
||||
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
|
||||
minGasSettings.gasLimit = decimalToHex(minimumGasLimit);
|
||||
}
|
||||
|
||||
// The minimum amount this transaction will cost's
|
||||
const minimumCostInHexWei = getMinimumGasTotalInHexWei(gasSettings);
|
||||
const minimumCostInHexWei = getMinimumGasTotalInHexWei({
|
||||
...gasSettings,
|
||||
...minGasSettings,
|
||||
});
|
||||
|
||||
// We need to display the estimated fiat currency impact of the
|
||||
// maxPriorityFeePerGas field to the user. This hook calculates that amount.
|
||||
@ -331,7 +355,12 @@ export function useGasFeeInputs(defaultEstimateToUse = 'medium', transaction) {
|
||||
const gasErrors = {};
|
||||
const gasWarnings = {};
|
||||
|
||||
if (gasLimit < 21000 || gasLimit > 7920027) {
|
||||
const gasLimitTooLow = conversionLessThan(
|
||||
{ value: gasLimit, fromNumericBase: 'dec' },
|
||||
{ value: minimumGasLimit || GAS_LIMITS.SIMPLE, fromNumericBase: 'hex' },
|
||||
);
|
||||
|
||||
if (gasLimitTooLow) {
|
||||
gasErrors.gasLimit = GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapStepIcon renders the component 1`] = `
|
||||
exports[`SwapStepIcon renders the component with step 1 by default 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
fill="none"
|
||||
@ -23,3 +23,27 @@ exports[`SwapStepIcon renders the component 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SwapStepIcon renders the component with step 2 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
fill="none"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
width="14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="7"
|
||||
cy="7"
|
||||
r="6.25"
|
||||
stroke="#037DD6"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M8.92 9.776H5V9.368C5 9.048 5.056 8.77067 5.168 8.536C5.28 8.296 5.42133 8.08533 5.592 7.904C5.768 7.71733 5.96267 7.54933 6.176 7.4C6.39467 7.25067 6.608 7.10133 6.816 6.952C6.928 6.872 7.03467 6.78933 7.136 6.704C7.24267 6.61867 7.33333 6.53067 7.408 6.44C7.488 6.34933 7.552 6.256 7.6 6.16C7.648 6.064 7.672 5.96533 7.672 5.864C7.672 5.67733 7.616 5.52 7.504 5.392C7.39733 5.25867 7.22933 5.192 7 5.192C6.88267 5.192 6.776 5.21333 6.68 5.256C6.584 5.29333 6.50133 5.344 6.432 5.408C6.368 5.472 6.31733 5.54667 6.28 5.632C6.248 5.71733 6.232 5.808 6.232 5.904H5.024C5.024 5.62667 5.07467 5.37067 5.176 5.136C5.27733 4.90133 5.41867 4.70133 5.6 4.536C5.78133 4.36533 5.99467 4.23467 6.24 4.144C6.48533 4.048 6.752 4 7.04 4C7.28 4 7.50933 4.03733 7.728 4.112C7.952 4.18667 8.14933 4.29867 8.32 4.448C8.49067 4.59733 8.62667 4.784 8.728 5.008C8.82933 5.22667 8.88 5.48267 8.88 5.776C8.88 6.032 8.85067 6.25867 8.792 6.456C8.73333 6.648 8.65067 6.824 8.544 6.984C8.44267 7.13867 8.32 7.28 8.176 7.408C8.032 7.536 7.87733 7.66133 7.712 7.784C7.64267 7.832 7.55733 7.888 7.456 7.952C7.36 8.016 7.26133 8.08267 7.16 8.152C7.064 8.22133 6.97333 8.29333 6.888 8.368C6.80267 8.44267 6.74133 8.51467 6.704 8.584H8.92V9.776Z"
|
||||
fill="#037DD6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
@ -4,8 +4,13 @@ import { renderWithProvider } from '../../../../test/jest';
|
||||
import SwapStepIcon from './swap-step-icon';
|
||||
|
||||
describe('SwapStepIcon', () => {
|
||||
it('renders the component', () => {
|
||||
it('renders the component with step 1 by default', () => {
|
||||
const { container } = renderWithProvider(<SwapStepIcon />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the component with step 2', () => {
|
||||
const { container } = renderWithProvider(<SwapStepIcon stepNumber={2} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ const createProps = (customProps = {}) => {
|
||||
return {
|
||||
swapComplete: false,
|
||||
txHash: 'txHash',
|
||||
tokensReceived: 'tokensReceived',
|
||||
tokensReceived: 'tokens received:',
|
||||
submittingSwap: true,
|
||||
inputValue: 5,
|
||||
maxSlippage: 3,
|
||||
@ -34,4 +34,16 @@ describe('AwaitingSwap', () => {
|
||||
).toMatchSnapshot();
|
||||
expect(getByText('View in activity')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the component with for completed swap', () => {
|
||||
const store = configureMockStore()(createSwapsMockStore());
|
||||
const { getByText } = renderWithProvider(
|
||||
<AwaitingSwap {...createProps({ swapComplete: true })} />,
|
||||
store,
|
||||
);
|
||||
expect(getByText('Transaction complete')).toBeInTheDocument();
|
||||
expect(getByText('tokens received: ETH')).toBeInTheDocument();
|
||||
expect(getByText('View at etherscan.io')).toBeInTheDocument();
|
||||
expect(getByText('Create a new swap')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,80 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FeeCard renders the component with EIP-1559 enabled 1`] = `
|
||||
<div
|
||||
class="fee-card__savings-and-quotes-header"
|
||||
data-testid="fee-card__savings-and-quotes-header"
|
||||
>
|
||||
<div
|
||||
class="fee-card__savings-and-quotes-row"
|
||||
>
|
||||
<p
|
||||
class="fee-card__savings-text"
|
||||
>
|
||||
Using the best quote
|
||||
</p>
|
||||
<div
|
||||
class="fee-card__quote-link-container"
|
||||
>
|
||||
<p
|
||||
class="fee-card__quote-link-text"
|
||||
>
|
||||
6 quotes
|
||||
</p>
|
||||
<div
|
||||
class="fee-card__caret-right"
|
||||
>
|
||||
<i
|
||||
class="fa fa-angle-up"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`FeeCard renders the component with EIP-1559 enabled 2`] = `
|
||||
<div
|
||||
class="fee-card__top-bordered-row"
|
||||
>
|
||||
<div
|
||||
class="fee-card__row-label"
|
||||
>
|
||||
<div
|
||||
class="fee-card__row-header-text"
|
||||
>
|
||||
Quote includes a 0.875% MetaMask fee
|
||||
</div>
|
||||
<div
|
||||
class="info-tooltip"
|
||||
>
|
||||
<div
|
||||
class="fee-card__info-tooltip-container"
|
||||
>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-6"
|
||||
class="info-tooltip__tooltip-container"
|
||||
data-original-title="null"
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 10 10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
|
||||
fill="#b8b8b8"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`FeeCard renders the component with initial props 1`] = `
|
||||
<div
|
||||
class="fee-card__savings-and-quotes-header"
|
||||
|
@ -2,12 +2,26 @@ import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import {
|
||||
MAINNET_CHAIN_ID,
|
||||
BSC_CHAIN_ID,
|
||||
LOCALHOST_CHAIN_ID,
|
||||
POLYGON_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
} from '../../../../shared/constants/network';
|
||||
import TransactionDetail from '../../../components/app/transaction-detail/transaction-detail.component';
|
||||
import TransactionDetailItem from '../../../components/app/transaction-detail-item/transaction-detail-item.component';
|
||||
import GasTiming from '../../../components/app/gas-timing/gas-timing.component';
|
||||
import Typography from '../../../components/ui/typography';
|
||||
import {
|
||||
COLORS,
|
||||
TYPOGRAPHY,
|
||||
FONT_WEIGHT,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
|
||||
const GAS_FEES_LEARN_MORE_URL =
|
||||
'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172';
|
||||
|
||||
export default function FeeCard({
|
||||
primaryFee,
|
||||
@ -23,6 +37,8 @@ export default function FeeCard({
|
||||
onQuotesClick,
|
||||
tokenConversionRate,
|
||||
chainId,
|
||||
EIP1559Network,
|
||||
maxPriorityFeePerGasDecGWEI,
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
|
||||
@ -43,11 +59,18 @@ export default function FeeCard({
|
||||
return t('networkNamePolygon');
|
||||
case LOCALHOST_CHAIN_ID:
|
||||
return t('networkNameTestnet');
|
||||
case RINKEBY_CHAIN_ID:
|
||||
return t('networkNameRinkeby');
|
||||
default:
|
||||
throw new Error('This network is not supported for token swaps');
|
||||
}
|
||||
};
|
||||
|
||||
const gasFeesLearnMoreLinkClickedEvent = useNewMetricEvent({
|
||||
category: 'Swaps',
|
||||
event: 'Clicked "Gas Fees: Learn More" Link',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fee-card">
|
||||
<div
|
||||
@ -72,74 +95,154 @@ export default function FeeCard({
|
||||
</div>
|
||||
</div>
|
||||
<div className="fee-card__main">
|
||||
<div
|
||||
className="fee-card__row-header"
|
||||
data-testid="fee-card__row-header"
|
||||
>
|
||||
<div>
|
||||
<div className="fee-card__row-header-text--bold">
|
||||
{t('swapEstimatedNetworkFee')}
|
||||
</div>
|
||||
<InfoTooltip
|
||||
position="top"
|
||||
contentText={
|
||||
<>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapNetworkFeeSummary', [getTranslatedNetworkName()])}
|
||||
</p>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapEstimatedNetworkFeeSummary', [
|
||||
<span className="fee-card__bold" key="fee-card-bold-1">
|
||||
{t('swapEstimatedNetworkFee')}
|
||||
</span>,
|
||||
])}
|
||||
</p>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapMaxNetworkFeeInfo', [
|
||||
<span className="fee-card__bold" key="fee-card-bold-2">
|
||||
{t('swapMaxNetworkFees')}
|
||||
</span>,
|
||||
])}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
containerClassName="fee-card__info-tooltip-content-container"
|
||||
wrapperClassName="fee-card__row-label fee-card__info-tooltip-container"
|
||||
wide
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="fee-card__row-header-secondary--bold">
|
||||
{primaryFee.fee}
|
||||
</div>
|
||||
{secondaryFee && (
|
||||
<div className="fee-card__row-header-primary--bold">
|
||||
{secondaryFee.fee}
|
||||
{EIP1559Network && (
|
||||
<TransactionDetail
|
||||
rows={[
|
||||
<TransactionDetailItem
|
||||
key="gas-item"
|
||||
detailTitle={
|
||||
<>
|
||||
{t('transactionDetailGasHeading')}
|
||||
<InfoTooltip
|
||||
position="top"
|
||||
contentText={
|
||||
<>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapGasFeesSummary', [
|
||||
getTranslatedNetworkName(),
|
||||
])}
|
||||
</p>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapGasFeesDetails')}
|
||||
</p>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
<a
|
||||
className="fee-card__link"
|
||||
onClick={() => {
|
||||
gasFeesLearnMoreLinkClickedEvent();
|
||||
global.platform.openTab({
|
||||
url: GAS_FEES_LEARN_MORE_URL,
|
||||
});
|
||||
}}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('swapGasFeesLearnMore')}
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
containerClassName="fee-card__info-tooltip-content-container"
|
||||
wrapperClassName="fee-card__row-label fee-card__info-tooltip-container"
|
||||
wide
|
||||
/>
|
||||
</>
|
||||
}
|
||||
detailText={primaryFee.fee}
|
||||
detailTotal={secondaryFee.fee}
|
||||
subTitle={
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={maxPriorityFeePerGasDecGWEI}
|
||||
/>
|
||||
}
|
||||
subText={
|
||||
secondaryFee?.maxFee !== undefined && (
|
||||
<>
|
||||
<Typography
|
||||
tag="span"
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
color={COLORS.UI4}
|
||||
variant={TYPOGRAPHY.H7}
|
||||
>
|
||||
{t('maxFee')}
|
||||
</Typography>
|
||||
{`: ${secondaryFee.maxFee}`}
|
||||
<span
|
||||
className="fee-card__edit-link"
|
||||
onClick={() => onFeeCardMaxRowClick()}
|
||||
>
|
||||
{t('edit')}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{!EIP1559Network && (
|
||||
<div
|
||||
className="fee-card__row-header"
|
||||
data-testid="fee-card__row-header"
|
||||
>
|
||||
<div>
|
||||
<div className="fee-card__row-header-text--bold">
|
||||
{t('swapEstimatedNetworkFee')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="fee-card__row-header"
|
||||
onClick={() => onFeeCardMaxRowClick()}
|
||||
>
|
||||
<div>
|
||||
<div className="fee-card__row-header-text">
|
||||
{t('swapMaxNetworkFees')}
|
||||
<InfoTooltip
|
||||
position="top"
|
||||
contentText={
|
||||
<>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapNetworkFeeSummary', [getTranslatedNetworkName()])}
|
||||
</p>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapEstimatedNetworkFeeSummary', [
|
||||
<span className="fee-card__bold" key="fee-card-bold-1">
|
||||
{t('swapEstimatedNetworkFee')}
|
||||
</span>,
|
||||
])}
|
||||
</p>
|
||||
<p className="fee-card__info-tooltip-paragraph">
|
||||
{t('swapMaxNetworkFeeInfo', [
|
||||
<span className="fee-card__bold" key="fee-card-bold-2">
|
||||
{t('swapMaxNetworkFees')}
|
||||
</span>,
|
||||
])}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
containerClassName="fee-card__info-tooltip-content-container"
|
||||
wrapperClassName="fee-card__row-label fee-card__info-tooltip-container"
|
||||
wide
|
||||
/>
|
||||
</div>
|
||||
<div className="fee-card__link">{t('edit')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="fee-card__row-header-secondary">
|
||||
{primaryFee.maxFee}
|
||||
</div>
|
||||
{secondaryFee?.maxFee !== undefined && (
|
||||
<div className="fee-card__row-header-primary">
|
||||
{secondaryFee.maxFee}
|
||||
<div>
|
||||
<div className="fee-card__row-header-secondary--bold">
|
||||
{primaryFee.fee}
|
||||
</div>
|
||||
)}
|
||||
{secondaryFee && (
|
||||
<div className="fee-card__row-header-primary--bold">
|
||||
{secondaryFee.fee}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!EIP1559Network && (
|
||||
<div
|
||||
className="fee-card__row-header"
|
||||
onClick={() => onFeeCardMaxRowClick()}
|
||||
>
|
||||
<div>
|
||||
<div className="fee-card__row-header-text">
|
||||
{t('swapMaxNetworkFees')}
|
||||
</div>
|
||||
<div className="fee-card__link">{t('edit')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="fee-card__row-header-secondary">
|
||||
{primaryFee.maxFee}
|
||||
</div>
|
||||
{secondaryFee?.maxFee !== undefined && (
|
||||
<div className="fee-card__row-header-primary">
|
||||
{secondaryFee.maxFee}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hideTokenApprovalRow && (
|
||||
<div className="fee-card__row-header">
|
||||
<div className="fee-card__row-label">
|
||||
@ -199,4 +302,6 @@ FeeCard.propTypes = {
|
||||
numberOfQuotes: PropTypes.number.isRequired,
|
||||
tokenConversionRate: PropTypes.number,
|
||||
chainId: PropTypes.string.isRequired,
|
||||
EIP1559Network: PropTypes.bool.isRequired,
|
||||
maxPriorityFeePerGasDecGWEI: PropTypes.string,
|
||||
};
|
||||
|
@ -1,9 +1,30 @@
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { renderWithProvider } from '../../../../test/jest';
|
||||
import {
|
||||
renderWithProvider,
|
||||
createSwapsMockStore,
|
||||
MOCKS,
|
||||
} from '../../../../test/jest';
|
||||
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
|
||||
import FeeCard from '.';
|
||||
|
||||
const middleware = [thunk];
|
||||
|
||||
jest.mock('../../../hooks/useGasFeeEstimates', () => {
|
||||
return {
|
||||
useGasFeeEstimates: () => {
|
||||
return {
|
||||
gasFeeEstimates: MOCKS.createGasFeeEstimatesForFeeMarket(),
|
||||
gasEstimateType: 'fee-market',
|
||||
estimatedGasFeeTimeBounds: undefined,
|
||||
isGasEstimatesLoading: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const createProps = (customProps = {}) => {
|
||||
return {
|
||||
primaryFee: {
|
||||
@ -32,6 +53,7 @@ const createProps = (customProps = {}) => {
|
||||
onQuotesClick: jest.fn(),
|
||||
tokenConversionRate: 0.015,
|
||||
chainId: MAINNET_CHAIN_ID,
|
||||
EIP1559Network: false,
|
||||
...customProps,
|
||||
};
|
||||
};
|
||||
@ -58,4 +80,29 @@ describe('FeeCard', () => {
|
||||
document.querySelector('.fee-card__top-bordered-row'),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the component with EIP-1559 enabled', () => {
|
||||
const store = configureMockStore(middleware)(createSwapsMockStore());
|
||||
const props = createProps({
|
||||
EIP1559Network: true,
|
||||
maxPriorityFeePerGasDecGWEI: '3',
|
||||
});
|
||||
const { getByText } = renderWithProvider(<FeeCard {...props} />, store);
|
||||
expect(getByText('Using the best quote')).toBeInTheDocument();
|
||||
expect(getByText('6 quotes')).toBeInTheDocument();
|
||||
expect(getByText('Estimated gas fee')).toBeInTheDocument();
|
||||
expect(getByText('Maybe in 5 minutes')).toBeInTheDocument();
|
||||
expect(getByText(props.primaryFee.fee)).toBeInTheDocument();
|
||||
expect(getByText(props.secondaryFee.fee)).toBeInTheDocument();
|
||||
expect(getByText(`: ${props.secondaryFee.maxFee}`)).toBeInTheDocument();
|
||||
expect(
|
||||
getByText('Quote includes a 0.875% MetaMask fee'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
document.querySelector('.fee-card__savings-and-quotes-header'),
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
document.querySelector('.fee-card__top-bordered-row'),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -21,8 +21,8 @@
|
||||
border-top-right-radius: 8px;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom: 0;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
&__savings-text {
|
||||
@ -141,11 +141,18 @@
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&__link {
|
||||
&__link,
|
||||
&__link:hover {
|
||||
color: $Blue-500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__edit-link {
|
||||
color: $Blue-500;
|
||||
cursor: pointer;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
&__total-box {
|
||||
border-top: 1px solid $Grey-100;
|
||||
padding: 12px 16px 16px 16px;
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
getUseNewSwapsApi,
|
||||
getFromToken,
|
||||
} from '../../ducks/swaps/swaps';
|
||||
import { isEIP1559Network } from '../../ducks/metamask/metamask';
|
||||
import {
|
||||
AWAITING_SIGNATURES_ROUTE,
|
||||
AWAITING_SWAP_ROUTE,
|
||||
@ -63,7 +64,7 @@ import {
|
||||
} from '../../store/actions';
|
||||
import { currentNetworkTxListSelector } from '../../selectors';
|
||||
import { useNewMetricEvent } from '../../hooks/useMetricEvent';
|
||||
|
||||
import { useGasFeeEstimates } from '../../hooks/useGasFeeEstimates';
|
||||
import FeatureToggledRoute from '../../helpers/higher-order-components/feature-toggled-route';
|
||||
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
|
||||
import {
|
||||
@ -111,8 +112,15 @@ export default function Swap() {
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const isSwapsChain = useSelector(getIsSwapsChain);
|
||||
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
|
||||
const EIP1559Network = useSelector(isEIP1559Network);
|
||||
const fromToken = useSelector(getFromToken);
|
||||
|
||||
if (EIP1559Network) {
|
||||
// This will pre-load gas fees before going to the View Quote page.
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useGasFeeEstimates();
|
||||
}
|
||||
|
||||
const {
|
||||
balance: ethBalance,
|
||||
address: selectedAccountAddress,
|
||||
@ -187,12 +195,14 @@ export default function Swap() {
|
||||
dispatch(setAggregatorMetadata(newAggregatorMetadata));
|
||||
},
|
||||
);
|
||||
dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
|
||||
if (!EIP1559Network) {
|
||||
dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
|
||||
}
|
||||
return () => {
|
||||
dispatch(prepareToLeaveSwaps());
|
||||
};
|
||||
}
|
||||
}, [dispatch, chainId, isFeatureFlagLoaded, useNewSwapsApi]);
|
||||
}, [dispatch, chainId, isFeatureFlagLoaded, useNewSwapsApi, EIP1559Network]);
|
||||
|
||||
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
||||
const hardwareWalletType = useSelector(getHardwareWalletType);
|
||||
|
@ -21,6 +21,8 @@ setBackgroundConnection({
|
||||
setSwapsLiveness: jest.fn(() => true),
|
||||
setSwapsTokens: jest.fn(),
|
||||
setSwapsTxGasPrice: jest.fn(),
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
||||
});
|
||||
|
||||
describe('Swap', () => {
|
||||
|
@ -59,10 +59,15 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const customGasTotal = calcGasTotal(customGasLimit, customGasPrice);
|
||||
|
||||
const swapsGasPriceEstimates = getSwapGasPriceEstimateData(state);
|
||||
const gasEstimates = getSwapGasPriceEstimateData(state);
|
||||
const gasEstimatesInNewFormat = {
|
||||
low: gasEstimates.safeLow,
|
||||
medium: gasEstimates.average,
|
||||
high: gasEstimates.fast,
|
||||
};
|
||||
|
||||
const { averageEstimateData, fastEstimateData } = getRenderableGasButtonData(
|
||||
swapsGasPriceEstimates,
|
||||
gasEstimatesInNewFormat,
|
||||
customGasLimit,
|
||||
true,
|
||||
conversionRate,
|
||||
|
@ -9,7 +9,9 @@ import {
|
||||
ETHEREUM,
|
||||
POLYGON,
|
||||
BSC,
|
||||
RINKEBY,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction';
|
||||
import {
|
||||
isSwapsDefaultTokenAddress,
|
||||
isSwapsDefaultTokenSymbol,
|
||||
@ -21,6 +23,7 @@ import {
|
||||
BSC_CHAIN_ID,
|
||||
POLYGON_CHAIN_ID,
|
||||
LOCALHOST_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
} from '../../../shared/constants/network';
|
||||
import { SECOND } from '../../../shared/constants/time';
|
||||
import {
|
||||
@ -656,6 +659,8 @@ export function getSwapsTokensReceivedFromTxMeta(
|
||||
chainId,
|
||||
) {
|
||||
const txReceipt = txMeta?.txReceipt;
|
||||
const EIP1559Network =
|
||||
txMeta?.txReceipt?.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
|
||||
if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) {
|
||||
if (
|
||||
!txReceipt ||
|
||||
@ -670,11 +675,16 @@ export function getSwapsTokensReceivedFromTxMeta(
|
||||
if (approvalTxMeta && approvalTxMeta.txReceipt) {
|
||||
approvalTxGasCost = calcGasTotal(
|
||||
approvalTxMeta.txReceipt.gasUsed,
|
||||
approvalTxMeta.txParams.gasPrice,
|
||||
EIP1559Network
|
||||
? approvalTxMeta.txReceipt.effectiveGasPrice // Base fee + priority fee.
|
||||
: approvalTxMeta.txParams.gasPrice,
|
||||
);
|
||||
}
|
||||
|
||||
const gasCost = calcGasTotal(txReceipt.gasUsed, txMeta.txParams.gasPrice);
|
||||
const gasCost = calcGasTotal(
|
||||
txReceipt.gasUsed,
|
||||
EIP1559Network ? txReceipt.effectiveGasPrice : txMeta.txParams.gasPrice,
|
||||
);
|
||||
const totalGasCost = new BigNumber(gasCost, 16)
|
||||
.plus(approvalTxGasCost, 16)
|
||||
.toString(16);
|
||||
@ -786,6 +796,8 @@ export const getNetworkNameByChainId = (chainId) => {
|
||||
return BSC;
|
||||
case POLYGON_CHAIN_ID:
|
||||
return POLYGON;
|
||||
case RINKEBY_CHAIN_ID:
|
||||
return RINKEBY;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@ -799,8 +811,8 @@ export const getNetworkNameByChainId = (chainId) => {
|
||||
*/
|
||||
export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
|
||||
const networkName = getNetworkNameByChainId(chainId);
|
||||
// Use old APIs for testnet.
|
||||
if (chainId === LOCALHOST_CHAIN_ID) {
|
||||
// Use old APIs for testnet and Rinkeby.
|
||||
if ([LOCALHOST_CHAIN_ID, RINKEBY_CHAIN_ID].includes(chainId)) {
|
||||
return {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
POLYGON_CHAIN_ID,
|
||||
LOCALHOST_CHAIN_ID,
|
||||
RINKEBY_CHAIN_ID,
|
||||
KOVAN_CHAIN_ID,
|
||||
} from '../../../shared/constants/network';
|
||||
import {
|
||||
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
ETHEREUM,
|
||||
POLYGON,
|
||||
BSC,
|
||||
RINKEBY,
|
||||
} from '../../../shared/constants/swaps';
|
||||
import {
|
||||
TOKENS,
|
||||
@ -394,8 +396,12 @@ describe('Swaps Util', () => {
|
||||
expect(getNetworkNameByChainId(POLYGON_CHAIN_ID)).toBe(POLYGON);
|
||||
});
|
||||
|
||||
it('returns "rinkeby" for Rinkeby chain ID', () => {
|
||||
expect(getNetworkNameByChainId(RINKEBY_CHAIN_ID)).toBe(RINKEBY);
|
||||
});
|
||||
|
||||
it('returns an empty string for an unsupported network', () => {
|
||||
expect(getNetworkNameByChainId(RINKEBY_CHAIN_ID)).toBe('');
|
||||
expect(getNetworkNameByChainId(KOVAN_CHAIN_ID)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
@ -413,6 +419,19 @@ describe('Swaps Util', () => {
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns info that Swaps are enabled and cannot use API v2 for Rinkeby chain ID', () => {
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: true,
|
||||
useNewSwapsApi: false,
|
||||
};
|
||||
expect(
|
||||
getSwapsLivenessForNetwork(
|
||||
MOCKS.createFeatureFlagsResponse(),
|
||||
RINKEBY_CHAIN_ID,
|
||||
),
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
||||
it('returns info that Swaps are disabled and cannot use API v2 if network name is not found', () => {
|
||||
const expectedSwapsLiveness = {
|
||||
swapsFeatureIsLive: false,
|
||||
@ -421,7 +440,7 @@ describe('Swaps Util', () => {
|
||||
expect(
|
||||
getSwapsLivenessForNetwork(
|
||||
MOCKS.createFeatureFlagsResponse(),
|
||||
RINKEBY_CHAIN_ID,
|
||||
KOVAN_CHAIN_ID,
|
||||
),
|
||||
).toMatchObject(expectedSwapsLiveness);
|
||||
});
|
||||
|
@ -1,5 +1,106 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ViewQuote renders the component with EIP-1559 enabled 1`] = `
|
||||
<div
|
||||
class="main-quote-summary__source-row"
|
||||
data-testid="main-quote-summary__source-row"
|
||||
>
|
||||
<span
|
||||
class="main-quote-summary__source-row-value"
|
||||
title="10"
|
||||
>
|
||||
10
|
||||
</span>
|
||||
<img
|
||||
alt=""
|
||||
class="url-icon main-quote-summary__icon"
|
||||
src="https://foo.bar/logo.png"
|
||||
/>
|
||||
<span
|
||||
class="main-quote-summary__source-row-symbol"
|
||||
title="DAI"
|
||||
>
|
||||
DAI
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ViewQuote renders the component with EIP-1559 enabled 2`] = `
|
||||
<div
|
||||
class="main-quote-summary__exchange-rate-container"
|
||||
data-testid="main-quote-summary__exchange-rate-container"
|
||||
>
|
||||
<div
|
||||
class="exchange-rate-display main-quote-summary__exchange-rate-display"
|
||||
>
|
||||
<span>
|
||||
1
|
||||
</span>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
DAI
|
||||
</span>
|
||||
<span>
|
||||
=
|
||||
</span>
|
||||
<span>
|
||||
2.2
|
||||
</span>
|
||||
<span
|
||||
class=""
|
||||
>
|
||||
USDC
|
||||
</span>
|
||||
<div
|
||||
class="exchange-rate-display__switch-arrows"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="13"
|
||||
viewBox="0 0 13 13"
|
||||
width="13"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.15294 4.38514H9.99223L8.50853 5.86884C8.30421 6.07297 8.30421 6.40418 8.50853 6.60869C8.61069 6.71085 8.74443 6.76203 8.87836 6.76203C9.01229 6.76203 9.14603 6.71085 9.24819 6.60869L11.6249 4.23219C11.649 4.20803 11.6707 4.1814 11.6899 4.15305C11.6947 4.14563 11.6981 4.13726 11.7025 4.12965C11.7154 4.10815 11.7282 4.08646 11.7381 4.06325C11.7426 4.05222 11.7447 4.04043 11.7487 4.0292C11.7558 4.00827 11.7636 3.98754 11.7681 3.96547C11.775 3.93161 11.7786 3.89717 11.7786 3.86198C11.7786 3.82678 11.775 3.79235 11.7681 3.75849C11.7638 3.73642 11.756 3.71568 11.7487 3.69476C11.7447 3.68353 11.7428 3.67174 11.7381 3.6607C11.7282 3.63749 11.7156 3.616 11.7025 3.59431C11.6981 3.5867 11.6947 3.57833 11.6899 3.57091C11.6707 3.54256 11.649 3.51593 11.6249 3.49177L9.24876 1.11564C9.04444 0.911322 8.71342 0.911322 8.50891 1.11564C8.30459 1.31977 8.30459 1.65098 8.50891 1.85549L9.99223 3.339H4.15294C2.22978 3.339 0.665039 4.90374 0.665039 6.8269C0.665039 7.11588 0.899227 7.35007 1.1882 7.35007C1.47718 7.35007 1.71137 7.11588 1.71137 6.8269C1.71137 5.48037 2.80659 4.38514 4.15294 4.38514ZM12.2066 6.57445C11.9177 6.57445 11.6835 6.80864 11.6835 7.09762C11.6835 8.44396 10.5883 9.53919 9.24191 9.53919H3.40262L4.88632 8.05549C5.09064 7.85136 5.09064 7.52014 4.88632 7.31563C4.682 7.11112 4.35098 7.11131 4.14647 7.31563L1.76977 9.69233C1.74561 9.71649 1.72393 9.74312 1.70471 9.77147C1.70015 9.7787 1.69691 9.78669 1.69273 9.79392C1.6796 9.81561 1.66647 9.83748 1.65677 9.86126C1.6524 9.87211 1.6503 9.88371 1.64631 9.89475C1.63927 9.91586 1.63128 9.93679 1.62671 9.95905C1.61986 9.99291 1.61625 10.0273 1.61625 10.0625C1.61625 10.0977 1.61986 10.1322 1.62671 10.166C1.63109 10.1883 1.63908 10.2092 1.64631 10.2303C1.6503 10.2414 1.65221 10.253 1.65677 10.2638C1.66666 10.2874 1.6796 10.3093 1.69273 10.3312C1.69691 10.3384 1.70015 10.3464 1.70471 10.3536C1.72393 10.382 1.74561 10.4086 1.76977 10.4328L4.14609 12.8091C4.24825 12.9112 4.38199 12.9624 4.51592 12.9624C4.64985 12.9624 4.78359 12.9112 4.88575 12.8091C5.09007 12.6049 5.09007 12.2737 4.88575 12.0692L3.40243 10.5857H9.24172C11.1649 10.5857 12.7296 9.02097 12.7296 7.09781C12.7298 6.80864 12.4956 6.57445 12.2066 6.57445Z"
|
||||
fill="#037DD6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ViewQuote renders the component with EIP-1559 enabled 3`] = `
|
||||
<div
|
||||
class="fee-card__savings-and-quotes-header"
|
||||
data-testid="fee-card__savings-and-quotes-header"
|
||||
>
|
||||
<div
|
||||
class="fee-card__savings-and-quotes-row"
|
||||
>
|
||||
|
||||
<div
|
||||
class="fee-card__quote-link-container"
|
||||
>
|
||||
<p
|
||||
class="fee-card__quote-link-text"
|
||||
>
|
||||
3 quotes
|
||||
</p>
|
||||
<div
|
||||
class="fee-card__caret-right"
|
||||
>
|
||||
<i
|
||||
class="fa fa-angle-up"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ViewQuote renders the component with initial props 1`] = `
|
||||
<div
|
||||
class="main-quote-summary__source-row"
|
||||
|
@ -10,8 +10,10 @@ import { useEthFiatAmount } from '../../../hooks/useEthFiatAmount';
|
||||
import { useEqualityCheck } from '../../../hooks/useEqualityCheck';
|
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import { usePrevious } from '../../../hooks/usePrevious';
|
||||
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics.new';
|
||||
import FeeCard from '../fee-card';
|
||||
import EditGasPopover from '../../../components/app/edit-gas-popover/edit-gas-popover.component';
|
||||
import {
|
||||
FALLBACK_GAS_MULTIPLIER,
|
||||
getQuotes,
|
||||
@ -21,7 +23,9 @@ import {
|
||||
setBalanceError,
|
||||
getQuotesLastFetched,
|
||||
getBalanceError,
|
||||
getCustomSwapsGas,
|
||||
getCustomSwapsGas, // Gas limit.
|
||||
getCustomMaxFeePerGas,
|
||||
getCustomMaxPriorityFeePerGas,
|
||||
getDestinationTokenInfo,
|
||||
getUsedSwapsGasPrice,
|
||||
getTopQuote,
|
||||
@ -41,7 +45,11 @@ import {
|
||||
isHardwareWallet,
|
||||
getHardwareWalletType,
|
||||
} from '../../../selectors';
|
||||
import { getNativeCurrency, getTokens } from '../../../ducks/metamask/metamask';
|
||||
import {
|
||||
getNativeCurrency,
|
||||
getTokens,
|
||||
isEIP1559Network,
|
||||
} from '../../../ducks/metamask/metamask';
|
||||
|
||||
import { toPrecisionWithoutTrailingZeros } from '../../../helpers/utils/util';
|
||||
|
||||
@ -68,6 +76,9 @@ import {
|
||||
decimalToHex,
|
||||
hexToDecimal,
|
||||
getValueFromWeiHex,
|
||||
decGWEIToHexWEI,
|
||||
hexWEIToDecGWEI,
|
||||
addHexes,
|
||||
} from '../../../helpers/utils/conversions.util';
|
||||
import MainQuoteSummary from '../main-quote-summary';
|
||||
import { calcGasTotal } from '../../send/send.utils';
|
||||
@ -79,6 +90,7 @@ import {
|
||||
} from '../swaps.util';
|
||||
import { useTokenTracker } from '../../../hooks/useTokenTracker';
|
||||
import { QUOTES_EXPIRED_ERROR } from '../../../../shared/constants/swaps';
|
||||
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
|
||||
import CountdownTimer from '../countdown-timer';
|
||||
import SwapsFooter from '../swaps-footer';
|
||||
import ViewQuotePriceDifference from './view-quote-price-difference';
|
||||
@ -94,6 +106,7 @@ export default function ViewQuote() {
|
||||
const [selectQuotePopoverShown, setSelectQuotePopoverShown] = useState(false);
|
||||
const [warningHidden, setWarningHidden] = useState(false);
|
||||
const [originalApproveAmount, setOriginalApproveAmount] = useState(null);
|
||||
const [showEditGasPopover, setShowEditGasPopover] = useState(false);
|
||||
|
||||
const [
|
||||
acknowledgedPriceDifference,
|
||||
@ -116,12 +129,15 @@ export default function ViewQuote() {
|
||||
// Select necessary data
|
||||
const gasPrice = useSelector(getUsedSwapsGasPrice);
|
||||
const customMaxGas = useSelector(getCustomSwapsGas);
|
||||
const customMaxFeePerGas = useSelector(getCustomMaxFeePerGas);
|
||||
const customMaxPriorityFeePerGas = useSelector(getCustomMaxPriorityFeePerGas);
|
||||
const tokenConversionRates = useSelector(getTokenExchangeRates);
|
||||
const memoizedTokenConversionRates = useEqualityCheck(tokenConversionRates);
|
||||
const { balance: ethBalance } = useSelector(getSelectedAccount);
|
||||
const conversionRate = useSelector(conversionRateSelector);
|
||||
const currentCurrency = useSelector(getCurrentCurrency);
|
||||
const swapsTokens = useSelector(getTokens);
|
||||
const EIP1559Network = useSelector(isEIP1559Network);
|
||||
const balanceError = useSelector(getBalanceError);
|
||||
const fetchParams = useSelector(getFetchParams);
|
||||
const approveTxParams = useSelector(getApproveTxParams);
|
||||
@ -134,6 +150,13 @@ export default function ViewQuote() {
|
||||
const chainId = useSelector(getCurrentChainId);
|
||||
const nativeCurrencySymbol = useSelector(getNativeCurrency);
|
||||
|
||||
let gasFeeInputs;
|
||||
if (EIP1559Network) {
|
||||
// For Swaps we want to get 'high' estimations by default.
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
gasFeeInputs = useGasFeeInputs('high');
|
||||
}
|
||||
|
||||
const { isBestQuote } = usedQuote;
|
||||
|
||||
const fetchParamsSourceToken = fetchParams?.sourceToken;
|
||||
@ -154,7 +177,30 @@ export default function ViewQuote() {
|
||||
: `0x${decimalToHex(usedQuote?.maxGas || 0)}`;
|
||||
const maxGasLimit = customMaxGas || nonCustomMaxGasLimit;
|
||||
|
||||
const gasTotalInWeiHex = calcGasTotal(maxGasLimit, gasPrice);
|
||||
let maxFeePerGas;
|
||||
let maxPriorityFeePerGas;
|
||||
let baseAndPriorityFeePerGas;
|
||||
|
||||
if (EIP1559Network) {
|
||||
const {
|
||||
maxFeePerGas: suggestedMaxFeePerGas,
|
||||
maxPriorityFeePerGas: suggestedMaxPriorityFeePerGas,
|
||||
gasFeeEstimates: { estimatedBaseFee = '0' },
|
||||
} = gasFeeInputs;
|
||||
maxFeePerGas = customMaxFeePerGas || decGWEIToHexWEI(suggestedMaxFeePerGas);
|
||||
maxPriorityFeePerGas =
|
||||
customMaxPriorityFeePerGas ||
|
||||
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas);
|
||||
baseAndPriorityFeePerGas = addHexes(
|
||||
decGWEIToHexWEI(estimatedBaseFee),
|
||||
maxPriorityFeePerGas,
|
||||
);
|
||||
}
|
||||
|
||||
const gasTotalInWeiHex = calcGasTotal(
|
||||
maxGasLimit,
|
||||
EIP1559Network ? maxFeePerGas : gasPrice,
|
||||
);
|
||||
|
||||
const { tokensWithBalances } = useTokenTracker(swapsTokens, true);
|
||||
const balanceToken =
|
||||
@ -185,7 +231,7 @@ export default function ViewQuote() {
|
||||
const renderablePopoverData = useMemo(() => {
|
||||
return quotesToRenderableData(
|
||||
quotes,
|
||||
gasPrice,
|
||||
EIP1559Network ? baseAndPriorityFeePerGas : gasPrice,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
approveGas,
|
||||
@ -195,6 +241,8 @@ export default function ViewQuote() {
|
||||
}, [
|
||||
quotes,
|
||||
gasPrice,
|
||||
baseAndPriorityFeePerGas,
|
||||
EIP1559Network,
|
||||
conversionRate,
|
||||
currentCurrency,
|
||||
approveGas,
|
||||
@ -221,7 +269,7 @@ export default function ViewQuote() {
|
||||
const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote({
|
||||
tradeGas: usedGasLimit,
|
||||
approveGas,
|
||||
gasPrice,
|
||||
gasPrice: EIP1559Network ? baseAndPriorityFeePerGas : gasPrice,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
tradeValue,
|
||||
@ -238,7 +286,7 @@ export default function ViewQuote() {
|
||||
} = getRenderableNetworkFeesForQuote({
|
||||
tradeGas: maxGasLimit,
|
||||
approveGas,
|
||||
gasPrice,
|
||||
gasPrice: EIP1559Network ? maxFeePerGas : gasPrice,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
tradeValue,
|
||||
@ -455,7 +503,10 @@ export default function ViewQuote() {
|
||||
};
|
||||
|
||||
const nonGasFeeIsPositive = new BigNumber(nonGasFee, 16).gt(0);
|
||||
const approveGasTotal = calcGasTotal(approveGas || '0x0', gasPrice);
|
||||
const approveGasTotal = calcGasTotal(
|
||||
approveGas || '0x0',
|
||||
EIP1559Network ? baseAndPriorityFeePerGas : gasPrice,
|
||||
);
|
||||
const extraNetworkFeeTotalInHexWEI = new BigNumber(nonGasFee, 16)
|
||||
.plus(approveGasTotal, 16)
|
||||
.toString(16);
|
||||
@ -474,26 +525,29 @@ export default function ViewQuote() {
|
||||
extraInfoRowLabel = t('aggregatorFeeCost');
|
||||
}
|
||||
|
||||
const onFeeCardMaxRowClick = () =>
|
||||
dispatch(
|
||||
showModal({
|
||||
name: 'CUSTOMIZE_METASWAP_GAS',
|
||||
value: tradeValue,
|
||||
customGasLimitMessage: approveGas
|
||||
? t('extraApprovalGas', [hexToDecimal(approveGas)])
|
||||
: '',
|
||||
customTotalSupplement: approveGasTotal,
|
||||
extraInfoRow: extraInfoRowLabel
|
||||
? {
|
||||
label: extraInfoRowLabel,
|
||||
value: `${extraNetworkFeeTotalInEth} ${nativeCurrencySymbol}`,
|
||||
}
|
||||
: null,
|
||||
initialGasPrice: gasPrice,
|
||||
initialGasLimit: maxGasLimit,
|
||||
minimumGasLimit: new BigNumber(nonCustomMaxGasLimit, 16).toNumber(),
|
||||
}),
|
||||
);
|
||||
const onFeeCardMaxRowClick = () => {
|
||||
EIP1559Network
|
||||
? setShowEditGasPopover(true)
|
||||
: dispatch(
|
||||
showModal({
|
||||
name: 'CUSTOMIZE_METASWAP_GAS',
|
||||
value: tradeValue,
|
||||
customGasLimitMessage: approveGas
|
||||
? t('extraApprovalGas', [hexToDecimal(approveGas)])
|
||||
: '',
|
||||
customTotalSupplement: approveGasTotal,
|
||||
extraInfoRow: extraInfoRowLabel
|
||||
? {
|
||||
label: extraInfoRowLabel,
|
||||
value: `${extraNetworkFeeTotalInEth} ${nativeCurrencySymbol}`,
|
||||
}
|
||||
: null,
|
||||
initialGasPrice: gasPrice,
|
||||
initialGasLimit: maxGasLimit,
|
||||
minimumGasLimit: new BigNumber(nonCustomMaxGasLimit, 16).toNumber(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const tokenApprovalTextComponent = (
|
||||
<span key="swaps-view-quote-approve-symbol-1" className="view-quote__bold">
|
||||
@ -590,6 +644,10 @@ export default function ViewQuote() {
|
||||
const isShowingWarning =
|
||||
showInsufficientWarning || shouldShowPriceDifferenceWarning;
|
||||
|
||||
const onCloseEditGasPopover = () => {
|
||||
setShowEditGasPopover(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="view-quote">
|
||||
<div
|
||||
@ -607,6 +665,24 @@ export default function ViewQuote() {
|
||||
onQuoteDetailsIsOpened={quoteDetailsOpened}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showEditGasPopover && EIP1559Network && (
|
||||
<EditGasPopover
|
||||
transaction={{
|
||||
txParams: {
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
gas: maxGasLimit,
|
||||
},
|
||||
}}
|
||||
minimumGasLimit={usedGasLimit}
|
||||
defaultEstimateToUse="high"
|
||||
mode={EDIT_GAS_MODES.SWAPS}
|
||||
confirmButtonText={t('submit')}
|
||||
onClose={onCloseEditGasPopover}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={classnames('view-quote__warning-wrapper', {
|
||||
'view-quote__warning-wrapper--thin': !isShowingWarning,
|
||||
@ -676,6 +752,8 @@ export default function ViewQuote() {
|
||||
: memoizedTokenConversionRates[destinationToken.address]
|
||||
}
|
||||
chainId={chainId}
|
||||
EIP1559Network={EIP1559Network}
|
||||
maxPriorityFeePerGasDecGWEI={hexWEIToDecGWEI(maxPriorityFeePerGas)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -697,8 +775,8 @@ export default function ViewQuote() {
|
||||
balanceError ||
|
||||
tokenBalanceUnavailable ||
|
||||
disableSubmissionDueToPriceWarning ||
|
||||
gasPrice === null ||
|
||||
gasPrice === undefined
|
||||
(EIP1559Network && baseAndPriorityFeePerGas === undefined) ||
|
||||
(!EIP1559Network && (gasPrice === null || gasPrice === undefined))
|
||||
}
|
||||
className={isShowingWarning && 'view-quote__thin-swaps-footer'}
|
||||
showTopBorder
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
renderWithProvider,
|
||||
createSwapsMockStore,
|
||||
setBackgroundConnection,
|
||||
MOCKS,
|
||||
} from '../../../../test/jest';
|
||||
import ViewQuote from '.';
|
||||
|
||||
@ -13,6 +14,18 @@ jest.mock('../../../components/ui/info-tooltip/info-tooltip-icon', () => () =>
|
||||
'<InfoTooltipIcon />',
|
||||
);
|
||||
|
||||
jest.mock('../../../hooks/useGasFeeInputs', () => {
|
||||
return {
|
||||
useGasFeeInputs: () => {
|
||||
return {
|
||||
maxFeePerGas: 16,
|
||||
maxPriorityFeePerGas: 3,
|
||||
gasFeeEstimates: MOCKS.createGasFeeEstimatesForFeeMarket(),
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const middleware = [thunk];
|
||||
const createProps = (customProps = {}) => {
|
||||
return {
|
||||
@ -31,6 +44,8 @@ setBackgroundConnection({
|
||||
resetPostFetchState: jest.fn(),
|
||||
safeRefetchQuotes: jest.fn(),
|
||||
setSwapsErrorKey: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
||||
updateTransaction: jest.fn(),
|
||||
});
|
||||
|
||||
describe('ViewQuote', () => {
|
||||
@ -53,4 +68,32 @@ describe('ViewQuote', () => {
|
||||
expect(getByText('Back')).toBeInTheDocument();
|
||||
expect(getByText('Swap')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the component with EIP-1559 enabled', () => {
|
||||
const state = createSwapsMockStore();
|
||||
state.metamask.networkDetails = {
|
||||
EIPS: {
|
||||
1559: true,
|
||||
},
|
||||
};
|
||||
const store = configureMockStore(middleware)(state);
|
||||
const props = createProps();
|
||||
const { getByText, getByTestId } = renderWithProvider(
|
||||
<ViewQuote {...props} />,
|
||||
store,
|
||||
);
|
||||
expect(getByText('New quotes in')).toBeInTheDocument();
|
||||
expect(getByTestId('main-quote-summary__source-row')).toMatchSnapshot();
|
||||
expect(
|
||||
getByTestId('main-quote-summary__exchange-rate-container'),
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
getByTestId('fee-card__savings-and-quotes-header'),
|
||||
).toMatchSnapshot();
|
||||
expect(getByText('Estimated gas fee')).toBeInTheDocument();
|
||||
expect(getByText('0.01044 ETH')).toBeInTheDocument();
|
||||
expect(getByText('Max fee')).toBeInTheDocument();
|
||||
expect(getByText('Back')).toBeInTheDocument();
|
||||
expect(getByText('Swap')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -2199,6 +2199,23 @@ export function setSwapsTxGasLimit(gasLimit) {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateCustomSwapsEIP1559GasParams({
|
||||
gasLimit,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
}) {
|
||||
return async (dispatch) => {
|
||||
await Promise.all([
|
||||
promisifiedBackground.setSwapsTxGasLimit(gasLimit),
|
||||
promisifiedBackground.setSwapsTxMaxFeePerGas(maxFeePerGas),
|
||||
promisifiedBackground.setSwapsTxMaxFeePriorityPerGas(
|
||||
maxPriorityFeePerGas,
|
||||
),
|
||||
]);
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function customSwapsGasParamsUpdated(gasLimit, gasPrice) {
|
||||
return async (dispatch) => {
|
||||
await promisifiedBackground.setSwapsTxGasPrice(gasPrice);
|
||||
|
Loading…
Reference in New Issue
Block a user