diff --git a/builds.yml b/builds.yml index 446ce72139ec..79f59dc4ff19 100644 --- a/builds.yml +++ b/builds.yml @@ -277,6 +277,8 @@ env: # Enables the notifications feature within the build: - NOTIFICATIONS: '' + - METAMASK_RAMP_API_BASE_URL: https://on-ramp-content.metaswap.codefi.network + ### # Meta variables ### diff --git a/privacy-snapshot.json b/privacy-snapshot.json index 4ce6cbbe0253..1312b44bec65 100644 --- a/privacy-snapshot.json +++ b/privacy-snapshot.json @@ -26,6 +26,7 @@ "mainnet.infura.io", "metamask.github.io", "min-api.cryptocompare.com", + "on-ramp-content.metaswap.codefi.network", "phishing-detection.metafi.codefi.network", "portfolio.metamask.io", "price-api.metafi.codefi.network", diff --git a/shared/constants/network.ts b/shared/constants/network.ts index 3b2908cea942..a09986188cb3 100644 --- a/shared/constants/network.ts +++ b/shared/constants/network.ts @@ -66,22 +66,6 @@ export type RPCDefinition = { rpcPrefs: RPCPreferences; }; -/** - * For each chain that we support fiat onramps for, we provide a set of - * configuration options that help for initializing the connectiong to the - * onramp providers. - */ -type BuyableChainSettings = { - /** - * The native currency for the given chain - */ - nativeCurrency: CurrencySymbol | TestNetworkCurrencySymbol; - /** - * The network name or identifier - */ - network: string; -}; - /** * Throughout the extension we set the current provider by referencing its * "type", which can be any of the values in the below object. These values @@ -861,100 +845,6 @@ export const UNSUPPORTED_RPC_METHODS = new Set([ export const IPFS_DEFAULT_GATEWAY_URL = 'dweb.link'; -// The first item in transakCurrencies must be the -// default crypto currency for the network -const BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME = 'ethereum'; - -export const BUYABLE_CHAINS_MAP: { - [K in Exclude< - ChainId, - | typeof CHAIN_IDS.LOCALHOST - | typeof CHAIN_IDS.OPTIMISM_TESTNET - | typeof CHAIN_IDS.OPTIMISM_GOERLI - | typeof CHAIN_IDS.BASE_TESTNET - | typeof CHAIN_IDS.OPBNB_TESTNET - | typeof CHAIN_IDS.OPBNB - | typeof CHAIN_IDS.BSC_TESTNET - | typeof CHAIN_IDS.POLYGON_TESTNET - | typeof CHAIN_IDS.AVALANCHE_TESTNET - | typeof CHAIN_IDS.FANTOM_TESTNET - | typeof CHAIN_IDS.MOONBEAM_TESTNET - | typeof CHAIN_IDS.LINEA_GOERLI - | typeof CHAIN_IDS.LINEA_SEPOLIA - | typeof CHAIN_IDS.GOERLI - | typeof CHAIN_IDS.SEPOLIA - | typeof CHAIN_IDS.GNOSIS - | typeof CHAIN_IDS.AURORA - | typeof CHAIN_IDS.ARBITRUM_GOERLI - | typeof CHAIN_IDS.BLAST - >]: BuyableChainSettings; -} = { - [CHAIN_IDS.MAINNET]: { - nativeCurrency: CURRENCY_SYMBOLS.ETH, - network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME, - }, - [CHAIN_IDS.BSC]: { - nativeCurrency: CURRENCY_SYMBOLS.BNB, - network: 'bsc', - }, - [CHAIN_IDS.POLYGON]: { - nativeCurrency: CURRENCY_SYMBOLS.MATIC, - network: 'polygon', - }, - [CHAIN_IDS.AVALANCHE]: { - nativeCurrency: CURRENCY_SYMBOLS.AVALANCHE, - network: 'avaxcchain', - }, - [CHAIN_IDS.FANTOM]: { - nativeCurrency: CURRENCY_SYMBOLS.FANTOM, - network: 'fantom', - }, - [CHAIN_IDS.CELO]: { - nativeCurrency: CURRENCY_SYMBOLS.CELO, - network: 'celo', - }, - [CHAIN_IDS.OPTIMISM]: { - nativeCurrency: CURRENCY_SYMBOLS.ETH, - network: 'optimism', - }, - [CHAIN_IDS.ARBITRUM]: { - nativeCurrency: CURRENCY_SYMBOLS.ARBITRUM, - network: 'arbitrum', - }, - [CHAIN_IDS.CRONOS]: { - nativeCurrency: CURRENCY_SYMBOLS.CRONOS, - network: 'cronos', - }, - [CHAIN_IDS.MOONBEAM]: { - nativeCurrency: CURRENCY_SYMBOLS.GLIMMER, - network: 'moonbeam', - }, - [CHAIN_IDS.MOONRIVER]: { - nativeCurrency: CURRENCY_SYMBOLS.MOONRIVER, - network: 'moonriver', - }, - [CHAIN_IDS.HARMONY]: { - nativeCurrency: CURRENCY_SYMBOLS.ONE, - network: 'harmony', - }, - [CHAIN_IDS.PALM]: { - nativeCurrency: CURRENCY_SYMBOLS.PALM, - network: 'palm', - }, - [CHAIN_IDS.LINEA_MAINNET]: { - nativeCurrency: CURRENCY_SYMBOLS.ETH, - network: 'linea', - }, - [CHAIN_IDS.ZKSYNC_ERA]: { - nativeCurrency: CURRENCY_SYMBOLS.ETH, - network: 'zksync', - }, - [CHAIN_IDS.BASE]: { - nativeCurrency: CURRENCY_SYMBOLS.ETH, - network: 'base', - }, -}; - export const FEATURED_RPCS: RPCDefinition[] = [ { chainId: CHAIN_IDS.ARBITRUM, diff --git a/test/data/mock-send-state.json b/test/data/mock-send-state.json index badc99d28341..532d2e3772c4 100644 --- a/test/data/mock-send-state.json +++ b/test/data/mock-send-state.json @@ -1216,6 +1216,88 @@ } ] }, + "ramps": { + "buyableChains": [ + { + "active": true, + "chainId": 1, + "chainName": "Ethereum Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 10, + "chainName": "Optimism Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 25, + "chainName": "Cronos Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 56, + "chainName": "BNB Chain Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 137, + "chainName": "Polygon Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 250, + "chainName": "Fantom Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 1285, + "chainName": "Moonriver Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 42161, + "chainName": "Arbitrum Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 42220, + "chainName": "Celo Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 43114, + "chainName": "Avalanche C-Chain Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 1313161554, + "chainName": "Aurora Mainnet", + "nativeTokenSupported": false + }, + { + "active": true, + "chainId": 1666600000, + "chainName": "Harmony Mainnet (Shard 0)", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 11297108109, + "chainName": "Palm", + "nativeTokenSupported": false + } + ] + }, "send": { "amountMode": "INPUT", "currentTransactionUUID": "1-tx", diff --git a/test/data/mock-state.json b/test/data/mock-state.json index d4d6555504f8..0b7ffce36f5f 100644 --- a/test/data/mock-state.json +++ b/test/data/mock-state.json @@ -1871,6 +1871,88 @@ } } }, + "ramps": { + "buyableChains": [ + { + "active": true, + "chainId": 1, + "chainName": "Ethereum Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 10, + "chainName": "Optimism Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 25, + "chainName": "Cronos Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 56, + "chainName": "BNB Chain Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 137, + "chainName": "Polygon Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 250, + "chainName": "Fantom Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 1285, + "chainName": "Moonriver Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 42161, + "chainName": "Arbitrum Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 42220, + "chainName": "Celo Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 43114, + "chainName": "Avalanche C-Chain Mainnet", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 1313161554, + "chainName": "Aurora Mainnet", + "nativeTokenSupported": false + }, + { + "active": true, + "chainId": 1666600000, + "chainName": "Harmony Mainnet (Shard 0)", + "nativeTokenSupported": true + }, + { + "active": true, + "chainId": 11297108109, + "chainName": "Palm", + "nativeTokenSupported": false + } + ] + }, "send": { "amountMode": "INPUT", "currentTransactionUUID": null, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 06928aa7da9d..007376727faa 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -75,7 +75,6 @@ "showAccountBanner": true, "trezorModel": null, "hadAdvancedGasFeesSetPriorToMigration92_3": false, - "hasDismissedOpenSeaToBlockaidBanner": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", "qrHardware": {}, @@ -103,6 +102,7 @@ "dismissSeedBackUpReminder": true, "disabledRpcMethodPreferences": { "eth_sign": false }, "useMultiAccountBalanceChecker": true, + "hasDismissedOpenSeaToBlockaidBanner": false, "useSafeChainsListValidation": true, "useTokenDetection": false, "useNftDetection": false, @@ -229,6 +229,7 @@ "storageMetadata": {}, "versionFileETag": "string" }, + "ramps": "object", "send": "object", "swaps": "object", "unconnectedAccount": { "state": "CLOSED" } diff --git a/ui/components/app/add-network/add-network.test.js b/ui/components/app/add-network/add-network.test.js index 3a15f4d33c3e..f3084b1db065 100644 --- a/ui/components/app/add-network/add-network.test.js +++ b/ui/components/app/add-network/add-network.test.js @@ -6,6 +6,7 @@ import mockState from '../../../../test/data/mock-state.json'; import AddNetwork from './add-network'; jest.mock('../../../selectors', () => ({ + ...jest.requireActual('../../../selectors'), getNetworkConfigurations: () => ({ networkConfigurationId: { chainId: '0x539', diff --git a/ui/components/app/asset-list/asset-list.js b/ui/components/app/asset-list/asset-list.js index 8e47993eac17..8bccfe42f32f 100644 --- a/ui/components/app/asset-list/asset-list.js +++ b/ui/components/app/asset-list/asset-list.js @@ -11,9 +11,6 @@ import { getDetectedTokensInCurrentNetwork, getIstokenDetectionInactiveOnNonMainnetSupportedNetwork, getShouldHideZeroBalanceTokens, - ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - getIsBuyableChain, - ///: END:ONLY_INCLUDE_IF getCurrentNetwork, getSelectedAccount, getPreferences, @@ -48,6 +45,7 @@ import { RAMPS_CARD_VARIANT_TYPES, RampsCard, } from '../../multichain/ramps-card/ramps-card'; +import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF const AssetList = ({ onClickAsset }) => { @@ -107,7 +105,7 @@ const AssetList = ({ onClickAsset }) => { }); const balanceIsZero = Number(totalFiatBalance) === 0; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - const isBuyableChain = useSelector(getIsBuyableChain); + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const shouldShowBuy = isBuyableChain && balanceIsZero; ///: END:ONLY_INCLUDE_IF diff --git a/ui/components/app/nfts-tab/nfts-tab.js b/ui/components/app/nfts-tab/nfts-tab.js index 7ee4e120510f..63ef2c6c5a3f 100644 --- a/ui/components/app/nfts-tab/nfts-tab.js +++ b/ui/components/app/nfts-tab/nfts-tab.js @@ -17,7 +17,6 @@ import { useNftsCollections } from '../../../hooks/useNftsCollections'; import { getCurrentNetwork, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - getIsBuyableChain, getShouldHideZeroBalanceTokens, getSelectedAccount, ///: END:ONLY_INCLUDE_IF @@ -48,6 +47,7 @@ import { RampsCard, } from '../../multichain/ramps-card/ramps-card'; import { useAccountTotalFiatBalance } from '../../../hooks/useAccountTotalFiatBalance'; +import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF export default function NftsTab() { @@ -68,7 +68,7 @@ export default function NftsTab() { shouldHideZeroBalanceTokens, ); const balanceIsZero = Number(totalFiatBalance) === 0; - const isBuyableChain = useSelector(getIsBuyableChain); + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const showRampsCard = isBuyableChain && balanceIsZero; ///: END:ONLY_INCLUDE_IF diff --git a/ui/components/app/selected-account/selected-account-component.test.js b/ui/components/app/selected-account/selected-account-component.test.js index a48e24a661c6..ae8a3ff000db 100644 --- a/ui/components/app/selected-account/selected-account-component.test.js +++ b/ui/components/app/selected-account/selected-account-component.test.js @@ -52,6 +52,7 @@ jest.mock('../../../selectors', () => { return { getAccountType: mockGetAccountType, getSelectedInternalAccount: mockGetSelectedAccount, + getCurrentChainId: jest.fn(() => '0x1'), }; }); diff --git a/ui/components/app/transaction-list/transaction-list.component.js b/ui/components/app/transaction-list/transaction-list.component.js index 298d8dcba44b..17c722c52d2a 100644 --- a/ui/components/app/transaction-list/transaction-list.component.js +++ b/ui/components/app/transaction-list/transaction-list.component.js @@ -10,7 +10,6 @@ import { getCurrentChainId, getSelectedAccount, ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - getIsBuyableChain, getShouldHideZeroBalanceTokens, ///: END:ONLY_INCLUDE_IF } from '../../../selectors'; @@ -33,6 +32,7 @@ import { RAMPS_CARD_VARIANT_TYPES, RampsCard, } from '../../multichain/ramps-card/ramps-card'; +import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF const PAGE_INCREMENT = 10; @@ -138,8 +138,7 @@ export default function TransactionList({ shouldHideZeroBalanceTokens, ); const balanceIsZero = Number(totalFiatBalance) === 0; - const isBuyableChain = useSelector(getIsBuyableChain); - + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const showRampsCard = isBuyableChain && balanceIsZero; ///: END:ONLY_INCLUDE_IF diff --git a/ui/components/app/wallet-overview/eth-overview.js b/ui/components/app/wallet-overview/eth-overview.js index abd5351ed712..e65876f793a3 100644 --- a/ui/components/app/wallet-overview/eth-overview.js +++ b/ui/components/app/wallet-overview/eth-overview.js @@ -38,7 +38,6 @@ import { getSwapsDefaultToken, getCurrentKeyring, getIsBridgeChain, - getIsBuyableChain, getMetaMetricsId, ///: END:ONLY_INCLUDE_IF } from '../../../selectors'; @@ -61,8 +60,9 @@ import { AssetType } from '../../../../shared/constants/transaction'; import { Icon, IconName } from '../../component-library'; import { IconColor } from '../../../helpers/constants/design-system'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) -import useRamps from '../../../hooks/experiences/useRamps'; +import useRamps from '../../../hooks/useRamps/useRamps'; import { getPortfolioUrl } from '../../../helpers/utils/portfolio'; +import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; ///: END:ONLY_INCLUDE_IF import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol'; import { getProviderConfig } from '../../../ducks/metamask/metamask'; @@ -77,7 +77,7 @@ const EthOverview = ({ className, showAddress }) => { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const location = useLocation(); const isBridgeChain = useSelector(getIsBridgeChain); - const isBuyableChain = useSelector(getIsBuyableChain); + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const metaMetricsId = useSelector(getMetaMetricsId); const keyring = useSelector(getCurrentKeyring); const usingHardwareWallet = isHardwareKeyring(keyring?.type); diff --git a/ui/components/app/wallet-overview/eth-overview.test.js b/ui/components/app/wallet-overview/eth-overview.test.js index ed5612b8cf4c..732682bccc91 100644 --- a/ui/components/app/wallet-overview/eth-overview.test.js +++ b/ui/components/app/wallet-overview/eth-overview.test.js @@ -12,25 +12,9 @@ import { import { renderWithProvider } from '../../../../test/jest/rendering'; import { KeyringType } from '../../../../shared/constants/keyring'; import { useIsOriginalNativeTokenSymbol } from '../../../hooks/useIsOriginalNativeTokenSymbol'; +import { defaultBuyableChains } from '../../../ducks/ramps/constants'; import EthOverview from './eth-overview'; -// Mock BUYABLE_CHAINS_MAP -jest.mock('../../../../shared/constants/network', () => ({ - ...jest.requireActual('../../../../shared/constants/network'), - BUYABLE_CHAINS_MAP: { - // MAINNET - '0x1': { - nativeCurrency: 'ETH', - network: 'ethereum', - }, - // POLYGON - '0x89': { - nativeCurrency: 'MATIC', - network: 'polygon', - }, - }, -})); - jest.mock('../../../hooks/useIsOriginalNativeTokenSymbol', () => { return { useIsOriginalNativeTokenSymbol: jest.fn(), @@ -135,6 +119,9 @@ describe('EthOverview', () => { ], contractExchangeRates: {}, }, + ramps: { + buyableChains: defaultBuyableChains, + }, }; const store = configureMockStore([thunk])(mockStore); @@ -178,6 +165,7 @@ describe('EthOverview', () => { it('should show the cached primary balance', async () => { const mockedStoreWithCachedBalance = { + ...mockStore, metamask: { ...mockStore.metamask, accounts: { @@ -264,6 +252,7 @@ describe('EthOverview', () => { it('should open the MMI PD Swaps URI when clicking on Swap button with a Custody account', async () => { const mockedStoreWithCustodyKeyring = { + ...mockStore, metamask: { ...mockStore.metamask, mmiConfiguration: { @@ -372,6 +361,7 @@ describe('EthOverview', () => { it('should have the Buy native token button disabled if chain id is not part of supported buyable chains', () => { const mockedStoreWithUnbuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { @@ -396,6 +386,7 @@ describe('EthOverview', () => { it('should have the Buy native token enabled if chain id is part of supported buyable chains', () => { const mockedStoreWithUnbuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { @@ -429,6 +420,7 @@ describe('EthOverview', () => { it('should open the Buy native token URI when clicking on Buy button for a buyable chain ID', async () => { const mockedStoreWithBuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { diff --git a/ui/components/app/wallet-overview/token-overview.js b/ui/components/app/wallet-overview/token-overview.js index b823dd399024..c27fa566e57c 100644 --- a/ui/components/app/wallet-overview/token-overview.js +++ b/ui/components/app/wallet-overview/token-overview.js @@ -17,7 +17,8 @@ import { startNewDraftTransaction } from '../../../ducks/send'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) import { isHardwareKeyring } from '../../../helpers/utils/hardware'; import { setSwapsFromToken } from '../../../ducks/swaps/swaps'; -import useRamps from '../../../hooks/experiences/useRamps'; +import useRamps from '../../../hooks/useRamps/useRamps'; +import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; import { getPortfolioUrl } from '../../../helpers/utils/portfolio'; ///: END:ONLY_INCLUDE_IF ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -32,7 +33,6 @@ import { ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) getIsBridgeChain, getCurrentKeyring, - getIsBuyableChain, getMetaMetricsId, ///: END:ONLY_INCLUDE_IF } from '../../../selectors'; @@ -80,7 +80,7 @@ const TokenOverview = ({ className, token }) => { const isSwapsChain = useSelector(getIsSwapsChain); ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) const isBridgeChain = useSelector(getIsBridgeChain); - const isBuyableChain = useSelector(getIsBuyableChain); + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const metaMetricsId = useSelector(getMetaMetricsId); const { openBuyCryptoInPdapp } = useRamps(); diff --git a/ui/components/app/wallet-overview/token-overview.test.js b/ui/components/app/wallet-overview/token-overview.test.js index 45d70265e3fc..c4c59bbc16f4 100644 --- a/ui/components/app/wallet-overview/token-overview.test.js +++ b/ui/components/app/wallet-overview/token-overview.test.js @@ -6,24 +6,9 @@ import { EthAccountType, EthMethod } from '@metamask/keyring-api'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import { renderWithProvider } from '../../../../test/jest/rendering'; import { KeyringType } from '../../../../shared/constants/keyring'; +import { defaultBuyableChains } from '../../../ducks/ramps/constants'; import TokenOverview from './token-overview'; -// Mock BUYABLE_CHAINS_MAP -jest.mock('../../../../shared/constants/network', () => ({ - ...jest.requireActual('../../../../shared/constants/network'), - BUYABLE_CHAINS_MAP: { - // MAINNET - '0x1': { - nativeCurrency: 'ETH', - network: 'ethereum', - }, - // POLYGON - '0x89': { - nativeCurrency: 'MATIC', - network: 'polygon', - }, - }, -})); let openTabSpy; describe('TokenOverview', () => { @@ -79,6 +64,9 @@ describe('TokenOverview', () => { url: 'https://metamask-institutional.io', }, }, + ramps: { + buyableChains: defaultBuyableChains, + }, }; const store = configureMockStore([thunk])(mockStore); @@ -134,6 +122,7 @@ describe('TokenOverview', () => { it('should always show the Buy button regardless of chain Id', () => { const mockedStoreWithUnbuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { type: 'test', chainId: CHAIN_IDS.PALM }, @@ -167,9 +156,10 @@ describe('TokenOverview', () => { it('should have the Buy token button disabled if chain id is not part of supported buyable chains', () => { const mockedStoreWithUnbuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, - providerConfig: { type: 'test', chainId: CHAIN_IDS.FANTOM }, + providerConfig: { type: 'test', chainId: CHAIN_IDS.GOERLI }, }, }; const mockedStore = configureMockStore([thunk])( @@ -187,6 +177,7 @@ describe('TokenOverview', () => { it('should have the Buy token button enabled if chain id is part of supported buyable chains', () => { const mockedStoreWithBuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { type: 'test', chainId: CHAIN_IDS.POLYGON }, @@ -212,6 +203,7 @@ describe('TokenOverview', () => { }; const mockedStoreWithBuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { type: 'test', chainId: CHAIN_IDS.POLYGON }, @@ -232,6 +224,7 @@ describe('TokenOverview', () => { it('should open the buy crypto URL for a buyable chain ID', async () => { const mockedStoreWithBuyableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { type: 'test', chainId: CHAIN_IDS.POLYGON }, @@ -270,6 +263,7 @@ describe('TokenOverview', () => { }; const mockedStoreWithBridgeableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { type: 'test', chainId: CHAIN_IDS.POLYGON }, @@ -308,6 +302,7 @@ describe('TokenOverview', () => { }; const mockedStoreWithBridgeableChainId = { + ...mockStore, metamask: { ...mockStore.metamask, providerConfig: { type: 'test', chainId: CHAIN_IDS.FANTOM }, diff --git a/ui/components/multichain/ramps-card/ramps-card.js b/ui/components/multichain/ramps-card/ramps-card.js index 9b1956ac6b7d..3c783675deb0 100644 --- a/ui/components/multichain/ramps-card/ramps-card.js +++ b/ui/components/multichain/ramps-card/ramps-card.js @@ -16,9 +16,7 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { MetaMetricsContext } from '../../../contexts/metametrics'; -import useRamps, { - RampsMetaMaskEntry, -} from '../../../hooks/experiences/useRamps'; +import useRamps, { RampsMetaMaskEntry } from '../../../hooks/useRamps/useRamps'; import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import { getCurrentLocale } from '../../../ducks/locale/locale'; diff --git a/ui/ducks/index.js b/ui/ducks/index.js index e976fa60e33d..f36e9bade19e 100644 --- a/ui/ducks/index.js +++ b/ui/ducks/index.js @@ -11,6 +11,7 @@ import gasReducer from './gas/gas.duck'; import { invalidCustomNetwork, unconnectedAccount } from './alerts'; import swapsReducer from './swaps/swaps'; import historyReducer from './history/history'; +import rampsReducer from './ramps/ramps'; export default combineReducers({ [AlertTypes.invalidCustomNetwork]: invalidCustomNetwork, @@ -24,6 +25,7 @@ export default combineReducers({ confirm: confirmReducer, confirmTransaction: confirmTransactionReducer, swaps: swapsReducer, + ramps: rampsReducer, gas: gasReducer, localeMessages: localeMessagesReducer, }); diff --git a/ui/ducks/ramps/constants.ts b/ui/ducks/ramps/constants.ts new file mode 100644 index 000000000000..597730fe3ff4 --- /dev/null +++ b/ui/ducks/ramps/constants.ts @@ -0,0 +1,82 @@ +import { AggregatorNetwork } from './types'; + +export const defaultBuyableChains: AggregatorNetwork[] = [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 10, + chainName: 'Optimism Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 25, + chainName: 'Cronos Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 56, + chainName: 'BNB Chain Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 137, + chainName: 'Polygon Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 250, + chainName: 'Fantom Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 1285, + chainName: 'Moonriver Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 42161, + chainName: 'Arbitrum Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 42220, + chainName: 'Celo Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 43114, + chainName: 'Avalanche C-Chain Mainnet', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 1313161554, + chainName: 'Aurora Mainnet', + nativeTokenSupported: false, + }, + { + active: true, + chainId: 1666600000, + chainName: 'Harmony Mainnet (Shard 0)', + nativeTokenSupported: true, + }, + { + active: true, + chainId: 11297108109, + chainName: 'Palm', + nativeTokenSupported: false, + }, +]; diff --git a/ui/ducks/ramps/index.ts b/ui/ducks/ramps/index.ts new file mode 100644 index 000000000000..b6f8f2473810 --- /dev/null +++ b/ui/ducks/ramps/index.ts @@ -0,0 +1 @@ +export * from './ramps'; diff --git a/ui/ducks/ramps/ramps.test.ts b/ui/ducks/ramps/ramps.test.ts new file mode 100644 index 000000000000..b9724d11943f --- /dev/null +++ b/ui/ducks/ramps/ramps.test.ts @@ -0,0 +1,125 @@ +import { configureStore, Store } from '@reduxjs/toolkit'; +import RampAPI from '../../hooks/useRamps/rampAPI'; +import { getCurrentChainId } from '../../selectors'; +import { CHAIN_IDS } from '../../../shared/constants/network'; +import rampsReducer, { + fetchBuyableChains, + getBuyableChains, + getIsNativeTokenBuyable, +} from './ramps'; +import { defaultBuyableChains } from './constants'; + +jest.mock('../../hooks/useRamps/rampAPI'); + +jest.mock('../../selectors', () => ({ + getCurrentChainId: jest.fn(), + getNames: jest.fn(), +})); + +describe('rampsSlice', () => { + let store: Store; + + beforeEach(() => { + store = configureStore({ + reducer: { + ramps: rampsReducer, + }, + }); + // @ts-expect-error mocked API has mockReset method + RampAPI.getNetworks.mockReset(); + }); + + it('should set the initial state to defaultBuyableChains', () => { + const { ramps: rampsState } = store.getState(); + expect(rampsState).toEqual({ + buyableChains: defaultBuyableChains, + }); + }); + + it('should update the buyableChains state when setBuyableChains is dispatched', () => { + const mockBuyableChains = [{ chainId: '0x1' }]; + store.dispatch({ + type: 'ramps/setBuyableChains', + payload: mockBuyableChains, + }); + const { ramps: rampsState } = store.getState(); + expect(rampsState.buyableChains).toEqual(mockBuyableChains); + }); + + describe('getBuyableChains', () => { + it('returns buyableChains', () => { + const state = store.getState(); + expect(getBuyableChains(state)).toBe(state.ramps.buyableChains); + }); + }); + + describe('fetchBuyableChains', () => { + it('should call RampAPI.getNetworks', async () => { + // @ts-expect-error this is a valid action + await store.dispatch(fetchBuyableChains()); + expect(RampAPI.getNetworks).toHaveBeenCalledTimes(1); + }); + + it('should update the state with the data that is returned', async () => { + const mockBuyableChains = [ + { + active: true, + chainId: 1, + chainName: 'Ethereum Mainnet', + nativeTokenSupported: true, + }, + ]; + jest.spyOn(RampAPI, 'getNetworks').mockResolvedValue(mockBuyableChains); + // @ts-expect-error this is a valid action + await store.dispatch(fetchBuyableChains()); + const { ramps: rampsState } = store.getState(); + expect(rampsState.buyableChains).toEqual(mockBuyableChains); + }); + it('should set state to defaultBuyableChains when returned networks are undefined', async () => { + // @ts-expect-error forcing undefined to test the behavior + jest.spyOn(RampAPI, 'getNetworks').mockResolvedValue(undefined); + // @ts-expect-error this is a valid action + await store.dispatch(fetchBuyableChains()); + const { ramps: rampsState } = store.getState(); + expect(rampsState.buyableChains).toEqual(defaultBuyableChains); + }); + + it('should set state to defaultBuyableChains when returned networks are empty', async () => { + jest.spyOn(RampAPI, 'getNetworks').mockResolvedValue([]); + // @ts-expect-error this is a valid action + await store.dispatch(fetchBuyableChains()); + const { ramps: rampsState } = store.getState(); + expect(rampsState.buyableChains).toEqual(defaultBuyableChains); + }); + + it('should set state to defaultBuyableChains when API request fails', async () => { + jest + .spyOn(RampAPI, 'getNetworks') + .mockRejectedValue(new Error('API error')); + // @ts-expect-error this is a valid action + await store.dispatch(fetchBuyableChains()); + const { ramps: rampsState } = store.getState(); + expect(rampsState.buyableChains).toEqual(defaultBuyableChains); + }); + }); + + describe('getIsNativeTokenBuyable', () => { + const getCurrentChainIdMock = jest.mocked(getCurrentChainId); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should return true when current chain is buyable', () => { + getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.MAINNET); + const state = store.getState(); + expect(getIsNativeTokenBuyable(state)).toEqual(true); + }); + + it('should return false when current chain is not buyable', () => { + getCurrentChainIdMock.mockReturnValue(CHAIN_IDS.GOERLI); + const state = store.getState(); + expect(getIsNativeTokenBuyable(state)).toEqual(false); + }); + }); +}); diff --git a/ui/ducks/ramps/ramps.ts b/ui/ducks/ramps/ramps.ts new file mode 100644 index 000000000000..8b74a2d2b800 --- /dev/null +++ b/ui/ducks/ramps/ramps.ts @@ -0,0 +1,58 @@ +import { createSelector } from 'reselect'; +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { getCurrentChainId } from '../../selectors'; +import RampAPI from '../../hooks/useRamps/rampAPI'; +import { hexToDecimal } from '../../../shared/modules/conversion.utils'; +import { defaultBuyableChains } from './constants'; +import { AggregatorNetwork } from './types'; + +export const fetchBuyableChains = createAsyncThunk( + 'ramps/fetchBuyableChains', + async () => { + return await RampAPI.getNetworks(); + }, +); + +const rampsSlice = createSlice({ + name: 'ramps', + initialState: { + buyableChains: defaultBuyableChains, + }, + reducers: { + setBuyableChains: (state, action) => { + state.buyableChains = action.payload; + }, + }, + extraReducers: (builder) => { + builder + .addCase(fetchBuyableChains.fulfilled, (state, action) => { + const networks = action.payload; + if (networks && networks.length > 0) { + state.buyableChains = action.payload; + } else { + state.buyableChains = defaultBuyableChains; + } + }) + .addCase(fetchBuyableChains.rejected, (state) => { + state.buyableChains = defaultBuyableChains; + }); + }, +}); + +const { reducer } = rampsSlice; + +// Can be typed to RootState if/when the interface is defined +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getBuyableChains = (state: any) => state.ramps.buyableChains; + +export const getIsNativeTokenBuyable = createSelector( + [getCurrentChainId, getBuyableChains], + (currentChainId, buyableChains) => { + return buyableChains.some( + (network: AggregatorNetwork) => + String(network.chainId) === hexToDecimal(currentChainId), + ); + }, +); + +export default reducer; diff --git a/ui/ducks/ramps/types.ts b/ui/ducks/ramps/types.ts new file mode 100644 index 000000000000..0d4373e20b43 --- /dev/null +++ b/ui/ducks/ramps/types.ts @@ -0,0 +1,6 @@ +export type AggregatorNetwork = { + active: boolean; + chainId: number; + chainName: string; + nativeTokenSupported: boolean; +}; diff --git a/ui/hooks/experiences/useRamps.test.js b/ui/hooks/experiences/useRamps.test.js index dcb1dc2eb43c..4f473e6c83a5 100644 --- a/ui/hooks/experiences/useRamps.test.js +++ b/ui/hooks/experiences/useRamps.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { renderHook } from '@testing-library/react-hooks'; import configureStore from '../../store/store'; -import useRamps, { RampsMetaMaskEntry } from './useRamps'; +import useRamps, { RampsMetaMaskEntry } from '../useRamps/useRamps'; const mockedMetametricsId = '0xtestMetaMetricsId'; diff --git a/ui/hooks/useRamps/rampAPI.test.ts b/ui/hooks/useRamps/rampAPI.test.ts new file mode 100644 index 000000000000..54d29d1f9847 --- /dev/null +++ b/ui/hooks/useRamps/rampAPI.test.ts @@ -0,0 +1,22 @@ +import nock from 'nock'; +import { defaultBuyableChains } from '../../ducks/ramps/constants'; +import rampAPI from './rampAPI'; + +const mockedResponse = { + networks: defaultBuyableChains, +}; + +describe('rampAPI', () => { + afterEach(() => { + nock.cleanAll(); + }); + + it('should fetch networks', async () => { + nock('https://on-ramp.dev.mmcx.codefi.network') + .get('/regions/networks') + .query(true) + .reply(200, mockedResponse); + const result = await rampAPI.getNetworks(); + expect(result).toEqual(mockedResponse.networks); + }); +}); diff --git a/ui/hooks/useRamps/rampAPI.ts b/ui/hooks/useRamps/rampAPI.ts new file mode 100644 index 000000000000..089028515951 --- /dev/null +++ b/ui/hooks/useRamps/rampAPI.ts @@ -0,0 +1,24 @@ +import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; +import { AggregatorNetwork } from '../../ducks/ramps/types'; +import { defaultBuyableChains } from '../../ducks/ramps/constants'; + +const fetchWithTimeout = getFetchWithTimeout(); + +const rampApiBaseUrl = + process.env.METAMASK_RAMP_API_BASE_URL || + 'https://on-ramp-content.metaswap.codefi.network'; + +const RampAPI = { + async getNetworks(): Promise { + try { + const url = `${rampApiBaseUrl}/regions/networks?context=extension`; + const response = await fetchWithTimeout(url); + const { networks } = await response.json(); + return networks; + } catch (error) { + return defaultBuyableChains; + } + }, +}; + +export default RampAPI; diff --git a/ui/hooks/useRamps/useRamps.test.tsx b/ui/hooks/useRamps/useRamps.test.tsx new file mode 100644 index 000000000000..5dec2adfd2f5 --- /dev/null +++ b/ui/hooks/useRamps/useRamps.test.tsx @@ -0,0 +1,109 @@ +import React, { FC } from 'react'; +import { Provider } from 'react-redux'; +import { renderHook } from '@testing-library/react-hooks'; +import configureStore from '../../store/store'; +import useRamps, { RampsMetaMaskEntry } from './useRamps'; + +const mockedMetametricsId = '0xtestMetaMetricsId'; + +let mockStoreState = { + metamask: { + providerConfig: { + chainId: '0x1', + }, + metaMetricsId: mockedMetametricsId, + }, +}; + +const wrapper: FC = ({ children }) => ( + {children} +); + +describe('useRamps', () => { + // mock the openTab function to test if it is called with the correct URL when opening the Pdapp + beforeAll(() => { + Object.defineProperty(global, 'platform', { + value: { + openTab: jest.fn(), + }, + }); + }); + + it('should default the metamask entry param when opening the buy crypto URL', () => { + const metaMaskEntry = 'ext_buy_sell_button'; + const mockChainId = '0x1'; + + mockStoreState = { + ...mockStoreState, + metamask: { + ...mockStoreState.metamask, + providerConfig: { + chainId: mockChainId, + }, + }, + }; + + const mockBuyURI = `${process.env.PORTFOLIO_URL}/buy?metamaskEntry=${metaMaskEntry}&chainId=${mockChainId}&metametricsId=${mockedMetametricsId}`; + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + + const { result } = renderHook(() => useRamps(), { wrapper }); // default metamask entry + + result.current.openBuyCryptoInPdapp(); + expect(openTabSpy).toHaveBeenCalledWith({ + url: mockBuyURI, + }); + }); + + it('should use the correct metamask entry param when opening the buy crypto URL', () => { + const metaMaskEntry = 'ext_buy_banner_tokens'; + const mockChainId = '0x1'; + + mockStoreState = { + ...mockStoreState, + metamask: { + ...mockStoreState.metamask, + providerConfig: { + chainId: mockChainId, + }, + }, + }; + + const mockBuyURI = `${process.env.PORTFOLIO_URL}/buy?metamaskEntry=${metaMaskEntry}&chainId=${mockChainId}&metametricsId=${mockedMetametricsId}`; + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + + const { result } = renderHook( + () => useRamps(RampsMetaMaskEntry.TokensBanner), + { wrapper }, + ); + + result.current.openBuyCryptoInPdapp(); + expect(openTabSpy).toHaveBeenCalledWith({ + url: mockBuyURI, + }); + }); + + it.each(['0x1', '0x38', '0xa'])( + 'should open the buy crypto URL with the currently connected chain ID', + (mockChainId) => { + mockStoreState = { + ...mockStoreState, + metamask: { + ...mockStoreState.metamask, + providerConfig: { + chainId: mockChainId, + }, + }, + }; + + const mockBuyURI = `${process.env.PORTFOLIO_URL}/buy?metamaskEntry=ext_buy_sell_button&chainId=${mockChainId}&metametricsId=${mockedMetametricsId}`; + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + const { result } = renderHook(() => useRamps(), { wrapper }); + + result.current.openBuyCryptoInPdapp(); + + expect(openTabSpy).toHaveBeenCalledWith({ + url: mockBuyURI, + }); + }, + ); +}); diff --git a/ui/hooks/experiences/useRamps.ts b/ui/hooks/useRamps/useRamps.ts similarity index 100% rename from ui/hooks/experiences/useRamps.ts rename to ui/hooks/useRamps/useRamps.ts diff --git a/ui/hooks/useTheme.test.ts b/ui/hooks/useTheme.test.ts index 8b0f2edd66ac..5b44d2c918eb 100644 --- a/ui/hooks/useTheme.test.ts +++ b/ui/hooks/useTheme.test.ts @@ -3,6 +3,7 @@ import { renderHookWithProvider } from '../../test/lib/render-helpers'; import { useTheme } from './useTheme'; jest.mock('../selectors', () => ({ + ...jest.requireActual('../selectors'), getTheme: jest.fn(), })); diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js index 82fd617131b7..9450e1622dba 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.component.js @@ -45,12 +45,11 @@ import { getAccountName, getAddressBookEntry, getInternalAccounts, - getIsBuyableChain, getMetadataContractName, getNetworkIdentifier, getSwapsDefaultToken, } from '../../../../selectors'; -import useRamps from '../../../../hooks/experiences/useRamps'; +import useRamps from '../../../../hooks/useRamps/useRamps'; ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) import { MetaMetricsContext } from '../../../../contexts/metametrics'; import { @@ -60,6 +59,7 @@ import { ///: END:ONLY_INCLUDE_IF import { BlockaidResultType } from '../../../../../shared/constants/security-provider'; +import { getIsNativeTokenBuyable } from '../../../../ducks/ramps'; import { ConfirmPageContainerHeader, ConfirmPageContainerContent, @@ -122,7 +122,7 @@ const ConfirmPageContainer = (props) => { const [isShowingTxInsightWarnings, setIsShowingTxInsightWarnings] = useState(false); ///: END:ONLY_INCLUDE_IF - const isBuyableChain = useSelector(getIsBuyableChain); + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const contact = useSelector((state) => getAddressBookEntry(state, toAddress)); const networkIdentifier = useSelector(getNetworkIdentifier); const defaultToken = useSelector(getSwapsDefaultToken); @@ -135,10 +135,6 @@ const ConfirmPageContainer = (props) => { getMetadataContractName(state, toAddress), ); - // TODO: Move useRamps hook to the confirm-transaction-base parent component. - // TODO: openBuyCryptoInPdapp should be passed to this component as a custom prop. - // We try to keep this component for layout purpose only, we need to move this hook to the confirm-transaction-base parent - // component once it is converted to a functional component const { openBuyCryptoInPdapp } = useRamps(); const isSetApproveForAll = diff --git a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.container.js b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.container.js index be3ba7793eaf..db07ad4117e7 100644 --- a/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.container.js +++ b/ui/pages/confirmations/components/confirm-page-container/confirm-page-container.container.js @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; import { getAddressBookEntry, - getIsBuyableChain, getNetworkIdentifier, getSwapsDefaultToken, getMetadataContractName, @@ -12,7 +11,6 @@ import ConfirmPageContainer from './confirm-page-container.component'; function mapStateToProps(state, ownProps) { const to = ownProps.toAddress; - const isBuyableChain = getIsBuyableChain(state); const contact = getAddressBookEntry(state, to); const networkIdentifier = getNetworkIdentifier(state); const defaultToken = getSwapsDefaultToken(state); @@ -23,7 +21,6 @@ function mapStateToProps(state, ownProps) { const toMetadataName = getMetadataContractName(state, to); return { - isBuyableChain, contact, toName, toMetadataName, diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js index d0c4cf629073..9c9d5a18c574 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.container.js @@ -47,7 +47,6 @@ import { doesAddressRequireLedgerHidConnection, getTokenList, getIsMultiLayerFeeNetwork, - getIsBuyableChain, getEnsResolutionByAddress, getUnapprovedTransaction, getFullTxData, @@ -111,6 +110,7 @@ import { showCustodyConfirmLink } from '../../../store/institutional/institution import { getTokenAddressParam } from '../../../helpers/utils/token-util'; import { calcGasTotal } from '../../../../shared/lib/transactions-controller-utils'; import { subtractHexes } from '../../../../shared/modules/conversion.utils'; +import { getIsNativeTokenBuyable } from '../../../ducks/ramps'; import ConfirmTransactionBase from './confirm-transaction-base.component'; let customNonceValue = ''; @@ -146,7 +146,7 @@ const mapStateToProps = (state, ownProps) => { const isGasEstimatesLoading = getIsGasEstimatesLoading(state); const gasLoadingAnimationIsShowing = getGasLoadingAnimationIsShowing(state); - const isBuyableChain = getIsBuyableChain(state); + const isBuyableChain = getIsNativeTokenBuyable(state); const { confirmTransaction, metamask } = state; const conversionRate = getConversionRate(state); const { addressBook, nextNonce } = metamask; diff --git a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js index d399bcad2073..fe702474add3 100644 --- a/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js +++ b/ui/pages/confirmations/confirm-transaction-base/confirm-transaction-base.test.js @@ -27,6 +27,7 @@ import { BlockaidReason, BlockaidResultType, } from '../../../../shared/constants/security-provider'; +import { defaultBuyableChains } from '../../../ducks/ramps/constants'; import ConfirmTransactionBase from './confirm-transaction-base.container'; jest.mock('../components/simulation-details/useSimulationMetrics'); @@ -210,6 +211,9 @@ const baseStore = { appState: { sendInputCurrencySwitched: false, }, + ramps: { + buyableChains: defaultBuyableChains, + }, }; const mockedStoreWithConfirmTxParams = ( @@ -572,6 +576,7 @@ describe('Confirm Transaction Base', () => { it('handleMMISubmit calls sendTransaction correctly and then showCustodianDeepLink', async () => { const state = { + ...baseStore, appState: { ...baseStore.appState, gasLoadingAnimationIsShowing: false, diff --git a/ui/pages/confirmations/hooks/useTransactionFunction.test.js b/ui/pages/confirmations/hooks/useTransactionFunction.test.js index cc4e69863892..22984ba71a37 100644 --- a/ui/pages/confirmations/hooks/useTransactionFunction.test.js +++ b/ui/pages/confirmations/hooks/useTransactionFunction.test.js @@ -22,6 +22,7 @@ useGasEstimates.mockImplementation(() => FEE_MARKET_ESTIMATE_RETURN_VALUE); jest.mock('../../../selectors', () => ({ checkNetworkAndAccountSupports1559: () => true, + getCurrentChainId: jest.fn().mockReturnValue('0x1'), })); const wrapper = ({ children }) => ( diff --git a/ui/pages/confirmations/send/gas-display/gas-display.js b/ui/pages/confirmations/send/gas-display/gas-display.js index fad12aecbe79..53baa2eeb75f 100644 --- a/ui/pages/confirmations/send/gas-display/gas-display.js +++ b/ui/pages/confirmations/send/gas-display/gas-display.js @@ -23,7 +23,6 @@ import TransactionDetail from '../../components/transaction-detail'; import ActionableMessage from '../../../../components/ui/actionable-message'; import { getPreferences, - getIsBuyableChain, transactionFeeSelector, getIsTestnet, getUseCurrencyRateCheck, @@ -46,7 +45,8 @@ import { MetaMetricsEventName, } from '../../../../../shared/constants/metametrics'; import { MetaMetricsContext } from '../../../../contexts/metametrics'; -import useRamps from '../../../../hooks/experiences/useRamps'; +import useRamps from '../../../../hooks/useRamps/useRamps'; +import { getIsNativeTokenBuyable } from '../../../../ducks/ramps'; export default function GasDisplay({ gasError }) { const t = useContext(I18nContext); @@ -58,7 +58,7 @@ export default function GasDisplay({ gasError }) { const providerConfig = useSelector(getProviderConfig); const isTestnet = useSelector(getIsTestnet); - const isBuyableChain = useSelector(getIsBuyableChain); + const isBuyableChain = useSelector(getIsNativeTokenBuyable); const draftTransaction = useSelector(getCurrentDraftTransaction); const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck); const { showFiatInTestnets, useNativeCurrencyAsPrimaryCurrency } = diff --git a/ui/pages/confirmations/send/send.test.js b/ui/pages/confirmations/send/send.test.js index 195038ce8091..1f5af441e502 100644 --- a/ui/pages/confirmations/send/send.test.js +++ b/ui/pages/confirmations/send/send.test.js @@ -16,6 +16,7 @@ import { renderWithProvider } from '../../../../test/jest'; import { GasEstimateTypes } from '../../../../shared/constants/gas'; import { KeyringType } from '../../../../shared/constants/keyring'; import { INITIAL_SEND_STATE_FOR_EXISTING_DRAFT } from '../../../../test/jest/mocks'; +import { defaultBuyableChains } from '../../../ducks/ramps/constants'; import Send from './send'; const middleware = [thunk]; @@ -159,6 +160,9 @@ const baseStore = { appState: { sendInputCurrencySwitched: false, }, + ramps: { + buyableChains: defaultBuyableChains, + }, }; const placeholderText = 'Enter public address (0x) or ENS name'; diff --git a/ui/pages/home/home.component.js b/ui/pages/home/home.component.js index 856242d0bded..51b5f2d73116 100644 --- a/ui/pages/home/home.component.js +++ b/ui/pages/home/home.component.js @@ -26,8 +26,8 @@ import ConnectedSites from '../connected-sites'; import ConnectedAccounts from '../connected-accounts'; import { Tabs, Tab } from '../../components/ui/tabs'; import { EthOverview } from '../../components/app/wallet-overview'; - import ActionableMessage from '../../components/ui/actionable-message/actionable-message'; + import { FontWeight, Display, @@ -208,6 +208,7 @@ export default class Home extends PureComponent { custodianDeepLink: PropTypes.object, accountType: PropTypes.string, ///: END:ONLY_INCLUDE_IF + fetchBuyableChains: PropTypes.func.isRequired, }; state = { @@ -347,6 +348,8 @@ export default class Home extends PureComponent { setWaitForConfirmDeepLinkDialog(false); }); ///: END:ONLY_INCLUDE_IF + + this.props.fetchBuyableChains(); } static getDerivedStateFromProps(props) { diff --git a/ui/pages/home/home.container.js b/ui/pages/home/home.container.js index f34b93b5f16e..977748bd6b9b 100644 --- a/ui/pages/home/home.container.js +++ b/ui/pages/home/home.container.js @@ -72,6 +72,7 @@ import { import { hideWhatsNewPopup } from '../../ducks/app/app'; import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask'; import { getSwapsFeatureIsLive } from '../../ducks/swaps/swaps'; +import { fetchBuyableChains } from '../../ducks/ramps'; import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { getIsBrowserDeprecated } from '../../helpers/utils/util'; import { @@ -274,6 +275,7 @@ const mapDispatchToProps = (dispatch) => { ///: END:ONLY_INCLUDE_IF setSurveyLinkLastClickedOrClosed: (time) => dispatch(setSurveyLinkLastClickedOrClosed(time)), + fetchBuyableChains: () => dispatch(fetchBuyableChains()), }; }; diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js index e684fedc32cb..72c8b0c2f9e9 100644 --- a/ui/pages/routes/routes.component.test.js +++ b/ui/pages/routes/routes.component.test.js @@ -1,7 +1,7 @@ import React from 'react'; import configureMockStore from 'redux-mock-store'; import { act, fireEvent } from '@testing-library/react'; - +import thunk from 'redux-thunk'; import { SEND_STAGES } from '../../ducks/send'; import { renderWithProvider } from '../../../test/jest'; import mockSendState from '../../../test/data/mock-send-state.json'; @@ -13,6 +13,8 @@ import { import { useIsOriginalNativeTokenSymbol } from '../../hooks/useIsOriginalNativeTokenSymbol'; import Routes from '.'; +const middlewares = [thunk]; + const mockShowNetworkDropdown = jest.fn(); const mockHideNetworkDropdown = jest.fn(); @@ -27,6 +29,7 @@ jest.mock('webextension-polyfill', () => ({ })); jest.mock('../../store/actions', () => ({ + ...jest.requireActual('../../store/actions'), getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()), gasFeeStartPollingByNetworkClientId: jest .fn() @@ -68,7 +71,7 @@ jest.mock( ); const render = async (route, state) => { - const store = configureMockStore()({ + const store = configureMockStore(middlewares)({ ...mockSendState, ...state, }); diff --git a/ui/pages/swaps/prepare-swap-page/review-quote.js b/ui/pages/swaps/prepare-swap-page/review-quote.js index b1d68921358f..99273dcfaba6 100644 --- a/ui/pages/swaps/prepare-swap-page/review-quote.js +++ b/ui/pages/swaps/prepare-swap-page/review-quote.js @@ -139,7 +139,7 @@ import { addHexPrefix } from '../../../../app/scripts/lib/util'; import { calcTokenValue } from '../../../../shared/lib/swaps-utils'; import ExchangeRateDisplay from '../exchange-rate-display'; import InfoTooltip from '../../../components/ui/info-tooltip'; -import useRamps from '../../../hooks/experiences/useRamps'; +import useRamps from '../../../hooks/useRamps/useRamps'; import ViewQuotePriceDifference from './view-quote-price-difference'; import SlippageNotificationModal from './slippage-notification-modal'; diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index ff0b686dec0d..de74ccdb92bb 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -16,7 +16,6 @@ import { TransactionStatus } from '@metamask/transaction-controller'; import { addHexPrefix, getEnvironmentType } from '../../app/scripts/lib/util'; import { TEST_CHAINS, - BUYABLE_CHAINS_MAP, MAINNET_DISPLAY_NAME, BSC_DISPLAY_NAME, POLYGON_DISPLAY_NAME, @@ -1276,11 +1275,6 @@ export function getIsBridgeChain(state) { const chainId = getCurrentChainId(state); return ALLOWED_BRIDGE_CHAIN_IDS.includes(chainId); } - -export function getIsBuyableChain(state) { - const chainId = getCurrentChainId(state); - return Object.keys(BUYABLE_CHAINS_MAP).includes(chainId); -} export function getNativeCurrencyImage(state) { const chainId = getCurrentChainId(state); return CHAIN_ID_TOKEN_IMAGE_MAP[chainId];