Skip to content
Draft
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
56 changes: 55 additions & 1 deletion test/data/bridge/mock-bridge-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,47 @@ export const MOCK_SOLANA_ACCOUNT = {
},
};

export const MOCK_BITCOIN_ACCOUNT = {
type: 'bip122:p2wpkh',
scopes: ['bip122:000000000019d6689c085ae165831e93'],
id: '40b25442-ed7e-4b94-9f9f-ea8ff06d03b3',
address: 'bc1q2pxsagdzfdn6k6umvf9gj3eme7a27p7acym9g2',
options: {
entropySource: '01K8NT6Z7XDKEKX9MFSZ5EPFMW',
exportable: false,
entropy: {
type: 'mnemonic',
id: '01K8NT6Z7XDKEKX9MFSZ5EPFMW',
derivationPath: "m/84'/0'/0'",
groupIndex: 0,
},
},
methods: [
'signPsbt',
'computeFee',
'fillPsbt',
'broadcastPsbt',
'sendTransfer',
'getUtxo',
'listUtxos',
'publicDescriptor',
'signMessage',
],
metadata: {
name: 'Snap Account 9',
importTime: 1763417984346,
keyring: {
type: 'Snap Keyring',
},
snap: {
id: 'npm:@metamask/bitcoin-wallet-snap',
name: 'Bitcoin',
enabled: true,
},
lastSelected: 1764203245474,
},
};

export const MOCK_EVM_ACCOUNT = {
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
id: 'cf8dace4-9439-4bd4-b3a8-88c821c8fcb3',
Expand Down Expand Up @@ -163,6 +204,7 @@ export const createBridgeMockStore = ({
...(internalAccountsOverrides?.accounts ?? {}),
[MOCK_LEDGER_ACCOUNT.id]: MOCK_LEDGER_ACCOUNT,
[MOCK_SOLANA_ACCOUNT.id]: MOCK_SOLANA_ACCOUNT,
[MOCK_BITCOIN_ACCOUNT.id]: MOCK_BITCOIN_ACCOUNT,
[MOCK_EVM_ACCOUNT.id]: MOCK_EVM_ACCOUNT,
[MOCK_EVM_ACCOUNT_2.id]: MOCK_EVM_ACCOUNT_2,
},
Expand Down Expand Up @@ -259,7 +301,11 @@ export const createBridgeMockStore = ({
groupIndex: 0,
},
},
accounts: [MOCK_EVM_ACCOUNT.id, MOCK_SOLANA_ACCOUNT.id],
accounts: [
MOCK_EVM_ACCOUNT.id,
MOCK_SOLANA_ACCOUNT.id,
MOCK_BITCOIN_ACCOUNT.id,
],
},
'entropy:01K2FF18CTTXJYD34R78X4N1N1/1': {
type: 'multichain-account',
Expand Down Expand Up @@ -325,6 +371,14 @@ export const createBridgeMockStore = ({
name: '',
},
},
{
type: KeyringType.snap,
accounts: [MOCK_BITCOIN_ACCOUNT.address],
metadata: {
id: '01K6GQ6SXDB9GKP6CAPSRV5AJG',
name: '',
},
},
{
type: KeyringType.ledger,
accounts: [MOCK_LEDGER_ACCOUNT.address],
Expand Down
112 changes: 30 additions & 82 deletions ui/ducks/bridge/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@ import {
type BridgeController,
BridgeUserAction,
formatChainIdToCaip,
isNativeAddress,
getNativeAssetForChainId,
type RequiredEventContextFromClient,
UnifiedSwapBridgeEventName,
formatChainIdToHex,
isCrossChain,
} 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 { trace, TraceName } from '../../../shared/lib/trace';
import { selectDefaultNetworkClientIdsByChainId } from '../../../shared/modules/selectors/networks';
import {
forceUpdateMetamaskState,
setActiveNetworkWithError,
Expand All @@ -31,11 +25,12 @@ import {
setEVMSrcNativeBalance,
} from './bridge';
import type { TokenPayload } from './types';
import { isNetworkAdded, isNonEvmChain } from './utils';
import { isNonEvmChain } from './utils';
import { type BridgeAppState, getFromChain } from './selectors';

const {
setToChainId,
setFromToken,
setFromToken: setFromTokenAction,
setToToken,
setFromTokenInputValue,
resetInputFields,
Expand All @@ -50,7 +45,6 @@ export {
setToChainId,
resetInputFields,
setToToken,
setFromToken,
setFromTokenInputValue,
setDestTokenExchangeRates,
setDestTokenUsdExchangeRates,
Expand Down Expand Up @@ -125,14 +119,6 @@ export const setEVMSrcTokenBalance = (
) => {
return async (dispatch: MetaMaskReduxDispatch) => {
if (token) {
trace({
name: TraceName.BridgeBalancesUpdated,
data: {
srcChainId: formatChainIdToCaip(token.chainId),
isNative: isNativeAddress(token.address),
},
startTime: Date.now(),
});
await dispatch(
setEVMSrcTokenBalance_({
selectedAddress,
Expand All @@ -144,71 +130,33 @@ export const setEVMSrcTokenBalance = (
};
};

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

// Check for ALL non-EVM chains
const isNonEvm = isNonEvmChain(networkConfig.chainId);
export const setFromToken = (token: NonNullable<TokenPayload['payload']>) => {
return async (
dispatch: MetaMaskReduxDispatch,
getState: () => BridgeAppState,
) => {
const { chainId } = token;
const isNonEvm = isNonEvmChain(chainId);

const currentChainId = getFromChain(getState())?.chainId;
const shouldSetNetwork = currentChainId
? isCrossChain(currentChainId, chainId)
: true;
// Set the src network
if (isNonEvm) {
dispatch(setActiveNetworkWithError(networkConfig.chainId));
} else {
const networkId = isNetworkAdded(networkConfig)
? networkConfig.rpcEndpoints?.[networkConfig.defaultRpcEndpointIndex]
?.networkClientId
: null;
if (networkId) {
dispatch(setActiveNetworkWithError(networkId));
}
}

// Set the src token - if no token provided, set native token for non-EVM chains
if (token) {
dispatch(setFromToken(token));
} else if (isNonEvm) {
// Auto-select native token for non-EVM chains when switching
const nativeAsset = getNativeAssetForChainId(networkConfig.chainId);
if (nativeAsset) {
dispatch(
setFromToken({
...nativeAsset,
chainId: networkConfig.chainId,
}),
);
if (shouldSetNetwork) {
if (isNonEvm) {
const caipChainId = formatChainIdToCaip(chainId);
dispatch(setActiveNetworkWithError(caipChainId));
} else {
const hexChainId = formatChainIdToHex(chainId);
const networkId =
selectDefaultNetworkClientIdsByChainId(getState())[hexChainId];
if (networkId && shouldSetNetwork) {
dispatch(setActiveNetworkWithError(networkId));
}
}
}

// 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,
}),
);
}
// Set the fromToken
dispatch(setFromTokenAction(token));
};
};
46 changes: 23 additions & 23 deletions ui/ducks/bridge/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import {
type GenericQuoteRequest,
type QuoteResponse,
isBitcoinChainId,
isNativeAddress,
} from '@metamask/bridge-controller';
import { zeroAddress } from 'ethereumjs-util';
import { fetchTxAlerts } from '../../../shared/modules/bridge-utils/security-alerts-api.util';
import { endTrace, TraceName } from '../../../shared/lib/trace';
import { trace, TraceName } from '../../../shared/lib/trace';
import { SlippageValue } from '../../pages/bridge/utils/slippage-service';
import { getTokenExchangeRate, toBridgeToken } from './utils';
import type { BridgeState, ChainIdPayload, TokenPayload } from './types';
Expand Down Expand Up @@ -67,14 +68,25 @@ const getBalanceAmount = async ({
if (isNonEvmChainId(chainId) || !selectedAddress) {
return null;
}
return (
await calcLatestSrcBalance(
global.ethereumProvider,
selectedAddress,
tokenAddress,
formatChainIdToHex(chainId),
)
)?.toString();
return await trace(
{
name: TraceName.BridgeBalancesUpdated,
data: {
srcChainId: formatChainIdToCaip(chainId),
isNative: isNativeAddress(tokenAddress),
},
startTime: Date.now(),
},
async () =>
(
await calcLatestSrcBalance(
global.ethereumProvider,
selectedAddress,
tokenAddress,
formatChainIdToHex(chainId),
)
)?.toString(),
);
};

export const setEVMSrcNativeBalance = createAsyncThunk(
Expand Down Expand Up @@ -216,29 +228,17 @@ const bridgeSlice = createSlice({
? action.meta.arg.tokenAddress === state.fromToken.address
: true
) {
state.fromTokenBalance = action.payload?.toString() ?? null;
state.fromTokenBalance = action.payload ?? null;
}
endTrace({
name: TraceName.BridgeBalancesUpdated,
});
});
builder.addCase(setEVMSrcTokenBalance.rejected, (state) => {
state.fromTokenBalance = null;
endTrace({
name: TraceName.BridgeBalancesUpdated,
});
});
builder.addCase(setEVMSrcNativeBalance.fulfilled, (state, action) => {
state.fromNativeBalance = action.payload?.toString() ?? null;
endTrace({
name: TraceName.BridgeBalancesUpdated,
});
state.fromNativeBalance = action.payload ?? null;
});
builder.addCase(setEVMSrcNativeBalance.rejected, (state) => {
state.fromNativeBalance = null;
endTrace({
name: TraceName.BridgeBalancesUpdated,
});
});
},
});
Expand Down
Loading
Loading