Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d24d24e
refactor: simplify bridge network type
micaelae Nov 26, 2025
e89411f
fix: infer non-evm balance type
micaelae Nov 26, 2025
0294b9f
refactor: remove unused getToTokenConversionRate selector
micaelae Nov 26, 2025
d8bced7
refactor: deprecate getIsBridgeTx selector
micaelae Nov 27, 2025
9a8eb30
refactor: deprecate token.string usages
micaelae Nov 27, 2025
a651ddb
test: fix featureFlagOverrides type
micaelae Nov 28, 2025
94976c4
test: fix bridgeStateOverrides mocks
micaelae Nov 28, 2025
31288a4
Merge branch 'main' into swaps-refactor-rm-unused-selectors
micaelae Dec 1, 2025
6598337
fix: lint errors
micaelae Dec 1, 2025
bd81e44
fix: lint errors
micaelae Dec 1, 2025
a51b7d8
Merge branch 'main' into swaps-refactor-rm-unused-selectors
micaelae Dec 1, 2025
f762b69
fix: lint error
micaelae Dec 1, 2025
ae45132
fix: e2e test
micaelae Dec 1, 2025
de91a29
fix: state-logs e2e test
micaelae Dec 1, 2025
992820d
fix: set EVM network
micaelae Dec 1, 2025
a7c3212
chore: update types
micaelae Dec 1, 2025
b4a84dc
fix: optional chaining
micaelae Dec 1, 2025
d884a21
Merge branch 'main' into swaps-refactor-rm-unused-selectors
micaelae Dec 2, 2025
b97ef32
fix: lint
micaelae Dec 2, 2025
e4d03e8
fix: e2e test
micaelae Dec 2, 2025
6026ed8
chore: rm native balance fetch on fromChain update
micaelae Dec 2, 2025
ecbf219
Merge branch 'main' into swaps-refactor-rm-unused-selectors
micaelae Dec 2, 2025
1450d09
fix: getFromChains
micaelae Dec 2, 2025
d64acea
chore: add constant for slip44
micaelae Dec 2, 2025
92155e9
chore: rm unused useSolanaBridgeTransactionMapping hook
micaelae Dec 2, 2025
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
2 changes: 1 addition & 1 deletion shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [
///: BEGIN:ONLY_INCLUDE_IF(tron)
MultichainNetworks.TRON,
///: END:ONLY_INCLUDE_IF
];
] as const;

export const ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP =
ALLOWED_EVM_BRIDGE_CHAIN_IDS.map(toEvmCaipChainId).concat(
Expand Down
17 changes: 6 additions & 11 deletions test/data/bridge/mock-bridge-store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
getDefaultBridgeControllerState,
formatChainIdToCaip,
FeatureFlagResponse,
BridgeControllerState,
} from '@metamask/bridge-controller';
import { DEFAULT_BRIDGE_STATUS_CONTROLLER_STATE } from '@metamask/bridge-status-controller';
import { AVAILABLE_MULTICHAIN_NETWORK_CONFIGURATIONS } from '@metamask/multichain-network-controller';
Expand Down Expand Up @@ -118,25 +120,21 @@ export const MOCK_EVM_ACCOUNT_2 = {
};

export const createBridgeMockStore = ({
featureFlagOverrides = {},
featureFlagOverrides = { bridgeConfig: {} },
bridgeSliceOverrides = {},
bridgeStateOverrides = {},
bridgeStatusStateOverrides = {},
metamaskStateOverrides = {},
stateOverrides = {},
}: {
// featureFlagOverrides?: Partial<BridgeControllerState['bridgeFeatureFlags']>;
// bridgeStateOverrides?: Partial<BridgeControllerState>;
featureFlagOverrides?: { bridgeConfig: Partial<FeatureFlagResponse> };
bridgeStateOverrides?: Partial<BridgeControllerState>;
// bridgeStatusStateOverrides?: Partial<BridgeStatusState>;
// metamaskStateOverrides?: Partial<BridgeAppState['metamask']>;
// TODO replace these with correct types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
featureFlagOverrides?: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bridgeSliceOverrides?: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bridgeStateOverrides?: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bridgeStatusStateOverrides?: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
metamaskStateOverrides?: Record<string, any>;
Expand Down Expand Up @@ -352,7 +350,6 @@ export const createBridgeMockStore = ({
support: false,
refreshRate: 5000,
maxRefreshCount: 5,
...featureFlagOverrides?.extensionConfig,
...featureFlagOverrides?.bridgeConfig,
chains: {
[formatChainIdToCaip('0x1')]: {
Expand All @@ -361,9 +358,7 @@ export const createBridgeMockStore = ({
},
...Object.fromEntries(
Object.entries(
featureFlagOverrides?.extensionConfig?.chains ??
featureFlagOverrides?.bridgeConfig?.chains ??
{},
featureFlagOverrides?.bridgeConfig?.chains ?? {},
).map(([chainId, config]) => [
formatChainIdToCaip(chainId),
config,
Expand Down
2 changes: 0 additions & 2 deletions test/e2e/tests/settings/state-logs.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@
"sortOrder": "string",
"toChainId": "null",
"toToken": "null",
"toTokenExchangeRate": "null",
"toTokenUsdExchangeRate": "null",
"txAlert": "null",
"wasTxDeclined": "boolean"
},
Expand Down
69 changes: 19 additions & 50 deletions ui/ducks/bridge/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ import {
getNativeAssetForChainId,
type RequiredEventContextFromClient,
UnifiedSwapBridgeEventName,
formatChainIdToHex,
} from '@metamask/bridge-controller';
import { type InternalAccount } from '@metamask/keyring-internal-api';
import { type CaipChainId } from '@metamask/utils';
import type {
AddNetworkFields,
NetworkConfiguration,
} from '@metamask/network-controller';
import type { CaipChainId, Hex } from '@metamask/utils';
import { trace, TraceName } from '../../../shared/lib/trace';
import { selectDefaultNetworkClientIdsByChainId } from '../../../shared/modules/selectors/networks';
import {
forceUpdateMetamaskState,
setActiveNetworkWithError,
Expand All @@ -23,15 +20,14 @@ import { submitRequestToBackground } from '../../store/background-connection';
import type { MetaMaskReduxDispatch } from '../../store/store';
import {
bridgeSlice,
setDestTokenExchangeRates,
setDestTokenUsdExchangeRates,
setSrcTokenExchangeRates,
setTxAlerts,
setEVMSrcTokenBalance as setEVMSrcTokenBalance_,
setEVMSrcNativeBalance,
} from './bridge';
import type { TokenPayload } from './types';
import { isNetworkAdded, isNonEvmChain } from './utils';
import { type TokenPayload } from './types';
import { type BridgeAppState } from './selectors';
import { isNonEvmChain } from './utils';

const {
setToChainId,
Expand All @@ -52,8 +48,6 @@ export {
setToToken,
setFromToken,
setFromTokenInputValue,
setDestTokenExchangeRates,
setDestTokenUsdExchangeRates,
setSrcTokenExchangeRates,
setSortOrder,
setSelectedQuote,
Expand Down Expand Up @@ -145,33 +139,26 @@ export const setEVMSrcTokenBalance = (
};

export const setFromChain = ({
networkConfig,
selectedAccount,
chainId,
token = null,
}: {
networkConfig?:
| NetworkConfiguration
| AddNetworkFields
| (Omit<NetworkConfiguration, 'chainId'> & { chainId: CaipChainId });
selectedAccount: InternalAccount | null;
chainId: Hex | CaipChainId;
token?: TokenPayload['payload'];
}) => {
return async (dispatch: MetaMaskReduxDispatch) => {
if (!networkConfig) {
return;
}

return async (
dispatch: MetaMaskReduxDispatch,
getState: () => BridgeAppState,
) => {
// Check for ALL non-EVM chains
const isNonEvm = isNonEvmChain(networkConfig.chainId);
const isNonEvm = isNonEvmChain(chainId);

// Set the src network
if (isNonEvm) {
dispatch(setActiveNetworkWithError(networkConfig.chainId));
dispatch(setActiveNetworkWithError(chainId));
} else {
const networkId = isNetworkAdded(networkConfig)
? networkConfig.rpcEndpoints?.[networkConfig.defaultRpcEndpointIndex]
?.networkClientId
: null;
const hexChainId = formatChainIdToHex(chainId);
const networkId =
selectDefaultNetworkClientIdsByChainId(getState())[hexChainId];
if (networkId) {
dispatch(setActiveNetworkWithError(networkId));
}
Expand All @@ -182,33 +169,15 @@ export const setFromChain = ({
dispatch(setFromToken(token));
} else if (isNonEvm) {
// Auto-select native token for non-EVM chains when switching
const nativeAsset = getNativeAssetForChainId(networkConfig.chainId);
const nativeAsset = getNativeAssetForChainId(chainId);
if (nativeAsset) {
dispatch(
setFromToken({
...nativeAsset,
chainId: networkConfig.chainId,
chainId,
}),
);
}
}

// Fetch the native balance (EVM only)
if (selectedAccount && !isNonEvm) {
trace({
name: TraceName.BridgeBalancesUpdated,
data: {
srcChainId: formatChainIdToCaip(networkConfig.chainId),
isNative: true,
},
startTime: Date.now(),
});
await dispatch(
setEVMSrcNativeBalance({
selectedAddress: selectedAccount.address,
chainId: networkConfig.chainId,
}),
);
}
};
};
113 changes: 0 additions & 113 deletions ui/ducks/bridge/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import {
BridgeBackgroundAction,
BridgeUserAction,
formatChainIdToCaip,
getNativeAssetForChainId,
} from '@metamask/bridge-controller';
import * as controllerUtils from '@metamask/controller-utils';
import { createBridgeMockStore } from '../../../test/data/bridge/mock-bridge-store';
import { toAssetId } from '../../../shared/lib/asset-utils';
import { CHAIN_IDS } from '../../../shared/constants/network';
import { setBackgroundConnection } from '../../store/background-connection';
import { MultichainNetworks } from '../../../shared/constants/multichain/networks';
Expand All @@ -23,7 +20,6 @@ import {
setToChainId,
updateQuoteRequestParams,
resetBridgeState,
setDestTokenExchangeRates,
setWasTxDeclined,
setSlippage,
} from './actions';
Expand Down Expand Up @@ -110,7 +106,6 @@ describe('Ducks - Bridge', () => {
chainId: '0xa',
image:
'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/10/erc20/0x13341431.png',
string: '0',
}),
);
});
Expand Down Expand Up @@ -144,11 +139,9 @@ describe('Ducks - Bridge', () => {
slippage: SlippageValue.BridgeDefault,
fromTokenInputValue: null,
sortOrder: 'cost_ascending',
toTokenExchangeRate: null,
fromTokenExchangeRate: null,
wasTxDeclined: false,
txAlert: null,
toTokenUsdExchangeRate: null,
fromTokenBalance: null,
fromNativeBalance: null,
});
Expand Down Expand Up @@ -245,119 +238,13 @@ describe('Ducks - Bridge', () => {
toChainId: null,
toToken: null,
txAlert: null,
toTokenExchangeRate: null,
wasTxDeclined: false,
toTokenUsdExchangeRate: null,
fromTokenBalance: null,
fromNativeBalance: null,
});
});
});

describe('setDestTokenExchangeRates', () => {
it('fetches token prices and updates dest exchange rates in state, native dest token', async () => {
// TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31973
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockStore = configureMockStore<any>(middleware)(
createBridgeMockStore(),
);
const state = mockStore.getState().bridge;
const fetchTokenExchangeRatesSpy = jest
.spyOn(controllerUtils, 'handleFetch')
.mockResolvedValue({
[getNativeAssetForChainId(CHAIN_IDS.LINEA_MAINNET).assetId]: {
price: 0.356628,
},
});

await mockStore.dispatch(
setDestTokenExchangeRates({
chainId: CHAIN_IDS.LINEA_MAINNET,
tokenAddress: zeroAddress(),
currency: 'usd',
}) as never,
);

expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledTimes(1);
expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledWith(
'https://price.api.cx.metamask.io/v3/spot-prices?assetIds=eip155%3A59144%2Fslip44%3A60&includeMarketData=true&vsCurrency=usd',
{
headers: { 'X-Client-Id': 'extension' },
method: 'GET',
signal: undefined,
},
);

const actions = mockStore.getActions();
expect(actions).toHaveLength(2);
expect(actions[0].type).toStrictEqual(
'bridge/setDestTokenExchangeRates/pending',
);
expect(actions[1].type).toStrictEqual(
'bridge/setDestTokenExchangeRates/fulfilled',
);
const newState = bridgeReducer(state, actions[1]);
expect(newState).toStrictEqual({
toChainId: null,
toTokenExchangeRate: 0.356628,
sortOrder: 'cost_ascending',
});
});

it('fetches token prices and updates dest exchange rates in state, erc20 dest token', async () => {
// TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31973
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockStore = configureMockStore<any>(middleware)(
createBridgeMockStore(),
);
const state = mockStore.getState().bridge;
const fetchTokenExchangeRatesSpy = jest
.spyOn(controllerUtils, 'handleFetch')
.mockResolvedValue({
[toAssetId(
'0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'.toLowerCase(),
formatChainIdToCaip(CHAIN_IDS.LINEA_MAINNET),
) as never]: {
price: 0.999881,
},
});

await mockStore.dispatch(
setDestTokenExchangeRates({
chainId: CHAIN_IDS.LINEA_MAINNET,
tokenAddress:
'0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'.toLowerCase(),
currency: 'usd',
}) as never,
);

expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledTimes(1);
expect(fetchTokenExchangeRatesSpy).toHaveBeenCalledWith(
'https://price.api.cx.metamask.io/v3/spot-prices?assetIds=eip155%3A59144%2Ferc20%3A0x3c499c542cef5e3811e1192ce70d8cc03d5c3359&includeMarketData=true&vsCurrency=usd',
{
headers: { 'X-Client-Id': 'extension' },
method: 'GET',
signal: undefined,
},
);

const actions = mockStore.getActions();
expect(actions).toHaveLength(2);
expect(actions[0].type).toStrictEqual(
'bridge/setDestTokenExchangeRates/pending',
);
expect(actions[1].type).toStrictEqual(
'bridge/setDestTokenExchangeRates/fulfilled',
);
const newState = bridgeReducer(state, actions[1]);
expect(newState).toStrictEqual({
toChainId: null,
toTokenExchangeRate: 0.999881,
sortOrder: 'cost_ascending',
});
});
});

describe('setWasTxDeclined', () => {
it('sets the wasTxDeclined flag to true', () => {
const state = store.getState().bridge;
Expand Down
Loading
Loading