Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
44b7ded
feat(bridge-status-controller): pass swap asset ids to Tron snap requ…
Battambang Oct 9, 2026
1c15fb1
feat(bridge-status-controller): pass swap asset ids to Solana snap re…
Battambang Oct 9, 2026
92368fa
feat(bridge-status-controller): pass swap asset ids to Bitcoin snap r…
Battambang Oct 10, 2026
876ab41
Merge branch 'feat/tron-flow-classification-analytics' into feat/mult…
Battambang Oct 10, 2026
cb6b8a4
Merge branch 'feat/solana-flow-classification-analytics' into feat/mu…
Battambang Oct 10, 2026
887625f
Merge branch 'feat/bitcoin-flow-classification-analytics' into feat/m…
Battambang Oct 10, 2026
67548be
refactor(bridge-status-controller): use chain-id guards for snap asse…
Battambang Oct 10, 2026
dc886c9
docs(bridge-status-controller): reference the PR in the changelog
Battambang Oct 10, 2026
c9cc4be
test(bridge-status-controller): use a same-chain Solana asset id in t…
Battambang Oct 10, 2026
131e5e6
fix(bridge-status-controller): omit swap asset ids from approval snap…
Battambang Oct 10, 2026
0ef8f17
feat(bridge-status-controller): pass swap asset ids to Tron snap requ…
Battambang Oct 9, 2026
cb917d5
feat(bridge-status-controller): pass swap asset ids to Solana snap re…
Battambang Oct 9, 2026
ae36a99
feat(bridge-status-controller): pass swap asset ids to Bitcoin snap r…
Battambang Oct 10, 2026
be34b7c
refactor(bridge-status-controller): use chain-id guards for snap asse…
Battambang Oct 10, 2026
65fef2c
docs(bridge-status-controller): reference the PR in the changelog
Battambang Oct 10, 2026
f2c7ae2
test(bridge-status-controller): use a same-chain Solana asset id in t…
Battambang Oct 10, 2026
fc8dda0
fix(bridge-status-controller): omit swap asset ids from approval snap…
Battambang Oct 10, 2026
6f86fc1
refactor(bridge-status-controller): unify non-EVM snap options
Battambang Oct 13, 2026
14c4aad
chore(bridge-status-controller): update lint suppression count
Battambang Oct 13, 2026
3feb7ff
merge: integrate multichain flow analytics changes
Battambang Oct 13, 2026
b6fbe03
test(bridge-status-controller): correct non-EVM asset fixtures
Battambang Oct 13, 2026
51cb011
fix(bridge-status-controller): type bridge fixture asset ID
Battambang Oct 13, 2026
0bfdd33
test(bridge-status-controller): restore Tron bridge fixture steps
Battambang Oct 13, 2026
ec899ef
refactor(bridge-status-controller): use non-EVM chain guard
Battambang Oct 13, 2026
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 oxlint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2238,7 +2238,7 @@
"count": 2
},
"typescript/no-unsafe-assignment": {
"count": 5
"count": 9
},
"typescript/no-unsafe-return": {
"count": 2
Expand Down
1 change: 1 addition & 0 deletions packages/bridge-status-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Pass the swap/bridge `sourceAssetId` and `destAssetId` to Tron, Solana, and Bitcoin Snap `signAndSendTransaction` requests. ([#10775](https://github.com/MetaMask/core/pull/10775))
- Bump `@metamask/profile-sync-controller` from `^34.0.0` to `^34.0.3` ([#10752](https://github.com/MetaMask/core/pull/10752), [#10756](https://github.com/MetaMask/core/pull/10756), [#10761](https://github.com/MetaMask/core/pull/10761))
- Bump `@metamask/snaps-controllers` from `^19.0.0` to `^19.0.1` ([#10719](https://github.com/MetaMask/core/pull/10719))
- Bump `@metamask/transaction-controller` from `^72.1.0` to `^72.2.0` ([#10772](https://github.com/MetaMask/core/pull/10772))
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2386,7 +2386,7 @@ describe('BridgeStatusController', () => {
symbol: 'SOL',
name: 'Solana',
decimals: 9,
assetId: 'eip155:1399811149/slip44:501',
assetId: getNativeAssetForChainId(ChainId.SOLANA).assetId,
},
destTokenAmount: '0.5',
minDestTokenAmount: '0.475',
Expand All @@ -2411,7 +2411,7 @@ describe('BridgeStatusController', () => {
symbol: 'SOL',
name: 'Solana',
decimals: 9,
assetId: 'eip155:1399811149/slip44:501',
assetId: getNativeAssetForChainId(ChainId.SOLANA).assetId,
},
destAsset: {
chainId: ChainId.ETH,
Expand All @@ -2432,7 +2432,7 @@ describe('BridgeStatusController', () => {
symbol: 'SOL',
name: 'Solana',
decimals: 9,
assetId: 'eip155:1399811149/slip44:501',
assetId: getNativeAssetForChainId(ChainId.SOLANA).assetId,
},
},
},
Expand Down Expand Up @@ -2632,30 +2632,31 @@ describe('BridgeStatusController', () => {
symbol: 'USDC',
name: 'USDC',
decimals: 18,
assetId: 'eip155:1399811149/slip44:501',
assetId: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
},
bridgeId: 'test-bridge',
bridges: [],
steps: [
{
action: ActionTypes.BRIDGE,
action: ActionTypes.SWAP,
srcChainId: ChainId.SOLANA,
destChainId: ChainId.ETH,
destChainId: ChainId.SOLANA,
srcAsset: {
chainId: ChainId.SOLANA,
address: 'native',
symbol: 'SOL',
name: 'Solana',
decimals: 9,
assetId: 'eip155:1399811149/slip44:501',
assetId: getNativeAssetForChainId(ChainId.SOLANA).assetId,
},
destAsset: {
chainId: ChainId.ETH,
chainId: ChainId.SOLANA,
address: '0x...',
symbol: 'ETH',
name: 'Ethereum',
symbol: 'USDC',
name: 'USDC',
decimals: 18,
assetId: 'eip155:1/slip44:60',
assetId:
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
},
},
],
Expand All @@ -2668,7 +2669,7 @@ describe('BridgeStatusController', () => {
symbol: 'SOL',
name: 'Solana',
decimals: 9,
assetId: 'eip155:1399811149/slip44:501',
assetId: getNativeAssetForChainId(ChainId.SOLANA).assetId,
},
},
},
Expand Down Expand Up @@ -2876,7 +2877,7 @@ describe('BridgeStatusController', () => {
symbol: 'USDT',
name: 'Tether USD',
decimals: 6,
assetId: 'tron:728126428/slip44:195',
assetId: 'tron:728126428/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
},
destTokenAmount: '500000000',
minDestTokenAmount: '475000000',
Expand All @@ -2901,7 +2902,8 @@ describe('BridgeStatusController', () => {
symbol: 'USDT',
name: 'Tether USD',
decimals: 6,
assetId: 'tron:728126428/slip44:195',
assetId:
'tron:728126428/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
},
destAsset: {
chainId: ChainId.TRON,
Expand Down Expand Up @@ -3049,6 +3051,31 @@ describe('BridgeStatusController', () => {
quote: {
...mockQuoteResponse.quote,
destChainId: ChainId.ETH, // Different chain = bridge
destAsset: {
chainId: ChainId.ETH,
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
assetId:
'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as CaipAssetType,
},
steps: [
{
...mockQuoteResponse.quote.steps[0],
action: ActionTypes.BRIDGE,
destChainId: ChainId.ETH,
destAsset: {
chainId: ChainId.ETH,
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
symbol: 'USDC',
name: 'USD Coin',
decimals: 6,
assetId:
'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as CaipAssetType,
},
},
],
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const handleTronApproval = async (
quoteResponse.approval,
quoteResponse,
args.selectedAccount,
true,
);
return txMeta.id;
}
Expand Down
37 changes: 37 additions & 0 deletions packages/bridge-status-controller/src/utils/snaps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,43 @@ describe('Snaps Utils', () => {
}
`);
});

it('omits the swap asset ids for an approval transaction', async () => {
const snapId = 'test-snap-id';
const transaction = 'base64-encoded-approval';
const accountId = 'test-account-id';

const mockCall = jest.fn((...args: unknown[]) => {
const [action] = args;
if (action === 'SnapController:handleRequest') {
return Promise.resolve({ transactionId: 'approval-tx-id' });
}
});
const messenger = {
call: (...args: unknown[]) => mockCall(...args),
} as unknown as BridgeStatusControllerMessenger;

await handleNonEvmTx(
messenger,
transaction,
{
quote: {
srcChainId: ChainId.SOLANA,
srcAsset: { symbol: 'SOL', assetId: 'solana:src/slip44:501' },
destAsset: { symbol: 'USDC', assetId: 'solana:dest/token:usdc' },
},
sentAmount: { amount: '1000000000' },
} as never,
{ id: accountId, metadata: { snap: { id: snapId } } } as never,
true,
);

const [, request] = mockCall.mock.calls[0] as [
string,
{ request: { params: { options?: unknown } } },
];
expect(request.request.params.options).toBeUndefined();
});
});

describe('createClientTransactionRequest', () => {
Expand Down
35 changes: 21 additions & 14 deletions packages/bridge-status-controller/src/utils/snaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
formatChainIdToCaip,
formatChainIdToHex,
isCrossChain,
isStellarTrade,
isNonEvmChainId,
isTronChainId,
isTronTrade,
} from '@metamask/bridge-controller';
import { SnapController } from '@metamask/snaps-controllers';
Expand Down Expand Up @@ -90,11 +91,11 @@ export const getClientRequest = (
const transaction = extractTradeData(trade);

let options: Record<string, unknown> | undefined;
const isTron = isTronChainId(srcChainId);

// Only Stellar trades expect asset IDs in the request options. Passing them
// for other non-EVM chains (e.g. Bitcoin) breaks strict snap request
// validation and prevents the transaction from being broadcast.
if (isStellarTrade(trade)) {
// Stellar, Solana, and Bitcoin trades expect the swap/bridge asset IDs in the
// request options; Tron also adds its own fields in the branch below.
if (isNonEvmChainId(srcChainId)) {
if (sourceAssetId !== undefined || destAssetId !== undefined) {
options = {
...(sourceAssetId !== undefined && {
Expand All @@ -105,14 +106,14 @@ export const getClientRequest = (
}),
};
}
}

if (isTronTrade(trade)) {
// Tron trades need the visible flag and contract type to be included in the request options
options = {
visible: trade.visible,
type: trade.raw_data?.contract?.[0]?.type,
};
if (isTron && isTronTrade(trade)) {
options = {
...(options ?? {}),
visible: trade.visible,
type: trade.raw_data?.contract?.[0]?.type,
};
}
}

return createClientTransactionRequest(
Expand Down Expand Up @@ -254,27 +255,33 @@ export const handleNonEvmTxResponse = (
* @param trade - The trade data (can be approval or main trade)
* @param quoteResponse - The quote response containing metadata
* @param selectedAccount - The account to submit the transaction for
* @param isApproval - Whether the trade is the approval transaction rather than the main trade
* @returns The transaction meta
*/
export const handleNonEvmTx = async (
messenger: BridgeStatusControllerMessenger,
trade: Trade,
quoteResponse: QuoteResponseV1<Trade, Trade> & QuoteMetadata,
selectedAccount: AccountsControllerState['internalAccounts']['accounts'][string],
isApproval = false,
): Promise<TransactionMeta> => {
if (!selectedAccount.metadata?.snap?.id) {
throw new Error(
'Failed to submit cross-chain swap transaction: undefined snap id',
);
}

// The quote's asset ids describe the swap/bridge transaction, not the token
// approval that precedes it. Passing them for the approval would make the snap
// classify the approval as a swap or a bridge, so they are only attached to
// the main trade.
const request = getClientRequest(
trade,
quoteResponse.quote.srcChainId,
selectedAccount.id,
selectedAccount.metadata?.snap?.id,
quoteResponse.quote.srcAsset.assetId,
quoteResponse.quote.destAsset.assetId,
isApproval ? undefined : quoteResponse.quote.srcAsset.assetId,
isApproval ? undefined : quoteResponse.quote.destAsset.assetId,
);
const requestResponse = (await messenger.call(
'SnapController:handleRequest',
Expand Down
Loading
Loading