diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 74c351810..657bf2004 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2131,6 +2131,9 @@ "swapDecentralizedExchange": { "message": "Decentralized exchange" }, + "swapDirectContract": { + "message": "Direct contract" + }, "swapEditLimit": { "message": "Edit limit" }, diff --git a/shared/constants/swaps.js b/shared/constants/swaps.js index b838228e7..5da0d08d8 100644 --- a/shared/constants/swaps.js +++ b/shared/constants/swaps.js @@ -75,8 +75,14 @@ const BSC_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31'; // It's the same as we use for BSC. const POLYGON_CONTRACT_ADDRESS = '0x1a1ec25dc08e98e5e93f1104b5e5cdd298707d31'; -export const ETH_WETH_CONTRACT_ADDRESS = +export const WETH_CONTRACT_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; +export const WETH_RINKEBY_CONTRACT_ADDRESS = + '0xc778417e063141139fce010982780140aa0cd5ab'; +export const WBNB_CONTRACT_ADDRESS = + '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c'; +export const WMATIC_CONTRACT_ADDRESS = + '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270'; const METASWAP_ETH_API_HOST = 'https://api.metaswap.codefi.network'; @@ -121,6 +127,37 @@ export const SWAPS_CHAINID_CONTRACT_ADDRESS_MAP = { [RINKEBY_CHAIN_ID]: TESTNET_CONTRACT_ADDRESS, }; +export const SWAPS_WRAPPED_TOKENS_ADDRESSES = { + [MAINNET_CHAIN_ID]: WETH_CONTRACT_ADDRESS, + [SWAPS_TESTNET_CHAIN_ID]: WETH_CONTRACT_ADDRESS, + [BSC_CHAIN_ID]: WBNB_CONTRACT_ADDRESS, + [POLYGON_CHAIN_ID]: WMATIC_CONTRACT_ADDRESS, + [RINKEBY_CHAIN_ID]: WETH_RINKEBY_CONTRACT_ADDRESS, +}; + +export const ALLOWED_CONTRACT_ADDRESSES = { + [MAINNET_CHAIN_ID]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[MAINNET_CHAIN_ID], + SWAPS_WRAPPED_TOKENS_ADDRESSES[MAINNET_CHAIN_ID], + ], + [SWAPS_TESTNET_CHAIN_ID]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[SWAPS_TESTNET_CHAIN_ID], + SWAPS_WRAPPED_TOKENS_ADDRESSES[SWAPS_TESTNET_CHAIN_ID], + ], + [RINKEBY_CHAIN_ID]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[RINKEBY_CHAIN_ID], + SWAPS_WRAPPED_TOKENS_ADDRESSES[RINKEBY_CHAIN_ID], + ], + [BSC_CHAIN_ID]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[BSC_CHAIN_ID], + SWAPS_WRAPPED_TOKENS_ADDRESSES[BSC_CHAIN_ID], + ], + [POLYGON_CHAIN_ID]: [ + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[POLYGON_CHAIN_ID], + SWAPS_WRAPPED_TOKENS_ADDRESSES[POLYGON_CHAIN_ID], + ], +}; + export const SWAPS_CHAINID_DEFAULT_TOKEN_MAP = { [MAINNET_CHAIN_ID]: ETH_SWAPS_TOKEN_OBJECT, [SWAPS_TESTNET_CHAIN_ID]: TEST_ETH_SWAPS_TOKEN_OBJECT, diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index d12d3c4ff..5cbb8dedc 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -779,7 +779,7 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => { sensitiveProperties: swapMetaData, }); - if (!isContractAddressValid(usedTradeTxParams.to, swapMetaData, chainId)) { + if (!isContractAddressValid(usedTradeTxParams.to, chainId)) { captureMessage('Invalid contract address', { extra: { token_from: swapMetaData.token_from, diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index 0a3b7b00b..73ad6c1ee 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -59,7 +59,11 @@ import { } from '../../../../shared/constants/swaps'; import { resetSwapsPostFetchState, removeToken } from '../../../store/actions'; -import { fetchTokenPrice, fetchTokenBalance } from '../swaps.util'; +import { + fetchTokenPrice, + fetchTokenBalance, + shouldEnableDirectWrapping, +} from '../swaps.util'; import SwapsFooter from '../swaps-footer'; const fuseSearchKeys = [ @@ -375,6 +379,12 @@ export default function BuildQuote({ fromTokenSymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.symbol || '', ]); + const isDirectWrappingEnabled = shouldEnableDirectWrapping( + chainId, + fromTokenAddress, + selectedToToken.address, + ); + return (
@@ -552,15 +562,17 @@ export default function BuildQuote({ )}
))} -
- { - setMaxSlippage(newSlippage); - }} - maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE} - currentSlippage={maxSlippage} - /> -
+ {!isDirectWrappingEnabled && ( +
+ { + setMaxSlippage(newSlippage); + }} + maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE} + currentSlippage={maxSlippage} + /> +
+ )}
{ diff --git a/ui/pages/swaps/fee-card/fee-card.js b/ui/pages/swaps/fee-card/fee-card.js index 5e58fcf09..f27a9b566 100644 --- a/ui/pages/swaps/fee-card/fee-card.js +++ b/ui/pages/swaps/fee-card/fee-card.js @@ -82,17 +82,19 @@ export default function FeeCard({ {bestQuoteText && (

{bestQuoteText}

)} -
-

- {t('swapNQuotes', [numberOfQuotes])} -

-
- + {numberOfQuotes > 1 && ( +
+

+ {t('swapNQuotes', [numberOfQuotes])} +

+
+ +
-
+ )}
diff --git a/ui/pages/swaps/swaps.util.js b/ui/pages/swaps/swaps.util.js index 2d4c9da9e..f7d052aee 100644 --- a/ui/pages/swaps/swaps.util.js +++ b/ui/pages/swaps/swaps.util.js @@ -4,8 +4,8 @@ import abi from 'human-standard-token-abi'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP, METASWAP_CHAINID_API_HOST_MAP, - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, - ETH_WETH_CONTRACT_ADDRESS, + ALLOWED_CONTRACT_ADDRESSES, + SWAPS_WRAPPED_TOKENS_ADDRESSES, ETHEREUM, POLYGON, BSC, @@ -21,8 +21,6 @@ import { isSwapsDefaultTokenSymbol, } from '../../../shared/modules/swaps.utils'; import { - ETH_SYMBOL, - WETH_SYMBOL, MAINNET_CHAIN_ID, BSC_CHAIN_ID, POLYGON_CHAIN_ID, @@ -267,6 +265,19 @@ function validateData(validators, object, urlUsed) { }); } +export const shouldEnableDirectWrapping = ( + chainId, + sourceToken, + destinationToken, +) => { + const wrappedToken = SWAPS_WRAPPED_TOKENS_ADDRESSES[chainId]; + const nativeToken = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.address; + return ( + (sourceToken === wrappedToken && destinationToken === nativeToken) || + (sourceToken === nativeToken && destinationToken === wrappedToken) + ); +}; + export async function fetchTradesInfo( { slippage, @@ -291,6 +302,9 @@ export async function fetchTradesInfo( if (exchangeList) { urlParams.exchangeList = exchangeList; } + if (shouldEnableDirectWrapping(chainId, sourceToken, destinationToken)) { + urlParams.enableDirectWrapping = true; + } const queryString = new URLSearchParams(urlParams).toString(); const tradeURL = `${getBaseApi( @@ -628,6 +642,8 @@ export function quotesToRenderableData( renderedSlippage = 0; } else if (aggType === 'DEX') { liquiditySourceKey = 'swapDecentralizedExchange'; + } else if (aggType === 'CONTRACT') { + liquiditySourceKey = 'swapDirectContract'; } else { liquiditySourceKey = 'swapUnknown'; } @@ -769,29 +785,16 @@ export function formatSwapsValueForDisplay(destinationAmount) { */ export const isContractAddressValid = ( contractAddress, - swapMetaData, chainId = MAINNET_CHAIN_ID, ) => { - const contractAddressForChainId = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[chainId]; - if (!contractAddress || !contractAddressForChainId) { + if (!contractAddress || !ALLOWED_CONTRACT_ADDRESSES[chainId]) { return false; } - if ( - (swapMetaData.token_from === ETH_SYMBOL && - swapMetaData.token_to === WETH_SYMBOL) || - (swapMetaData.token_from === WETH_SYMBOL && - swapMetaData.token_to === ETH_SYMBOL) - ) { + return ALLOWED_CONTRACT_ADDRESSES[chainId].some( // Sometimes we get a contract address with a few upper-case chars and since addresses are - // case-insensitive, we compare uppercase versions for validity. - return ( - contractAddress.toUpperCase() === - ETH_WETH_CONTRACT_ADDRESS.toUpperCase() || - contractAddressForChainId.toUpperCase() === contractAddress.toUpperCase() - ); - } - return ( - contractAddressForChainId.toUpperCase() === contractAddress.toUpperCase() + // case-insensitive, we compare lowercase versions for validity. + (allowedContractAddress) => + contractAddress.toLowerCase() === allowedContractAddress.toLowerCase(), ); }; diff --git a/ui/pages/swaps/swaps.util.test.js b/ui/pages/swaps/swaps.util.test.js index 7d4957dd0..347086d0c 100644 --- a/ui/pages/swaps/swaps.util.test.js +++ b/ui/pages/swaps/swaps.util.test.js @@ -1,8 +1,6 @@ import nock from 'nock'; import { MOCKS } from '../../../test/jest'; import { - ETH_SYMBOL, - WETH_SYMBOL, MAINNET_CHAIN_ID, BSC_CHAIN_ID, POLYGON_CHAIN_ID, @@ -12,7 +10,10 @@ import { } from '../../../shared/constants/network'; import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, - ETH_WETH_CONTRACT_ADDRESS, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP, + WETH_CONTRACT_ADDRESS, + WBNB_CONTRACT_ADDRESS, + WMATIC_CONTRACT_ADDRESS, ETHEREUM, POLYGON, BSC, @@ -34,6 +35,7 @@ import { getNetworkNameByChainId, getSwapsLivenessForNetwork, countDecimals, + shouldEnableDirectWrapping, } from './swaps.util'; jest.mock('../../helpers/utils/storage-helpers.js', () => ({ @@ -188,197 +190,113 @@ describe('Swaps Util', () => { }); describe('isContractAddressValid', () => { - let swapMetaData; let usedTradeTxParams; beforeEach(() => { - swapMetaData = { - available_quotes: undefined, - average_savings: undefined, - best_quote_source: 'paraswap', - custom_slippage: true, - estimated_gas: '134629', - fee_savings: undefined, - gas_fees: '47.411896', - median_metamask_fee: undefined, - other_quote_selected: false, - other_quote_selected_source: '', - performance_savings: undefined, - slippage: 5, - suggested_gas_price: '164', - token_from: ETH_SYMBOL, - token_from_amount: '1', - token_to: WETH_SYMBOL, - token_to_amount: '1.0000000', - used_gas_price: '164', - }; usedTradeTxParams = { data: 'testData', from: '0xe53a5bc256898bfa5673b20aceeb2b2152075d17', gas: '2427c', gasPrice: '27592f5a00', - to: ETH_WETH_CONTRACT_ADDRESS, + to: WETH_CONTRACT_ADDRESS, value: '0xde0b6b3a7640000', }; }); - it('returns true if "token_from" is ETH, "token_to" is WETH and "to" is ETH_WETH contract address', () => { + it('returns true if "to" is WETH contract address', () => { expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, MAINNET_CHAIN_ID), ).toBe(true); }); - it('returns true if "token_from" is WETH, "token_to" is ETH and "to" is ETH_WETH contract address', () => { - swapMetaData.token_from = WETH_SYMBOL; - swapMetaData.token_to = ETH_SYMBOL; - expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), - ).toBe(true); - }); - - it('returns true if "token_from" is ETH, "token_to" is WETH and "to" is ETH_WETH contract address with some uppercase chars', () => { + it('returns true if "to" is WETH contract address with some uppercase chars', () => { usedTradeTxParams.to = '0xc02AAA39B223fe8d0a0e5c4f27ead9083c756cc2'; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, MAINNET_CHAIN_ID), ).toBe(true); }); - it('returns true if "token_from" is ETH, "token_to" is WETH and "to" is mainnet contract address', () => { + it('returns true if "to" is ETH mainnet contract address on ETH mainnet', () => { usedTradeTxParams.to = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[MAINNET_CHAIN_ID]; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, MAINNET_CHAIN_ID), ).toBe(true); }); - it('returns true if "token_from" is WETH, "token_to" is ETH and "to" is mainnet contract address', () => { - swapMetaData.token_from = WETH_SYMBOL; - swapMetaData.token_to = ETH_SYMBOL; - usedTradeTxParams.to = - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[MAINNET_CHAIN_ID]; + it('returns true if "to" is WBNB contract address on BSC mainnet', () => { + usedTradeTxParams.to = WBNB_CONTRACT_ADDRESS; + expect(isContractAddressValid(usedTradeTxParams.to, BSC_CHAIN_ID)).toBe( + true, + ); + }); + + it('returns true if "to" is WMATIC contract address on Polygon mainnet', () => { + usedTradeTxParams.to = WMATIC_CONTRACT_ADDRESS; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, POLYGON_CHAIN_ID), ).toBe(true); }); - it('returns false if "token_from" is ETH, "token_to" is WETH and "to" is BSC contract address', () => { + it('returns false if "to" is BSC contract address on ETH mainnet', () => { usedTradeTxParams.to = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[BSC_CHAIN_ID]; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), - ).toBe(false); - }); - - it('returns false if "token_from" is WETH, "token_to" is ETH and "to" is BSC contract address', () => { - swapMetaData.token_from = WETH_SYMBOL; - swapMetaData.token_to = ETH_SYMBOL; - usedTradeTxParams.to = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[BSC_CHAIN_ID]; - expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, MAINNET_CHAIN_ID), ).toBe(false); }); it('returns false if contractAddress is null', () => { - expect( - isContractAddressValid(null, swapMetaData, LOCALHOST_CHAIN_ID), - ).toBe(false); + expect(isContractAddressValid(null, LOCALHOST_CHAIN_ID)).toBe(false); }); it('returns false if chainId is incorrect', () => { expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - 'incorrectChainId', - ), + isContractAddressValid(usedTradeTxParams.to, 'incorrectChainId'), ).toBe(false); }); - it('returns true if "token_from" is BAT and "to" is mainnet contract address', () => { - swapMetaData.token_from = 'BAT'; - usedTradeTxParams.to = - SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[MAINNET_CHAIN_ID]; - expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - MAINNET_CHAIN_ID, - ), - ).toBe(true); - }); - - it('returns true if "token_to" is BAT and "to" is BSC contract address', () => { - swapMetaData.token_to = 'BAT'; + it('returns true if "to" is BSC contract address on BSC network', () => { usedTradeTxParams.to = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[BSC_CHAIN_ID]; + expect(isContractAddressValid(usedTradeTxParams.to, BSC_CHAIN_ID)).toBe( + true, + ); + }); + + it('returns true if "to" is Polygon contract address on Polygon network', () => { + usedTradeTxParams.to = + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[POLYGON_CHAIN_ID]; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - BSC_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, POLYGON_CHAIN_ID), ).toBe(true); }); - it('returns true if "token_to" is BAT and "to" is testnet contract address', () => { - swapMetaData.token_to = 'BAT'; + it('returns true if "to" is Rinkeby contract address on Rinkeby network', () => { + usedTradeTxParams.to = + SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[RINKEBY_CHAIN_ID]; + expect( + isContractAddressValid(usedTradeTxParams.to, RINKEBY_CHAIN_ID), + ).toBe(true); + }); + + it('returns true if "to" is testnet contract address', () => { usedTradeTxParams.to = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[LOCALHOST_CHAIN_ID]; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - LOCALHOST_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, LOCALHOST_CHAIN_ID), ).toBe(true); }); - it('returns true if "token_to" is BAT and "to" is testnet contract address with some uppercase chars', () => { - swapMetaData.token_to = 'BAT'; + it('returns true if "to" is testnet contract address with some uppercase chars', () => { usedTradeTxParams.to = '0x881D40237659C251811CEC9c364ef91dC08D300C'; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - LOCALHOST_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, LOCALHOST_CHAIN_ID), ).toBe(true); }); - it('returns false if "token_to" is BAT and "to" has mismatch with current chainId', () => { - swapMetaData.token_to = 'BAT'; + it('returns false if "to" has mismatch with current chainId', () => { + usedTradeTxParams.to = SWAPS_CHAINID_CONTRACT_ADDRESS_MAP[BSC_CHAIN_ID]; expect( - isContractAddressValid( - usedTradeTxParams.to, - swapMetaData, - LOCALHOST_CHAIN_ID, - ), + isContractAddressValid(usedTradeTxParams.to, LOCALHOST_CHAIN_ID), ).toBe(false); }); }); @@ -492,4 +410,114 @@ describe('Swaps Util', () => { expect(countDecimals(1.123456789)).toBe(9); }); }); + + describe('shouldEnableDirectWrapping', () => { + const randomTokenAddress = '0x881d40237659c251811cec9c364ef91234567890'; + + it('returns true if swapping from ETH to WETH', () => { + expect( + shouldEnableDirectWrapping( + MAINNET_CHAIN_ID, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[MAINNET_CHAIN_ID]?.address, + WETH_CONTRACT_ADDRESS, + ), + ).toBe(true); + }); + + it('returns true if swapping from WETH to ETH', () => { + expect( + shouldEnableDirectWrapping( + MAINNET_CHAIN_ID, + WETH_CONTRACT_ADDRESS, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[MAINNET_CHAIN_ID]?.address, + ), + ).toBe(true); + }); + + it('returns false if swapping from ETH to a non-WETH token', () => { + expect( + shouldEnableDirectWrapping( + MAINNET_CHAIN_ID, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[MAINNET_CHAIN_ID]?.address, + randomTokenAddress, + ), + ).toBe(false); + }); + + it('returns true if swapping from BNB to WBNB', () => { + expect( + shouldEnableDirectWrapping( + BSC_CHAIN_ID, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[BSC_CHAIN_ID]?.address, + WBNB_CONTRACT_ADDRESS, + ), + ).toBe(true); + }); + + it('returns true if swapping from WBNB to BNB', () => { + expect( + shouldEnableDirectWrapping( + BSC_CHAIN_ID, + WBNB_CONTRACT_ADDRESS, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[BSC_CHAIN_ID]?.address, + ), + ).toBe(true); + }); + + it('returns false if swapping from BNB to a non-WBNB token', () => { + expect( + shouldEnableDirectWrapping( + BSC_CHAIN_ID, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[BSC_CHAIN_ID]?.address, + randomTokenAddress, + ), + ).toBe(false); + }); + + it('returns true if swapping from MATIC to WMATIC', () => { + expect( + shouldEnableDirectWrapping( + POLYGON_CHAIN_ID, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[POLYGON_CHAIN_ID]?.address, + WMATIC_CONTRACT_ADDRESS, + ), + ).toBe(true); + }); + + it('returns true if swapping from WMATIC to MATIC', () => { + expect( + shouldEnableDirectWrapping( + POLYGON_CHAIN_ID, + WMATIC_CONTRACT_ADDRESS, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[POLYGON_CHAIN_ID]?.address, + ), + ).toBe(true); + }); + + it('returns false if swapping from MATIC to a non-WMATIC token', () => { + expect( + shouldEnableDirectWrapping( + POLYGON_CHAIN_ID, + SWAPS_CHAINID_DEFAULT_TOKEN_MAP[POLYGON_CHAIN_ID]?.address, + randomTokenAddress, + ), + ).toBe(false); + }); + + it('returns false if a source token is undefined', () => { + expect( + shouldEnableDirectWrapping( + MAINNET_CHAIN_ID, + undefined, + WETH_CONTRACT_ADDRESS, + ), + ).toBe(false); + }); + + it('returns false if a destination token is undefined', () => { + expect( + shouldEnableDirectWrapping(MAINNET_CHAIN_ID, WETH_CONTRACT_ADDRESS), + ).toBe(false); + }); + }); });