From 7a2e679a098d5c0d9de117432ffb80b10843870d Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 22 Sep 2025 11:07:16 -0700 Subject: [PATCH 1/8] wip --- .../app/wallet-overview/coin-buttons.tsx | 9 ++- ui/ducks/bridge/selectors.ts | 69 ++++++++++++++++++- ui/hooks/bridge/useBridgeQueryParams.ts | 3 + ui/hooks/bridge/useBridging.ts | 48 ++++++++++--- 4 files changed, 113 insertions(+), 16 deletions(-) diff --git a/ui/components/app/wallet-overview/coin-buttons.tsx b/ui/components/app/wallet-overview/coin-buttons.tsx index fa06b3646f7d..d2c36c7af685 100644 --- a/ui/components/app/wallet-overview/coin-buttons.tsx +++ b/ui/components/app/wallet-overview/coin-buttons.tsx @@ -9,7 +9,7 @@ import { getNativeAssetForChainId } from '@metamask/bridge-controller'; import { isEvmAccountType } from '@metamask/keyring-api'; import { InternalAccount } from '@metamask/keyring-internal-api'; ///: END:ONLY_INCLUDE_IF -import { ChainId } from '../../../../shared/constants/network'; +import { CHAIN_IDS, ChainId } from '../../../../shared/constants/network'; import { I18nContext } from '../../../contexts/i18n'; @@ -328,11 +328,14 @@ const CoinButtons = ({ const handleBridgeOnClick = useCallback( async (isSwap: boolean) => { - await setCorrectChain(); // Handle clicking from the wallet overview page openBridgeExperience( MetaMetricsSwapsEventSource.MainView, - getNativeAssetForChainId(chainId), + // TODO sync this with token filter or selectedChainId + getNativeAssetForChainId( + location.pathname.split('/').filter(Boolean).at(-1) ?? + CHAIN_IDS.MAINNET, + ), isSwap, ); }, diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index c7efac2622d2..b82c12bd59af 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -24,7 +24,12 @@ import { createSelector } from 'reselect'; import type { GasFeeState } from '@metamask/gas-fee-controller'; import { BigNumber } from 'bignumber.js'; import { calcTokenAmount } from '@metamask/notification-services-controller/push-services'; -import type { CaipAssetType, CaipChainId, Hex } from '@metamask/utils'; +import { + parseCaipChainId, + type CaipAssetType, + type CaipChainId, + type Hex, +} from '@metamask/utils'; import type { CurrencyRateState, MultichainAssetsControllerState, @@ -182,9 +187,13 @@ export const getFromChains = createDeepEqualSelector( ); export const getFromChain = createDeepEqualSelector( - getMultichainProviderConfig, + getMultichainProviderConfig, // TODO remove this reference + // TODO should be based on fromToken getFromChains, (providerConfig, fromChains) => { + // TODO move this to bridge state? + // TODO return null initially, wait for token selection then set to token's chainId + // otherwise should use EVM return fromChains.find(({ chainId }) => chainId === providerConfig.chainId); }, ); @@ -228,6 +237,60 @@ export const getToChain = createSelector( : fromChain, ); +export const getDefaultTokenPair = createDeepEqualSelector( + [ + (state) => getFromChain(state)?.chainId, + (state) => + // @ts-expect-error will be fixed when controller is updated + getBridgeFeatureFlags(state).bip44DefaultPairs, + // TODO remove fallback and update LD to this + // ({ + // bip122: { + // other: {}, + // standard: { + // 'bip122:000000000019d6689c085ae165831e93/slip44:0': + // 'eip155:1/slip44:60', + // }, + // }, + // eip155: { + // other: {}, + // standard: { + // 'eip155:1/slip44:60': + // // 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + // 'eip155:1/erc20:0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', + // }, + // }, + // solana: { + // other: {}, + // standard: { + // 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': + // 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + // }, + // }, + // }), + ], + (fromChainId, bip44DefaultPairs): null | [CaipAssetType, CaipAssetType] => { + console.log('=====getDefaultTokenPair', fromChainId); + if (!fromChainId) { + return null; + } + const { namespace } = parseCaipChainId(formatChainIdToCaip(fromChainId)); + // console.log( + // '=====getDefaultTokenPair namespace', + // namespace, + // bip44DefaultPairs, + // ); + const defaultTokenPair = bip44DefaultPairs[namespace].standard; + if (defaultTokenPair) { + return Object.entries(defaultTokenPair).flat() as [ + CaipAssetType, + CaipAssetType, + ]; + } + return null; + }, +); + export const getFromToken = createSelector( [(state: BridgeAppState) => state.bridge.fromToken, getFromChain], (fromToken, fromChain) => { @@ -260,6 +323,8 @@ export const getToToken = createSelector( if (toToken) { return toToken; } + console.log('=====returning hardcoded toToken'); + // TODO if fromToken is in bip44Defaults, return its mapped toToken // Otherwise, determine the default token to use based on fromToken and toChain const defaultToken = getDefaultToToken(toChain, fromToken); return defaultToken ? toBridgeToken(defaultToken) : null; diff --git a/ui/hooks/bridge/useBridgeQueryParams.ts b/ui/hooks/bridge/useBridgeQueryParams.ts index 8d8c8d75ae7e..17ded85579ac 100644 --- a/ui/hooks/bridge/useBridgeQueryParams.ts +++ b/ui/hooks/bridge/useBridgeQueryParams.ts @@ -150,6 +150,7 @@ export const useBridgeQueryParams = () => { if (searchParamsTo?.assetId || searchParamsFrom?.assetId) { abortController.current.abort(); abortController.current = new AbortController(); + console.log('=====fetching asset metadata'); fetchAssetMetadata( abortController.current.signal, searchParamsFrom?.assetId, @@ -173,6 +174,7 @@ export const useBridgeQueryParams = () => { const { chainId: fromChainId } = fromAsset; if (fromTokenMetadata) { + console.log('=====setting fromToken', fromTokenMetadata.symbol); const { chainId, assetReference } = parseCaipAssetType( fromTokenMetadata.assetId, ); @@ -214,6 +216,7 @@ export const useBridgeQueryParams = () => { const { chainId, assetReference } = parseCaipAssetType( toTokenMetadata.assetId, ); + console.log('=====setting toToken', toTokenMetadata.symbol); dispatch( setToToken({ ...toTokenMetadata, diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index 101d21f66814..c8fe45547e7a 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -34,6 +34,7 @@ import { trace, TraceName } from '../../../shared/lib/trace'; import { toAssetId } from '../../../shared/lib/asset-utils'; import { ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP } from '../../../shared/constants/bridge'; import { getMultichainProviderConfig } from '../../selectors/multichain'; +import { getDefaultTokenPair } from '../../ducks/bridge/selectors'; const useBridging = () => { const history = useHistory(); @@ -49,6 +50,10 @@ const useBridging = () => { const isBridgeChain = useSelector((state) => getIsBridgeChain(state, providerConfig?.chainId), ); + + // TODO should set default toToken here too + const defaultTokenPair = useSelector(getDefaultTokenPair); + const openBridgeExperience = useCallback( ( location: MetaMetricsSwapsEventSource | 'Carousel', @@ -57,19 +62,30 @@ const useBridging = () => { }, isSwap = false, ) => { - const token = - srcToken ?? getNativeAssetForChainId(providerConfig.chainId); + console.log('=====useBridging', defaultTokenPair); + // TODO if no network filter, default to ethereum + // TODO if no srcToken, get default + const token = srcToken; + const isBridgeToken = token?.chainId && ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP.includes( formatChainIdToCaip(token.chainId), ); - const isChainOrTokenSupported = isBridgeChain || isBridgeToken; + + const isChainOrTokenSupported = + isBridgeChain || isBridgeToken || defaultTokenPair; if (!isChainOrTokenSupported || !providerConfig) { return; } + // TODO if srcToken is defined, use getDefaultToToken + // const defaultToken = getDefaultToToken(token.chainId, fromToken); + // const defaultDestToken ? toBridgeToken(defaultToken) : null; + + const [defaultSrcAssetId, defaultDestAssetId] = defaultTokenPair ?? []; + trace({ name: isSwap ? TraceName.SwapViewLoaded : TraceName.BridgeViewLoaded, startTime: Date.now(), @@ -82,7 +98,7 @@ const useBridging = () => { properties: { // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention - token_symbol: token.symbol, + token_symbol: token?.symbol ?? defaultSrcAssetId ?? '', location, text: isSwap ? 'Swap' : 'Bridge', // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 @@ -95,19 +111,28 @@ const useBridging = () => { location: location as never, // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention - token_symbol_source: token.symbol, + token_symbol_source: token?.symbol ?? defaultSrcAssetId ?? '', // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention - token_symbol_destination: null, + token_symbol_destination: defaultDestAssetId ?? '', }), ); dispatch(resetInputFields()); let url = `${CROSS_CHAIN_SWAP_ROUTE}${PREPARE_SWAP_ROUTE}`; - const assetId = toAssetId( - token.address, - formatChainIdToCaip(token.chainId ?? providerConfig.chainId), - ); - url += `?${BridgeQueryParams.FROM}=${assetId}`; + const assetId = + (token + ? toAssetId( + token.address, + formatChainIdToCaip(token.chainId ?? providerConfig.chainId), + ) + : defaultSrcAssetId) ?? + getNativeAssetForChainId(providerConfig.chainId)?.assetId; + if (assetId) { + url += `?${BridgeQueryParams.FROM}=${assetId}`; + } + if (defaultDestAssetId && !srcToken) { + url += `&${BridgeQueryParams.TO}=${defaultDestAssetId}`; + } if (isSwap) { url += `&${BridgeQueryParams.SWAPS}=true`; } @@ -121,6 +146,7 @@ const useBridging = () => { isMetaMetricsEnabled, isMarketingEnabled, providerConfig, + defaultTokenPair, ], ); From 172fea618b92aabe76e41c4da9815efb605bf236 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 22 Sep 2025 11:43:12 -0700 Subject: [PATCH 2/8] refactor: useBridging --- ui/hooks/bridge/useBridging.ts | 52 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index c8fe45547e7a..63bdd84c467e 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -35,6 +35,7 @@ import { toAssetId } from '../../../shared/lib/asset-utils'; import { ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP } from '../../../shared/constants/bridge'; import { getMultichainProviderConfig } from '../../selectors/multichain'; import { getDefaultTokenPair } from '../../ducks/bridge/selectors'; +import { getDefaultToToken } from '../../ducks/bridge/utils'; const useBridging = () => { const history = useHistory(); @@ -65,27 +66,36 @@ const useBridging = () => { console.log('=====useBridging', defaultTokenPair); // TODO if no network filter, default to ethereum // TODO if no srcToken, get default - const token = srcToken; + const [defaultBip44SrcAssetId, defaultBip44DestAssetId] = + defaultTokenPair ?? []; + const srcAssetIdToUse = + (srcToken + ? toAssetId( + srcToken.address, + formatChainIdToCaip(srcToken.chainId ?? providerConfig.chainId), + ) + : defaultBip44SrcAssetId) ?? + getNativeAssetForChainId(providerConfig.chainId)?.assetId; + // TODO use hardcoded dest token if srcToken is defined + const destAssetIdToUse = srcToken ? null : defaultBip44DestAssetId; + + // TODO if srcToken is defined, use getDefaultToToken + // const defaultToken = srcToken? getDefaultToToken(srcToken.chainId, srcToken) : getDefaultToToken(providerConfig.chainId, fromToken); + // const defaultDestToken ? toBridgeToken(defaultToken) : null; const isBridgeToken = - token?.chainId && + srcToken?.chainId && ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP.includes( - formatChainIdToCaip(token.chainId), + formatChainIdToCaip(srcToken.chainId), ); const isChainOrTokenSupported = - isBridgeChain || isBridgeToken || defaultTokenPair; + isBridgeChain || isBridgeToken || srcAssetIdToUse; if (!isChainOrTokenSupported || !providerConfig) { return; } - // TODO if srcToken is defined, use getDefaultToToken - // const defaultToken = getDefaultToToken(token.chainId, fromToken); - // const defaultDestToken ? toBridgeToken(defaultToken) : null; - - const [defaultSrcAssetId, defaultDestAssetId] = defaultTokenPair ?? []; - trace({ name: isSwap ? TraceName.SwapViewLoaded : TraceName.BridgeViewLoaded, startTime: Date.now(), @@ -98,7 +108,7 @@ const useBridging = () => { properties: { // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention - token_symbol: token?.symbol ?? defaultSrcAssetId ?? '', + token_symbol: srcToken?.symbol ?? defaultBip44SrcAssetId ?? '', location, text: isSwap ? 'Swap' : 'Bridge', // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 @@ -111,27 +121,19 @@ const useBridging = () => { location: location as never, // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention - token_symbol_source: token?.symbol ?? defaultSrcAssetId ?? '', + token_symbol_source: srcToken?.symbol ?? defaultBip44SrcAssetId ?? '', // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention - token_symbol_destination: defaultDestAssetId ?? '', + token_symbol_destination: destAssetIdToUse ?? '', }), ); dispatch(resetInputFields()); let url = `${CROSS_CHAIN_SWAP_ROUTE}${PREPARE_SWAP_ROUTE}`; - const assetId = - (token - ? toAssetId( - token.address, - formatChainIdToCaip(token.chainId ?? providerConfig.chainId), - ) - : defaultSrcAssetId) ?? - getNativeAssetForChainId(providerConfig.chainId)?.assetId; - if (assetId) { - url += `?${BridgeQueryParams.FROM}=${assetId}`; + if (srcAssetIdToUse) { + url += `?${BridgeQueryParams.FROM}=${srcAssetIdToUse}`; } - if (defaultDestAssetId && !srcToken) { - url += `&${BridgeQueryParams.TO}=${defaultDestAssetId}`; + if (destAssetIdToUse) { + url += `&${BridgeQueryParams.TO}=${destAssetIdToUse}`; } if (isSwap) { url += `&${BridgeQueryParams.SWAPS}=true`; From ae67e76067d62ed17467434f51e0eacb767f4923 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 22 Sep 2025 11:58:25 -0700 Subject: [PATCH 3/8] fix: if srcToken is present, add default toToken to URL --- shared/constants/bridge.ts | 22 +++++++++++----------- ui/ducks/bridge/selectors.ts | 5 ++++- ui/ducks/bridge/utils.ts | 11 ++++------- ui/hooks/bridge/useBridging.ts | 17 +++++++---------- 4 files changed, 26 insertions(+), 29 deletions(-) diff --git a/shared/constants/bridge.ts b/shared/constants/bridge.ts index 376787d2c0ed..bd1344ec5d82 100644 --- a/shared/constants/bridge.ts +++ b/shared/constants/bridge.ts @@ -88,7 +88,7 @@ export const STATIC_METAMASK_BASE_URL = 'https://static.cx.metamask.io'; export const BRIDGE_CHAINID_COMMON_TOKEN_PAIR: Partial< Record< - AllowedBridgeChainIds, + (typeof ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP)[number], { address: string; symbol: string; @@ -97,70 +97,70 @@ export const BRIDGE_CHAINID_COMMON_TOKEN_PAIR: Partial< } > > = { - [CHAIN_IDS.MAINNET]: { + [toEvmCaipChainId(CHAIN_IDS.MAINNET)]: { // ETH -> USDC on mainnet address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', symbol: 'USDC', decimals: 6, name: 'USD Coin', }, - [CHAIN_IDS.OPTIMISM]: { + [toEvmCaipChainId(CHAIN_IDS.OPTIMISM)]: { // ETH -> USDC on Optimism address: '0x0b2c639c533813f4aa9d7837caf62653d097ff85', symbol: 'USDC', decimals: 6, name: 'USD Coin', }, - [CHAIN_IDS.ARBITRUM]: { + [toEvmCaipChainId(CHAIN_IDS.ARBITRUM)]: { // ETH -> USDC on Arbitrum address: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', symbol: 'USDC', decimals: 6, name: 'USD Coin', }, - [CHAIN_IDS.BASE]: { + [toEvmCaipChainId(CHAIN_IDS.BASE)]: { // ETH -> USDC on Base address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', symbol: 'USDC', decimals: 6, name: 'USD Coin', }, - [CHAIN_IDS.POLYGON]: { + [toEvmCaipChainId(CHAIN_IDS.POLYGON)]: { // POL -> USDT on Polygon address: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', symbol: 'USDT', decimals: 6, name: 'Tether USD', }, - [CHAIN_IDS.BSC]: { + [toEvmCaipChainId(CHAIN_IDS.BSC)]: { // BNB -> USDT on BSC address: '0x55d398326f99059ff775485246999027b3197955', symbol: 'USDT', decimals: 18, name: 'Tether USD', }, - [CHAIN_IDS.AVALANCHE]: { + [toEvmCaipChainId(CHAIN_IDS.AVALANCHE)]: { // AVAX -> USDC on Avalanche address: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e', symbol: 'USDC', decimals: 6, name: 'USD Coin', }, - [CHAIN_IDS.ZKSYNC_ERA]: { + [toEvmCaipChainId(CHAIN_IDS.ZKSYNC_ERA)]: { // ETH -> USDT on zkSync Era address: '0x493257fd37edb34451f62edf8d2a0c418852ba4c', symbol: 'USDT', decimals: 6, name: 'Tether USD', }, - [CHAIN_IDS.LINEA_MAINNET]: { + [toEvmCaipChainId(CHAIN_IDS.LINEA_MAINNET)]: { // ETH -> USDC on Linea address: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff', symbol: 'USDC', decimals: 6, name: 'USD Coin', }, - [CHAIN_IDS.SEI]: { + [toEvmCaipChainId(CHAIN_IDS.SEI)]: { // SEI -> USDC on Sei address: '0x3894085Ef7Ff0f0aeDf52E2A2704928d1Ec074F1', symbol: 'USDC', diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index b82c12bd59af..4ce4b8b543a1 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -326,7 +326,10 @@ export const getToToken = createSelector( console.log('=====returning hardcoded toToken'); // TODO if fromToken is in bip44Defaults, return its mapped toToken // Otherwise, determine the default token to use based on fromToken and toChain - const defaultToken = getDefaultToToken(toChain, fromToken); + const defaultToken = getDefaultToToken( + formatChainIdToCaip(toChain.chainId), + fromToken, + ); return defaultToken ? toBridgeToken(defaultToken) : null; }, ); diff --git a/ui/ducks/bridge/utils.ts b/ui/ducks/bridge/utils.ts index cdebc063129e..1e7ea3a69b6f 100644 --- a/ui/ducks/bridge/utils.ts +++ b/ui/ducks/bridge/utils.ts @@ -266,7 +266,7 @@ const createBridgeTokenPayload = ( name?: string; assetId?: string; }, - chainId: ChainId | Hex, + chainId: ChainId | Hex | CaipChainId, ): TokenPayload['payload'] | null => { const { assetId, ...rest } = tokenData; return toBridgeToken({ @@ -276,13 +276,10 @@ const createBridgeTokenPayload = ( }; export const getDefaultToToken = ( - { chainId: targetChainId }: NetworkConfiguration | AddNetworkFields, - fromToken: NonNullable, + targetChainId: CaipChainId, + fromToken: Pick, 'address'>, ) => { - const commonPair = - BRIDGE_CHAINID_COMMON_TOKEN_PAIR[ - targetChainId as keyof typeof BRIDGE_CHAINID_COMMON_TOKEN_PAIR - ]; + const commonPair = BRIDGE_CHAINID_COMMON_TOKEN_PAIR[targetChainId]; if (commonPair) { // If source is native token, default to USDC on same chain diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index 63bdd84c467e..43273f16772c 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -35,7 +35,7 @@ import { toAssetId } from '../../../shared/lib/asset-utils'; import { ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP } from '../../../shared/constants/bridge'; import { getMultichainProviderConfig } from '../../selectors/multichain'; import { getDefaultTokenPair } from '../../ducks/bridge/selectors'; -import { getDefaultToToken } from '../../ducks/bridge/utils'; +import { getDefaultToToken, toBridgeToken } from '../../ducks/bridge/utils'; const useBridging = () => { const history = useHistory(); @@ -63,9 +63,6 @@ const useBridging = () => { }, isSwap = false, ) => { - console.log('=====useBridging', defaultTokenPair); - // TODO if no network filter, default to ethereum - // TODO if no srcToken, get default const [defaultBip44SrcAssetId, defaultBip44DestAssetId] = defaultTokenPair ?? []; const srcAssetIdToUse = @@ -76,12 +73,12 @@ const useBridging = () => { ) : defaultBip44SrcAssetId) ?? getNativeAssetForChainId(providerConfig.chainId)?.assetId; - // TODO use hardcoded dest token if srcToken is defined - const destAssetIdToUse = srcToken ? null : defaultBip44DestAssetId; - - // TODO if srcToken is defined, use getDefaultToToken - // const defaultToken = srcToken? getDefaultToToken(srcToken.chainId, srcToken) : getDefaultToToken(providerConfig.chainId, fromToken); - // const defaultDestToken ? toBridgeToken(defaultToken) : null; + // If srcToken is present, use the default dest token for that chain + const destAssetIdToUse = srcToken?.chainId + ? toBridgeToken( + getDefaultToToken(formatChainIdToCaip(srcToken.chainId), srcToken), + )?.assetId + : defaultBip44DestAssetId; const isBridgeToken = srcToken?.chainId && From 97db74a4b507b73cdea1a351863dccbe0c07262d Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 22 Sep 2025 13:05:51 -0700 Subject: [PATCH 4/8] chore: add comments --- ui/ducks/bridge/selectors.ts | 1 - ui/hooks/bridge/useBridging.ts | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 4ce4b8b543a1..1416d6f898cd 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -323,7 +323,6 @@ export const getToToken = createSelector( if (toToken) { return toToken; } - console.log('=====returning hardcoded toToken'); // TODO if fromToken is in bip44Defaults, return its mapped toToken // Otherwise, determine the default token to use based on fromToken and toChain const defaultToken = getDefaultToToken( diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index 43273f16772c..8104cbd9f3b3 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -32,6 +32,7 @@ import { import { BridgeQueryParams } from '../../../shared/lib/deep-links/routes/swap'; import { trace, TraceName } from '../../../shared/lib/trace'; import { toAssetId } from '../../../shared/lib/asset-utils'; +import { CHAIN_IDS } from '../../../shared/constants/network'; import { ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP } from '../../../shared/constants/bridge'; import { getMultichainProviderConfig } from '../../selectors/multichain'; import { getDefaultTokenPair } from '../../ducks/bridge/selectors'; @@ -52,7 +53,6 @@ const useBridging = () => { getIsBridgeChain(state, providerConfig?.chainId), ); - // TODO should set default toToken here too const defaultTokenPair = useSelector(getDefaultTokenPair); const openBridgeExperience = useCallback( @@ -65,14 +65,21 @@ const useBridging = () => { ) => { const [defaultBip44SrcAssetId, defaultBip44DestAssetId] = defaultTokenPair ?? []; + const srcAssetIdToUse = + // If srcToken is present, use the srcToken assetId (srcToken ? toAssetId( srcToken.address, formatChainIdToCaip(srcToken.chainId ?? providerConfig.chainId), ) - : defaultBip44SrcAssetId) ?? - getNativeAssetForChainId(providerConfig.chainId)?.assetId; + : // Otherwise, use the default bip44 src asset id + defaultBip44SrcAssetId) ?? + // Otherwise, use the native asset for Ethereum mainnet + // This should only happen if the feature flags are unavailable and the + // user clicks on Swap from the home page + getNativeAssetForChainId(CHAIN_IDS.MAINNET)?.assetId; + // If srcToken is present, use the default dest token for that chain const destAssetIdToUse = srcToken?.chainId ? toBridgeToken( From 0c27a4d52283f78f0479afb6423cdca60eb18a33 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Mon, 22 Sep 2025 15:00:56 -0700 Subject: [PATCH 5/8] chore: remove comments and logs --- .../app/wallet-overview/coin-buttons.tsx | 1 - ui/ducks/bridge/selectors.ts | 38 +------------------ ui/hooks/bridge/useBridgeQueryParams.ts | 3 -- 3 files changed, 1 insertion(+), 41 deletions(-) diff --git a/ui/components/app/wallet-overview/coin-buttons.tsx b/ui/components/app/wallet-overview/coin-buttons.tsx index d2c36c7af685..e19490e3f5ee 100644 --- a/ui/components/app/wallet-overview/coin-buttons.tsx +++ b/ui/components/app/wallet-overview/coin-buttons.tsx @@ -331,7 +331,6 @@ const CoinButtons = ({ // Handle clicking from the wallet overview page openBridgeExperience( MetaMetricsSwapsEventSource.MainView, - // TODO sync this with token filter or selectedChainId getNativeAssetForChainId( location.pathname.split('/').filter(Boolean).at(-1) ?? CHAIN_IDS.MAINNET, diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 1416d6f898cd..b684995d9af1 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -187,13 +187,8 @@ export const getFromChains = createDeepEqualSelector( ); export const getFromChain = createDeepEqualSelector( - getMultichainProviderConfig, // TODO remove this reference - // TODO should be based on fromToken - getFromChains, + [getMultichainProviderConfig, getFromChains], (providerConfig, fromChains) => { - // TODO move this to bridge state? - // TODO return null initially, wait for token selection then set to token's chainId - // otherwise should use EVM return fromChains.find(({ chainId }) => chainId === providerConfig.chainId); }, ); @@ -243,43 +238,12 @@ export const getDefaultTokenPair = createDeepEqualSelector( (state) => // @ts-expect-error will be fixed when controller is updated getBridgeFeatureFlags(state).bip44DefaultPairs, - // TODO remove fallback and update LD to this - // ({ - // bip122: { - // other: {}, - // standard: { - // 'bip122:000000000019d6689c085ae165831e93/slip44:0': - // 'eip155:1/slip44:60', - // }, - // }, - // eip155: { - // other: {}, - // standard: { - // 'eip155:1/slip44:60': - // // 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - // 'eip155:1/erc20:0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', - // }, - // }, - // solana: { - // other: {}, - // standard: { - // 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': - // 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - // }, - // }, - // }), ], (fromChainId, bip44DefaultPairs): null | [CaipAssetType, CaipAssetType] => { - console.log('=====getDefaultTokenPair', fromChainId); if (!fromChainId) { return null; } const { namespace } = parseCaipChainId(formatChainIdToCaip(fromChainId)); - // console.log( - // '=====getDefaultTokenPair namespace', - // namespace, - // bip44DefaultPairs, - // ); const defaultTokenPair = bip44DefaultPairs[namespace].standard; if (defaultTokenPair) { return Object.entries(defaultTokenPair).flat() as [ diff --git a/ui/hooks/bridge/useBridgeQueryParams.ts b/ui/hooks/bridge/useBridgeQueryParams.ts index 17ded85579ac..8d8c8d75ae7e 100644 --- a/ui/hooks/bridge/useBridgeQueryParams.ts +++ b/ui/hooks/bridge/useBridgeQueryParams.ts @@ -150,7 +150,6 @@ export const useBridgeQueryParams = () => { if (searchParamsTo?.assetId || searchParamsFrom?.assetId) { abortController.current.abort(); abortController.current = new AbortController(); - console.log('=====fetching asset metadata'); fetchAssetMetadata( abortController.current.signal, searchParamsFrom?.assetId, @@ -174,7 +173,6 @@ export const useBridgeQueryParams = () => { const { chainId: fromChainId } = fromAsset; if (fromTokenMetadata) { - console.log('=====setting fromToken', fromTokenMetadata.symbol); const { chainId, assetReference } = parseCaipAssetType( fromTokenMetadata.assetId, ); @@ -216,7 +214,6 @@ export const useBridgeQueryParams = () => { const { chainId, assetReference } = parseCaipAssetType( toTokenMetadata.assetId, ); - console.log('=====setting toToken', toTokenMetadata.symbol); dispatch( setToToken({ ...toTokenMetadata, From a1fe60a0072612d58781dc0603740f6c8444b2b0 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 23 Sep 2025 09:32:32 -0700 Subject: [PATCH 6/8] fix: tests --- ui/ducks/bridge/selectors.test.ts | 2 +- ui/ducks/bridge/selectors.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/ducks/bridge/selectors.test.ts b/ui/ducks/bridge/selectors.test.ts index 8412ebd05ddf..e85ae134a20a 100644 --- a/ui/ducks/bridge/selectors.test.ts +++ b/ui/ducks/bridge/selectors.test.ts @@ -505,7 +505,7 @@ describe('Bridge selectors', () => { address: '0xaca92e438df0b2401ff60da7e4337b687a2435da', assetId: 'eip155:1/erc20:0xaca92e438df0b2401ff60da7e4337b687a2435da', balance: '0', - chainId: '0x1', + chainId: 'eip155:1', decimals: 18, image: 'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/erc20/0xaca92e438df0b2401ff60da7e4337b687a2435da.png', diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index 32324854eb42..bec99ec94424 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -246,7 +246,7 @@ export const getDefaultTokenPair = createDeepEqualSelector( return null; } const { namespace } = parseCaipChainId(formatChainIdToCaip(fromChainId)); - const defaultTokenPair = bip44DefaultPairs[namespace].standard; + const defaultTokenPair = bip44DefaultPairs?.[namespace]?.standard; if (defaultTokenPair) { return Object.entries(defaultTokenPair).flat() as [ CaipAssetType, From 0d5aa7339ee150c8a421589fdc539103a4cbd346 Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 23 Sep 2025 10:14:01 -0700 Subject: [PATCH 7/8] test: add useBridging unit test --- ui/hooks/bridge/useBridging.test.ts | 97 ++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/ui/hooks/bridge/useBridging.test.ts b/ui/hooks/bridge/useBridging.test.ts index 6c67048e8c8c..d88be31a5428 100644 --- a/ui/hooks/bridge/useBridging.test.ts +++ b/ui/hooks/bridge/useBridging.test.ts @@ -20,8 +20,8 @@ jest.mock('../../ducks/bridge/actions', () => ({ const MOCK_METAMETRICS_ID = '0xtestMetaMetricsId'; -const renderUseBridging = (mockStoreState: object) => - renderHookWithProvider(() => useBridging(), mockStoreState); +const renderUseBridging = (mockStoreState: object, pathname?: string) => + renderHookWithProvider(() => useBridging(), mockStoreState, pathname); describe('useBridging', () => { beforeAll(() => { @@ -52,7 +52,7 @@ describe('useBridging', () => { false, ], [ - '/cross-chain/swaps/prepare-swap-page?from=eip155:10/erc20:0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d&swaps=true', + '/cross-chain/swaps/prepare-swap-page?from=eip155:10/erc20:0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d&to=eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85&swaps=true', { iconUrl: 'https://icon.url', symbol: 'TEST', @@ -110,5 +110,96 @@ describe('useBridging', () => { expect(openTabSpy).not.toHaveBeenCalled(); }, ); + + // @ts-expect-error This is missing from the Mocha type definitions + it.each([ + [ + '/', + '/cross-chain/swaps/prepare-swap-page?from=eip155:1/slip44:60&swaps=true', + undefined, + 'Home', + true, + ], + [ + '/asset/0xa/', + '/cross-chain/swaps/prepare-swap-page?from=eip155:1/slip44:60&swaps=true', + ETH_SWAPS_TOKEN_OBJECT, + MetaMetricsSwapsEventSource.TokenView, + true, + ], + [ + '/asset/0xa/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d', + '/cross-chain/swaps/prepare-swap-page?from=eip155:10/erc20:0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d&to=eip155:10/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff85&swaps=true', + { + iconUrl: 'https://icon.url', + symbol: 'TEST', + address: toChecksumAddress( + '0x8ac76a51cc950d9822d68b83fe1ad97b32cd580D', + ), + balance: '0x5f5e100', + string: '123', + chainId: '0xa', + decimals: 18, + }, + MetaMetricsSwapsEventSource.TokenView, + true, + ], + ])( + 'should open swap with correct token pair when pathname is %s', + async ( + pathname: string, + expectedUrl: string, + token: string, + location: string, + isSwap: boolean, + ) => { + const openTabSpy = jest.spyOn(global.platform, 'openTab'); + const { result, history } = renderUseBridging({ + metamask: { + useExternalServices: true, + ...mockNetworkState({ chainId: CHAIN_IDS.MAINNET }), + metaMetricsId: MOCK_METAMETRICS_ID, + remoteFeatureFlags: { + bridgeConfig: { + bip44DefaultPairs: { + eip155: { + standard: { + 'eip155:1/slip44:60': + 'eip155:1/erc20:0x0b2c639c533813f4aa9d7837caf62653d097ff84', + }, + }, + }, + refreshRate: 5000, + minimumVersion: '0.0.0', + maxRefreshCount: 5, + chains: { + '1': { + isActiveSrc: true, + isActiveDest: true, + }, + '10': { + isActiveSrc: true, + isActiveDest: true, + }, + }, + }, + }, + internalAccounts: { + selectedAccount: '0xabc', + accounts: { '0xabc': { metadata: { keyring: {} } } }, + }, + pathname, + }, + }); + const mockHistoryPush = jest.spyOn(history, 'push'); + + result.current.openBridgeExperience(location, token, isSwap); + + expect(mockDispatch.mock.calls).toHaveLength(2); + expect(mockHistoryPush.mock.calls).toHaveLength(1); + expect(mockHistoryPush).toHaveBeenCalledWith(expectedUrl); + expect(openTabSpy).not.toHaveBeenCalled(); + }, + ); }); }); From 32db4873868a1c91afd44e3921485f9d37cf200d Mon Sep 17 00:00:00 2001 From: Micaela Estabillo Date: Tue, 23 Sep 2025 10:23:55 -0700 Subject: [PATCH 8/8] chore: comments --- ui/ducks/bridge/selectors.ts | 1 - ui/hooks/bridge/useBridging.test.ts | 4 ++-- ui/hooks/bridge/useBridging.ts | 7 +++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ui/ducks/bridge/selectors.ts b/ui/ducks/bridge/selectors.ts index bec99ec94424..3ae779dc2970 100644 --- a/ui/ducks/bridge/selectors.ts +++ b/ui/ducks/bridge/selectors.ts @@ -289,7 +289,6 @@ export const getToToken = createSelector( if (toToken) { return toToken; } - // TODO if fromToken is in bip44Defaults, return its mapped toToken // Otherwise, determine the default token to use based on fromToken and toChain const defaultToken = getDefaultToToken( formatChainIdToCaip(toChain.chainId), diff --git a/ui/hooks/bridge/useBridging.test.ts b/ui/hooks/bridge/useBridging.test.ts index d88be31a5428..af5a20eea70f 100644 --- a/ui/hooks/bridge/useBridging.test.ts +++ b/ui/hooks/bridge/useBridging.test.ts @@ -122,7 +122,7 @@ describe('useBridging', () => { ], [ '/asset/0xa/', - '/cross-chain/swaps/prepare-swap-page?from=eip155:1/slip44:60&swaps=true', + '/cross-chain/swaps/prepare-swap-page?from=eip155:10/slip44:60&swaps=true', ETH_SWAPS_TOKEN_OBJECT, MetaMetricsSwapsEventSource.TokenView, true, @@ -157,7 +157,7 @@ describe('useBridging', () => { const { result, history } = renderUseBridging({ metamask: { useExternalServices: true, - ...mockNetworkState({ chainId: CHAIN_IDS.MAINNET }), + ...mockNetworkState({ chainId: CHAIN_IDS.OPTIMISM }), metaMetricsId: MOCK_METAMETRICS_ID, remoteFeatureFlags: { bridgeConfig: { diff --git a/ui/hooks/bridge/useBridging.ts b/ui/hooks/bridge/useBridging.ts index 8104cbd9f3b3..954378a26d2c 100644 --- a/ui/hooks/bridge/useBridging.ts +++ b/ui/hooks/bridge/useBridging.ts @@ -67,15 +67,14 @@ const useBridging = () => { defaultTokenPair ?? []; const srcAssetIdToUse = - // If srcToken is present, use the srcToken assetId (srcToken ? toAssetId( srcToken.address, formatChainIdToCaip(srcToken.chainId ?? providerConfig.chainId), ) - : // Otherwise, use the default bip44 src asset id - defaultBip44SrcAssetId) ?? - // Otherwise, use the native asset for Ethereum mainnet + : defaultBip44SrcAssetId) ?? + // If neither the srcAsset or default BIP44 default pair are present + // use the native asset for Ethereum mainnet // This should only happen if the feature flags are unavailable and the // user clicks on Swap from the home page getNativeAssetForChainId(CHAIN_IDS.MAINNET)?.assetId;