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
14 changes: 14 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
BRIDGE_CONTROLLER_NAME,
BridgeUserAction,
BridgeBackgroundAction,
calcLatestSrcBalance,
} from '@metamask/bridge-controller';

import {
Expand Down Expand Up @@ -3179,6 +3180,19 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
`${BRIDGE_CONTROLLER_NAME}:${BridgeBackgroundAction.FETCH_QUOTES}`,
),
getBalanceAmount: async (selectedAddress, tokenAddress, chainId) => {
const networkClientId =
await this.networkController.findNetworkClientIdByChainId(chainId);
const networkClient =
await this.networkController.getNetworkClientById(networkClientId);
const balance = await calcLatestSrcBalance(
networkClient.provider,
selectedAddress,
tokenAddress,
chainId,
);
return balance?.toString();
},

// Bridge Tx submission
[BridgeStatusAction.SUBMIT_TX]: this.controllerMessenger.call.bind(
Expand Down
19 changes: 18 additions & 1 deletion shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import {
BRIDGE_DEV_API_BASE_URL,
BRIDGE_PROD_API_BASE_URL,
ChainId,
formatChainIdToCaip,
} from '@metamask/bridge-controller';
import { MultichainNetworks } from './multichain/networks';
import { CHAIN_IDS, NETWORK_TO_NAME_MAP } from './network';
import {
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP,
CHAIN_IDS,
NETWORK_TO_NAME_MAP,
} from './network';

const ALLOWED_MULTICHAIN_BRIDGE_CHAIN_IDS = [
MultichainNetworks.SOLANA,
Expand Down Expand Up @@ -95,6 +100,18 @@ export const NETWORK_TO_SHORT_NETWORK_NAME_MAP: Record<
///: END:ONLY_INCLUDE_IF
};

export const BRIDGE_CHAIN_ID_TO_NETWORK_IMAGE_MAP: Record<
(typeof ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP)[number],
string
> = ALLOWED_BRIDGE_CHAIN_IDS.reduce(
(acc, chainId) => {
acc[formatChainIdToCaip(chainId)] =
CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP[chainId];
return acc;
},
{} as Record<(typeof ALLOWED_BRIDGE_CHAIN_IDS_IN_CAIP)[number], string>,
);

export const STATIC_METAMASK_BASE_URL = 'https://static.cx.metamask.io';

export const BRIDGE_CHAINID_COMMON_TOKEN_PAIR: Partial<
Expand Down
12 changes: 10 additions & 2 deletions shared/lib/asset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@ import { TEN_SECONDS_IN_MILLISECONDS } from './transactions-controller-utils';
const TOKEN_API_V3_BASE_URL = 'https://tokens.api.cx.metamask.io/v3';
const STATIC_METAMASK_BASE_URL = 'https://static.cx.metamask.io';

/**
* Converts an address and chainId to a CAIP asset type
*
* @param address - The address of the asset
* @param chainId - The chainId of the asset
* @returns The CAIP asset type
* @throws An error if the chainId is not supported by the Swap and Bridge APIs
*/
export const toAssetId = (
address: Hex | CaipAssetType | string,
chainId: CaipChainId,
): CaipAssetType | undefined => {
): CaipAssetType => {
if (isCaipAssetType(address)) {
return address;
}
Expand All @@ -43,7 +51,7 @@ export const toAssetId = (
`${chainId}/erc20:${address.toLowerCase()}`,
);
}
return undefined;
throw new Error(`Invalid address or chainId: ${address} ${chainId}`);
};

/**
Expand Down
179 changes: 78 additions & 101 deletions ui/ducks/bridge/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,46 @@ import {
BridgeUserAction,
formatChainIdToCaip,
isNativeAddress,
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 Hex } from '@metamask/utils';
import { zeroAddress } from 'ethereumjs-util';
import { trace, TraceName } from '../../../shared/lib/trace';
import {
forceUpdateMetamaskState,
setActiveNetworkWithError,
} from '../../store/actions';
import { forceUpdateMetamaskState } from '../../store/actions';
import { submitRequestToBackground } from '../../store/background-connection';
import type { MetaMaskReduxDispatch } from '../../store/store';
import { getInternalAccountBySelectedAccountGroupAndCaip } from '../../selectors/multichain-accounts/account-tree';
import type {
MetaMaskReduxDispatch,
MetaMaskReduxState,
} 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 { BridgeToken } from './types';
import { isNonEvmChain } from './utils';

const {
setToChainId,
setFromToken,
setToToken,
setFromTokenInputValue,
setEVMSrcTokenBalance,
setEVMSrcNativeBalance,
resetInputFields,
setSortOrder,
setSelectedQuote,
setWasTxDeclined,
setSlippage,
restoreQuoteRequestFromState,
switchTokens,
} = bridgeSlice.actions;

export {
setToChainId,
resetInputFields,
setToToken,
setFromToken,
Expand All @@ -60,17 +56,18 @@ export {
setWasTxDeclined,
setSlippage,
setTxAlerts,
setEVMSrcNativeBalance,
restoreQuoteRequestFromState,
switchTokens,
};

const callBridgeControllerMethod = (
bridgeAction: BridgeUserAction | BridgeBackgroundAction,
bridgeAction: BridgeUserAction | BridgeBackgroundAction | 'getBalanceAmount',
...args: unknown[]
) => {
return async (dispatch: MetaMaskReduxDispatch) => {
await submitRequestToBackground(bridgeAction, args);
const result = await submitRequestToBackground(bridgeAction, args);
await forceUpdateMetamaskState(dispatch);
return result;
};
};

Expand Down Expand Up @@ -119,96 +116,76 @@ export const updateQuoteRequestParams = (
};
};

export const setEVMSrcTokenBalance = (
token: TokenPayload['payload'],
selectedAddress?: string,
const getEVMBalance = async (
accountAddress: string,
chainId: Hex,
address?: string,
) => {
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,
tokenAddress: token.address,
chainId: token.chainId,
}),
);
}
};
return async (dispatch: MetaMaskReduxDispatch) =>
((await dispatch(
await callBridgeControllerMethod(
'getBalanceAmount',
accountAddress,
address || zeroAddress(),
chainId,
),
)) as string) || null;
};

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;
}
/**
* This action reads the latest on chain balance for the selected token and its chain's native token
* It also traces the balance update.
*
* @param token - The token to fetch the balance for
*/
export const setLatestEVMBalances = (token: BridgeToken) => {
return async (
dispatch: MetaMaskReduxDispatch,
getState: () => MetaMaskReduxState,
) => {
const { chainId, assetId, address } = token;

// Check for ALL non-EVM chains
const isNonEvm = isNonEvmChain(networkConfig.chainId);

// 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));
}
if (isNonEvmChain(chainId)) {
return null;
}

// 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,
}),
);
}
const hexChainId = formatChainIdToHex(chainId);
const caipChainId = formatChainIdToCaip(hexChainId);
const account = getInternalAccountBySelectedAccountGroupAndCaip(
getState(),
caipChainId,
);
if (!account?.address) {
return null;
}

// Fetch the native balance (EVM only)
if (selectedAccount && !isNonEvm) {
trace({
return await trace(
{
name: TraceName.BridgeBalancesUpdated,
data: {
srcChainId: formatChainIdToCaip(networkConfig.chainId),
isNative: true,
chainId,
isNative: isNativeAddress(address),
},
startTime: Date.now(),
});
await dispatch(
setEVMSrcNativeBalance({
selectedAddress: selectedAccount.address,
chainId: networkConfig.chainId,
}),
);
}
},
async () => {
dispatch(
setEVMSrcTokenBalance({
balance: await dispatch(
await getEVMBalance(account.address, hexChainId, address),
),
assetId,
}),
);

dispatch(
setEVMSrcNativeBalance({
balance: await dispatch(
await getEVMBalance(account.address, hexChainId),
),
chainId,
}),
);
},
);
};
};
Loading
Loading