Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -97,70 +97,70 @@ export const BRIDGE_CHAINID_COMMON_TOKEN_PAIR: Partial<
}
>
> = {
[CHAIN_IDS.MAINNET]: {
[toEvmCaipChainId(CHAIN_IDS.MAINNET)]: {
// ETH -> mUSD on mainnet
address: '0xaca92e438df0b2401ff60da7e4337b687a2435da',
symbol: 'mUSD',
decimals: 18,
name: 'MetaMask USD',
},
[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 -> mUSD on Linea
address: '0xaca92e438df0b2401ff60da7e4337b687a2435da',
symbol: 'mUSD',
decimals: 18,
name: 'MetaMask USD',
},
[CHAIN_IDS.SEI]: {
[toEvmCaipChainId(CHAIN_IDS.SEI)]: {
// SEI -> USDC on Sei
address: '0x3894085Ef7Ff0f0aeDf52E2A2704928d1Ec074F1',
symbol: 'USDC',
Expand Down
8 changes: 5 additions & 3 deletions ui/components/app/wallet-overview/coin-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -328,11 +328,13 @@ const CoinButtons = ({

const handleBridgeOnClick = useCallback(
async (isSwap: boolean) => {
await setCorrectChain();
// Handle clicking from the wallet overview page
openBridgeExperience(
MetaMetricsSwapsEventSource.MainView,
getNativeAssetForChainId(chainId),
getNativeAssetForChainId(
location.pathname.split('/').filter(Boolean).at(-1) ??
CHAIN_IDS.MAINNET,
),
isSwap,
);
},
Expand Down
2 changes: 1 addition & 1 deletion ui/ducks/bridge/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
38 changes: 34 additions & 4 deletions ui/ducks/bridge/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,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,
Expand Down Expand Up @@ -184,8 +189,7 @@ export const getFromChains = createDeepEqualSelector(
);

export const getFromChain = createDeepEqualSelector(
getMultichainProviderConfig,
getFromChains,
[getMultichainProviderConfig, getFromChains],
(providerConfig, fromChains) => {
return fromChains.find(({ chainId }) => chainId === providerConfig.chainId);
},
Expand Down Expand Up @@ -230,6 +234,29 @@ 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,
],
(fromChainId, bip44DefaultPairs): null | [CaipAssetType, CaipAssetType] => {
if (!fromChainId) {
return null;
}
const { namespace } = parseCaipChainId(formatChainIdToCaip(fromChainId));
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) => {
Expand Down Expand Up @@ -263,7 +290,10 @@ export const getToToken = createSelector(
return 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;
},
);
Expand Down
11 changes: 4 additions & 7 deletions ui/ducks/bridge/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -276,13 +276,10 @@ const createBridgeTokenPayload = (
};

export const getDefaultToToken = (
{ chainId: targetChainId }: NetworkConfiguration | AddNetworkFields,
fromToken: NonNullable<TokenPayload['payload']>,
targetChainId: CaipChainId,
fromToken: Pick<NonNullable<TokenPayload['payload']>, '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
Expand Down
97 changes: 94 additions & 3 deletions ui/hooks/bridge/useBridging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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:10/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.OPTIMISM }),
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();
},
);
});
});
Loading
Loading