Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/bridge-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"@metamask/messenger": "^3.0.0",
"@metamask/metamask-eth-abis": "^3.1.1",
"@metamask/multichain-network-controller": "^4.0.0",
"@metamask/money-account-controller": "^3.0.0",
"@metamask/money-account-utils": "^2.1.0",
"@metamask/network-controller": "^37.1.1",
"@metamask/polling-controller": "^17.0.0",
"@metamask/profile-sync-controller": "^36.0.0",
Expand Down
38 changes: 14 additions & 24 deletions packages/bridge-controller/src/bridge-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type {
} from './types.js';
import { getAssetIdsForToken, toExchangeRates } from './utils/assets.js';
import { hasSufficientBalance } from './utils/balance.js';
import { getAccountByAddress } from './utils/controllers/accounts.js';
import {
getDefaultBridgeControllerState,
isCrossChain,
Expand Down Expand Up @@ -102,6 +103,7 @@ import {
} from './validators/quote-request.js';
import type { QuoteResponseV1 } from './validators/quote-response-v1.js';
import type { QuoteResponse } from './validators/quote-response.js';
import { getNetworkClientByChainId } from './utils/controllers/network.js';

const metadata: StateMetadata<BridgeControllerState> = {
quoteRequest: {
Expand Down Expand Up @@ -622,8 +624,10 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
) => {
try {
const srcChainIdInHex = formatChainIdToHex(quoteRequest.srcChainId);
const provider =
this.#getNetworkClientByChainId(srcChainIdInHex)?.provider;
const provider = getNetworkClientByChainId(
this.messenger,
srcChainIdInHex,
)?.provider;
const normalizedSrcTokenAddress = formatAddressToCaipReference(
quoteRequest.srcTokenAddress,
);
Expand Down Expand Up @@ -654,7 +658,8 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
const isSrcChainNonEVM = isNonEvmChainId(quoteRequest.srcChainId);
const providerConfig = isSrcChainNonEVM
? undefined
: this.#getNetworkClientByChainId(
: getNetworkClientByChainId(
this.messenger,
formatChainIdToHex(quoteRequest.srcChainId),
)?.configuration;

Expand Down Expand Up @@ -1067,6 +1072,7 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
const pendingFeeAppendPromises = new Set<Promise<void>>();

await fetchBridgeQuoteStream(
this.messenger,
this.#fetchFn,
quoteRequests,
signal,
Expand Down Expand Up @@ -1180,28 +1186,10 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
if (!addressToUse) {
throw new Error('Account address is required');
}
const selectedAccount = this.messenger.call(
'AccountsController:getAccountByAddress',
addressToUse,
);
const selectedAccount = getAccountByAddress(this.messenger, addressToUse);
return selectedAccount;
}

#getNetworkClientByChainId(chainId: Hex) {
const networkClientId = this.messenger.call(
'NetworkController:findNetworkClientIdByChainId',
chainId,
);
if (!networkClientId) {
throw new Error(`No network client found for chainId: ${chainId}`);
}
const networkClient = this.messenger.call(
'NetworkController:getNetworkClientById',
networkClientId,
);
return networkClient;
}

readonly #getJwt = async (): Promise<string | undefined> => {
try {
const token = await this.messenger.call(
Expand Down Expand Up @@ -1538,8 +1526,10 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
contractAddress: string,
destinationChainId: GenericQuoteRequest['destChainId'],
): Promise<string> => {
const networkClient = this.#getNetworkClientByChainId(CHAIN_IDS.MAINNET);
const provider = networkClient?.provider;
const provider = getNetworkClientByChainId(
this.messenger,
CHAIN_IDS.MAINNET,
)?.provider;
if (!provider) {
throw new Error('No provider found');
}
Expand Down
3 changes: 3 additions & 0 deletions packages/bridge-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,6 @@ export {
fetchTokens,
type SwapsToken,
} from './utils/swaps.js';

export { getAccountByAddress } from './utils/controllers/accounts.js';
export { getNetworkClientIdByChainId } from './utils/controllers/network.js';
13 changes: 9 additions & 4 deletions packages/bridge-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
import type { AuthenticationControllerGetBearerTokenAction } from '@metamask/profile-sync-controller/auth';
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
import type { SnapControllerHandleRequestAction } from '@metamask/snaps-controllers';
import type { MoneyAccountControllerGetMoneyAccountAction } from '@metamask/money-account-controller';
import type { Infer } from '@metamask/superstruct';
import type {
CaipAccountId,
Expand Down Expand Up @@ -200,6 +201,10 @@ export type QuoteRequest<
* The fee that will be charged by MetaMask
*/
fee?: number;
/**
* Whether the destWalletAddress is a money account, only needed for same-chain swaps
*/
isMoneyAccount?: boolean;
};

export enum StatusTypes {
Expand Down Expand Up @@ -390,20 +395,20 @@ export type BridgeControllerStateChangeEvent = ControllerStateChangeEvent<
>;

export type BridgeControllerActions =
| BridgeControllerGetStateAction
| BridgeControllerMethodActions;
BridgeControllerGetStateAction | BridgeControllerMethodActions;

export type BridgeControllerEvents = BridgeControllerStateChangeEvent;

export type AllowedActions =
| AccountsControllerGetAccountByAddressAction
| AssetsControllerGetExchangeRatesForBridgeAction
| AuthenticationControllerGetBearerTokenAction
| CurrencyRateControllerGetStateAction
| SnapControllerHandleRequestAction
| MoneyAccountControllerGetMoneyAccountAction
| NetworkControllerFindNetworkClientIdByChainIdAction
| NetworkControllerGetNetworkClientByIdAction
| RemoteFeatureFlagControllerGetStateAction
| AssetsControllerGetExchangeRatesForBridgeAction;
| SnapControllerHandleRequestAction;
export type AllowedEvents = never;

/**
Expand Down
16 changes: 16 additions & 0 deletions packages/bridge-controller/src/utils/controllers/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { AccountsControllerGetAccountByAddressAction } from '../../../../accounts-controller/src/AccountsController-method-action-types.js';

export const getAccountByAddress = <
T extends {
call(
action: AccountsControllerGetAccountByAddressAction['type'],
address: string,
): ReturnType<AccountsControllerGetAccountByAddressAction['handler']>;
},
>(
messenger: T,
address: string,
) => {
return messenger.call('AccountsController:getAccountByAddress', address);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { BridgeControllerMessenger, QuoteRequest } from '../../types.js';
import { getMoneyAccountVaultConfig } from '@metamask/money-account-utils';
import { getFeatureFlags } from './remote-feature-flags.js';
import { getAccountByAddress } from './accounts.js';
import { CHAIN_IDS } from '../../constants/chains.js';

/**
* Resolves the primary money account and vault config to use for the money account deposit request
*
* UI: Money is dest token
*
* emvAccount swap X to MUSD at moneyAccount's evmAddress (swap + send)
* moneyAccount's evmAddress deposit MUSD to moneyAccount
*
* @param params - The parameters for the function
* @param params.messenger - The bridge-controller messenger instance
* @param params.quoteRequest - The normalized quote request
* @returns The money account deposit config, undefined if the dest wallet address is not a money account
*/
export const getMoneyAccountDepositSetup = async ({
messenger,
quoteRequest,
}: {
messenger: BridgeControllerMessenger;
quoteRequest: QuoteRequest;
}) => {
// Get primary money account for dest wallet address
const depositor = getAccountByAddress(
messenger,
quoteRequest.destWalletAddress ?? quoteRequest.walletAddress,
)?.id;
if (!depositor) {
return undefined;
}
const primaryMoneyAccount = messenger.call(
'MoneyAccountController:getMoneyAccount',
{ id: depositor },
);
if (!primaryMoneyAccount) {
return undefined;
}
if (!primaryMoneyAccount.address) {
return undefined;
}

// Get vault config
const ff = getFeatureFlags(messenger);
const vaultConfig = getMoneyAccountVaultConfig(ff);
if (!vaultConfig) {
return undefined;
}

const {
chainId,
boringVault,
tellerAddress,
accountantAddress,
lensAddress,
} = vaultConfig;

return {
chainIdHex: chainId,
boringVault,
tellerAddress,
accountantAddress,
lensAddress,
moneyAccountAddress: primaryMoneyAccount.address as `0x${string}`,
isGasFeeSponsored: CHAIN_IDS.MONAD === chainId,
};
};
45 changes: 45 additions & 0 deletions packages/bridge-controller/src/utils/controllers/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { NetworkControllerFindNetworkClientIdByChainIdAction } from '@metamask/network-controller';

import type {
BridgeControllerMessenger,
GenericQuoteRequest,
} from '../../types.js';
import { formatChainIdToHex } from '../caip-formatters.js';

export const getNetworkClientIdByChainId = <
T extends {
call(
action: NetworkControllerFindNetworkClientIdByChainIdAction['type'],
chainId: Parameters<
NetworkControllerFindNetworkClientIdByChainIdAction['handler']
>[0],
): ReturnType<
NetworkControllerFindNetworkClientIdByChainIdAction['handler']
>;
},
>(
messenger: T,
chainId: GenericQuoteRequest['srcChainId'],
) => {
const hexChainId = formatChainIdToHex(chainId);
return messenger.call(
'NetworkController:findNetworkClientIdByChainId',
hexChainId,
);
};

export const getNetworkClientByChainId = (
messenger: BridgeControllerMessenger,
chainId: GenericQuoteRequest['srcChainId'],
) => {
const networkClientId = getNetworkClientIdByChainId(messenger, chainId);
if (!networkClientId) {
throw new Error(`No network client found for chainId: ${chainId}`);
}

return messenger.call(
'NetworkController:getNetworkClientById',
networkClientId,
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';

export const getFeatureFlags = <
T extends {
call(
action: RemoteFeatureFlagControllerGetStateAction['type'],
): ReturnType<RemoteFeatureFlagControllerGetStateAction['handler']>;
},
>(
messenger: T,
) => {
const remoteFeatureFlags = messenger.call(
'RemoteFeatureFlagController:getState',
)?.remoteFeatureFlags;
return remoteFeatureFlags;
};
19 changes: 9 additions & 10 deletions packages/bridge-controller/src/utils/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from '../types.js';
import { validateFeatureFlagsResponse } from '../validators/feature-flags.js';
import { formatChainIdToCaip } from './caip-formatters.js';
import { getFeatureFlags } from './controllers/remote-feature-flags.js';

export const formatFeatureFlags = (
bridgeFeatureFlags: FeatureFlagsPlatformConfig,
Expand Down Expand Up @@ -55,32 +56,30 @@ export const processFeatureFlags = (
* @param messenger - Any messenger with access to RemoteFeatureFlagController:getState
* @returns The bridge feature flags
*/
export function getBridgeFeatureFlags<
export const getBridgeFeatureFlags = <
T extends {
call(
action: 'RemoteFeatureFlagController:getState',
): RemoteFeatureFlagControllerState;
},
>(messenger: T): FeatureFlagsPlatformConfig {
>(
messenger: T,
): FeatureFlagsPlatformConfig => {
// This will return the bridgeConfig for the current platform even without specifying the platform
const remoteFeatureFlagControllerState = messenger.call(
'RemoteFeatureFlagController:getState',
);
const remoteFeatureFlagControllerState = getFeatureFlags(messenger);

// bridgeConfigV2 is the feature flag for the mobile app
// bridgeConfig for Mobile has been deprecated since release of bridge and Solana in 7.46.0 was pushed back
// and there's no way to turn on bridgeConfig for 7.47.0 without affecting 7.46.0 as well.
// You will still get bridgeConfig returned from remoteFeatureFlagControllerState but you should use bridgeConfigV2 instead
// Mobile's bridgeConfig will be permanently serving the disabled variation, so falling back to it in Mobile will be ok
const rawMobileFlags =
remoteFeatureFlagControllerState?.remoteFeatureFlags?.bridgeConfigV2;
const rawMobileFlags = remoteFeatureFlagControllerState?.bridgeConfigV2;

// Extension LaunchDarkly will not have the bridgeConfigV2 field, so we'll continue to use bridgeConfig
const rawBridgeConfig =
remoteFeatureFlagControllerState?.remoteFeatureFlags?.bridgeConfig;
const rawBridgeConfig = remoteFeatureFlagControllerState?.bridgeConfig;

return processFeatureFlags(rawMobileFlags || rawBridgeConfig);
}
};

/**
* Checks if the client version is greater than or equal to the minimum required version
Expand Down
8 changes: 4 additions & 4 deletions packages/bridge-controller/src/utils/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ describe('fetch', () => {
);

expect(mockFetchFn).toHaveBeenCalledWith(
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&slippage=0.5',
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&isMoneyAccount=false&slippage=0.5',
{
headers: {
'X-Client-Id': 'extension',
Expand Down Expand Up @@ -260,7 +260,7 @@ describe('fetch', () => {
);

expect(mockFetchFn).toHaveBeenCalledWith(
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&slippage=0.5',
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&isMoneyAccount=false&slippage=0.5',
{
headers: {
'X-Client-Id': 'extension',
Expand Down Expand Up @@ -352,7 +352,7 @@ describe('fetch', () => {
);

expect(mockFetchFn).toHaveBeenCalledWith(
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&slippage=0.5',
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&isMoneyAccount=false&slippage=0.5',
{
headers: {
'X-Client-Id': 'extension',
Expand Down Expand Up @@ -427,7 +427,7 @@ describe('fetch', () => {
);

expect(mockFetchFn).toHaveBeenCalledWith(
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&slippage=0.5&fee=0&aggIds=socket%2Clifi&bridgeIds=bridge1%2Cbridge2',
'https://bridge.api.cx.metamask.io/getQuote?walletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&destWalletAddress=0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984&srcChainId=1&destChainId=10&srcTokenAddress=0x0000000000000000000000000000000000000000&destTokenAddress=0x0000000000000000000000000000000000000000&srcTokenAmount=20000&insufficientBal=false&resetApproval=false&gasIncluded=false&gasIncluded7702=false&isMoneyAccount=false&slippage=0.5&fee=0&aggIds=socket%2Clifi&bridgeIds=bridge1%2Cbridge2',
{
headers: {
'X-Client-Id': 'extension',
Expand Down
Loading
Loading