Skip to content

Commit e44ec99

Browse files
ghgoodreaumetamaskbotSteP-n-sgauthierpetetin
authored
fix: cp-13.5.0 bitcoin activity list updates and general followups (#36645) (#36854)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Original PR into main: #36645 Updates multichain activity list components to be non-EVM chain agnostic from being Solana-specific. Fixes a bug which incorrectly showed solana txs as bitcoin txs. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/36854?quickstart=1) ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: fixes a bug in which multichain transaction history was showing incorrect chain information. ## **Related issues** Fixes: #36751 ## **Manual testing steps** 1. Go to the activity list 2. Solana bridges should show correct information (name and icon should be solana) 3. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Generalizes bridge activity/details to support non-EVM chains (incl. Bitcoin), fixes chain badges/explorer logic, and updates rate/namespace checks. > > - **UI (Activity & Details)** > - Rename Solana-specific classes/test IDs to multichain (`multichain-bridge-transaction-*-*`). > - Use `useBridgeChainInfo` for `srcNetwork`/`destNetwork`; derive source network `name`/icon via `MULTICHAIN_NETWORK_TO_NICKNAME` and `MULTICHAIN_TOKEN_IMAGE_MAP`. > - Replace Solana-only logic with `isNonEvmChainId` for explorer URL handling and network detection. > - Remove Solana account branching; badges/icons now reflect actual source chain. > - **Bridge State/Selectors** > - Swap `isSolanaChainId` checks with `isNonEvmChainId` where appropriate; compute cross-namespace via `parseCaipChainId`. > - Add Bitcoin-specific exchange rate handling using multichain rates; keep EVM via market data. > - **Explorer Links** > - Determine human-readable explorer name using `isNonEvmChainId` and CAIP-to-hex formatting. > - **Styles** > - Update SCSS block/class names from `solana-...` to `multichain-...`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2862270. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: MetaMask Bot <[email protected]> Co-authored-by: SteP-n-s <[email protected]> Co-authored-by: Gauthier Petetin <[email protected]>
1 parent 4f220ee commit e44ec99

File tree

7 files changed

+76
-52
lines changed

7 files changed

+76
-52
lines changed

ui/components/app/multichain-bridge-transaction-details-modal/index.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@use "design-system";
22

3-
.solana-bridge-transaction-details-modal {
3+
.multichain-bridge-transaction-details-modal {
44
&__content {
55
overflow-y: auto;
66
max-height: 60vh; // Allow scrolling if content exceeds this height

ui/components/app/multichain-bridge-transaction-details-modal/multichain-bridge-transaction-details-modal.tsx

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { BridgeHistoryItem } from '@metamask/bridge-status-controller';
44
import {
55
formatChainIdToCaip,
66
formatChainIdToHex,
7+
isNonEvmChainId,
78
} from '@metamask/bridge-controller';
89
import {
910
Display,
@@ -52,9 +53,8 @@ import {
5253
} from '../multichain-transaction-details-modal/helpers';
5354
import { formatBlockExplorerTransactionUrl } from '../../../../shared/lib/multichain/networks';
5455
import {
55-
MULTICHAIN_PROVIDER_CONFIGS,
56-
MultichainNetworks,
57-
SOLANA_TOKEN_IMAGE_URL,
56+
MULTICHAIN_NETWORK_TO_NICKNAME,
57+
MULTICHAIN_TOKEN_IMAGE_MAP,
5858
MULTICHAIN_NETWORK_BLOCK_EXPLORER_FORMAT_URLS_MAP,
5959
} from '../../../../shared/constants/multichain/networks';
6060
import { CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP } from '../../../../shared/constants/network';
@@ -109,11 +109,10 @@ const MultichainBridgeTransactionDetailsModal = ({
109109

110110
try {
111111
const caipChainId = formatChainIdToCaip(chainId);
112-
const isSolana = caipChainId === MultichainNetworks.SOLANA;
113112

114113
let blockExplorerUrl = '';
115114

116-
if (isSolana) {
115+
if (isNonEvmChainId(chainId)) {
117116
const blockExplorerUrls =
118117
MULTICHAIN_NETWORK_BLOCK_EXPLORER_FORMAT_URLS_MAP[caipChainId];
119118
if (blockExplorerUrls) {
@@ -179,17 +178,25 @@ const MultichainBridgeTransactionDetailsModal = ({
179178
nonEvmTransaction: transaction,
180179
});
181180

181+
// Get source network info from chain ID
182+
const sourceNetworkNickname = srcNetwork?.chainId
183+
? MULTICHAIN_NETWORK_TO_NICKNAME[srcNetwork.chainId]
184+
: undefined;
185+
const sourceNetworkImage = srcNetwork?.chainId
186+
? MULTICHAIN_TOKEN_IMAGE_MAP[srcNetwork.chainId]
187+
: undefined;
188+
182189
return (
183190
<Modal
184191
onClose={onClose}
185-
data-testid="solana-bridge-transaction-details-modal"
192+
data-testid="multichain-bridge-transaction-details-modal"
186193
isOpen
187194
isClosedOnOutsideClick
188195
isClosedOnEscapeKey
189196
>
190197
<ModalOverlay />
191198
<ModalContent
192-
className="solana-bridge-transaction-details-modal"
199+
className="multichain-bridge-transaction-details-modal"
193200
modalDialogProps={{
194201
display: Display.Flex,
195202
flexDirection: FlexDirection.Column,
@@ -213,7 +220,7 @@ const MultichainBridgeTransactionDetailsModal = ({
213220
</Box>
214221
{/* Scrollable Content Section */}
215222
<Box
216-
className="solana-bridge-transaction-details-modal__content"
223+
className="multichain-bridge-transaction-details-modal__content"
217224
style={{ overflow: 'auto', flex: '1' }}
218225
>
219226
{/* Status Section */}
@@ -355,19 +362,13 @@ const MultichainBridgeTransactionDetailsModal = ({
355362
>
356363
<AvatarNetwork
357364
size={AvatarNetworkSize.Sm}
358-
className="solana-bridge-transaction-details-modal__network-badge"
359-
name={
360-
MULTICHAIN_PROVIDER_CONFIGS[MultichainNetworks.SOLANA]
361-
.nickname
362-
}
363-
src={SOLANA_TOKEN_IMAGE_URL}
365+
className="multichain-bridge-transaction-details-modal__network-badge"
366+
name={sourceNetworkNickname ?? ''}
367+
src={sourceNetworkImage ?? ''}
364368
borderColor={BorderColor.backgroundDefault}
365369
/>
366370
<Text variant={TextVariant.bodyMd}>
367-
{
368-
MULTICHAIN_PROVIDER_CONFIGS[MultichainNetworks.SOLANA]
369-
.nickname
370-
}
371+
{sourceNetworkNickname}
371372
</Text>
372373
</Box>
373374
</Box>
@@ -391,7 +392,7 @@ const MultichainBridgeTransactionDetailsModal = ({
391392
>
392393
<AvatarNetwork
393394
size={AvatarNetworkSize.Sm}
394-
className="solana-bridge-transaction-details-modal__network-badge"
395+
className="multichain-bridge-transaction-details-modal__network-badge"
395396
name={
396397
destNetwork?.chainId
397398
? (NETWORK_TO_SHORT_NETWORK_NAME_MAP[

ui/components/app/multichain-bridge-transaction-list-item/index.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@use "design-system";
22

3-
.solana-bridge-transaction-list-item {
3+
.multichain-bridge-transaction-list-item {
44
&__segment {
55
height: 4px;
66
width: 0;

ui/components/app/multichain-bridge-transaction-list-item/multichain-bridge-transaction-list-item.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
isBridgeFailed,
1111
} from '../../../../shared/lib/bridge-status/utils';
1212
import { useI18nContext } from '../../../hooks/useI18nContext';
13-
import { isSelectedInternalAccountSolana } from '../../../selectors/accounts';
1413
import { KEYRING_TRANSACTION_STATUS_KEY } from '../../../hooks/useMultichainTransactionDisplay';
1514
import { formatTimestamp } from '../multichain-transaction-details-modal/helpers';
1615
import TransactionIcon from '../transaction-icon';
@@ -36,10 +35,8 @@ import {
3635
AvatarNetworkSize,
3736
} from '../../component-library';
3837
import {
39-
MULTICHAIN_PROVIDER_CONFIGS,
40-
MultichainNetworks,
41-
SOLANA_TOKEN_IMAGE_URL,
42-
BITCOIN_TOKEN_IMAGE_URL,
38+
MULTICHAIN_NETWORK_TO_NICKNAME,
39+
MULTICHAIN_TOKEN_IMAGE_MAP,
4340
} from '../../../../shared/constants/multichain/networks';
4441
import { TransactionGroupCategory } from '../../../../shared/constants/transaction';
4542
import { NETWORK_TO_SHORT_NETWORK_NAME_MAP } from '../../../../shared/constants/bridge';
@@ -54,7 +51,7 @@ type MultichainBridgeTransactionListItemProps = {
5451
};
5552

5653
/**
57-
* Renders a transaction list item specifically for Solana bridge operations,
54+
* Renders a transaction list item for multichain bridge operations (Solana, Bitcoin, etc.),
5855
* displaying progress across source and destination chains.
5956
*
6057
* @param options0 - Component props
@@ -68,8 +65,6 @@ const MultichainBridgeTransactionListItem: React.FC<
6865
const t = useI18nContext();
6966
const locale = useSelector(getIntlLocale);
7067

71-
const isSolanaAccount = useSelector(isSelectedInternalAccountSolana);
72-
7368
const isSourceTxConfirmed =
7469
transaction.status === TransactionStatus.Confirmed;
7570

@@ -96,11 +91,19 @@ const MultichainBridgeTransactionListItem: React.FC<
9691

9792
const txIndex = isSourceTxConfirmed ? 2 : 1;
9893

99-
const { destNetwork } = useBridgeChainInfo({
94+
const { srcNetwork, destNetwork } = useBridgeChainInfo({
10095
bridgeHistoryItem,
10196
nonEvmTransaction: transaction,
10297
});
10398

99+
// Get source network info from chain ID
100+
const sourceNetworkNickname = srcNetwork?.chainId
101+
? MULTICHAIN_NETWORK_TO_NICKNAME[srcNetwork.chainId]
102+
: undefined;
103+
const sourceNetworkImage = srcNetwork?.chainId
104+
? MULTICHAIN_TOKEN_IMAGE_MAP[srcNetwork.chainId]
105+
: undefined;
106+
104107
const displayChainName =
105108
(destNetwork?.chainId
106109
? NETWORK_TO_SHORT_NETWORK_NAME_MAP[destNetwork.chainId]
@@ -112,8 +115,8 @@ const MultichainBridgeTransactionListItem: React.FC<
112115

113116
return (
114117
<ActivityListItem
115-
className="solana-bridge-transaction-list-item"
116-
data-testid="solana-bridge-activity-item"
118+
className="multichain-bridge-transaction-list-item"
119+
data-testid="multichain-bridge-activity-item"
117120
onClick={() => toggleShowDetails(transaction)}
118121
icon={
119122
<BadgeWrapper
@@ -124,19 +127,9 @@ const MultichainBridgeTransactionListItem: React.FC<
124127
borderWidth={1}
125128
className="activity-tx__network-badge"
126129
data-testid="activity-tx-network-badge"
127-
name={
128-
isSolanaAccount
129-
? MULTICHAIN_PROVIDER_CONFIGS[MultichainNetworks.SOLANA]
130-
.nickname
131-
: MULTICHAIN_PROVIDER_CONFIGS[MultichainNetworks.BITCOIN]
132-
.nickname
133-
}
130+
name={sourceNetworkNickname ?? ''}
134131
size={AvatarNetworkSize.Xs}
135-
src={
136-
isSolanaAccount
137-
? SOLANA_TOKEN_IMAGE_URL
138-
: BITCOIN_TOKEN_IMAGE_URL
139-
}
132+
src={sourceNetworkImage ?? ''}
140133
/>
141134
}
142135
display={Display.Block}

ui/ducks/bridge/bridge.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
formatChainIdToCaip,
66
getNativeAssetForChainId,
77
calcLatestSrcBalance,
8-
isSolanaChainId,
8+
isNonEvmChainId,
99
isCrossChain,
1010
formatChainIdToHex,
1111
type GenericQuoteRequest,
@@ -63,7 +63,7 @@ const getBalanceAmount = async ({
6363
tokenAddress: string;
6464
chainId: GenericQuoteRequest['srcChainId'];
6565
}) => {
66-
if (isSolanaChainId(chainId) || !selectedAddress) {
66+
if (isNonEvmChainId(chainId) || !selectedAddress) {
6767
return null;
6868
}
6969
return (

ui/ducks/bridge/selectors.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
selectMinimumBalanceForRentExemptionInSOL,
1717
isValidQuoteRequest,
1818
isCrossChain,
19+
isBitcoinChainId,
1920
} from '@metamask/bridge-controller';
2021
import type { RemoteFeatureFlagControllerState } from '@metamask/remote-feature-flag-controller';
2122
import { SolAccountType } from '@metamask/keyring-api';
@@ -522,6 +523,30 @@ export const getFromTokenConversionRate = createSelector(
522523
Number(nativeToUsdRate),
523524
);
524525
}
526+
527+
if (
528+
isBitcoinChainId(fromChain.chainId) &&
529+
nativeAssetId &&
530+
tokenAssetId
531+
) {
532+
// For Bitcoin tokens, we use the conversion rates provided by the multichain rates controller
533+
const nativeAssetRate = Number(
534+
conversionRates?.[nativeAssetId as CaipAssetType]?.rate ?? null,
535+
);
536+
const tokenToNativeAssetRate = tokenPriceInNativeAsset(
537+
Number(
538+
conversionRates?.[tokenAssetId]?.rate ??
539+
fromTokenExchangeRate ??
540+
null,
541+
),
542+
nativeAssetRate,
543+
);
544+
return exchangeRatesFromNativeAndCurrencyRates(
545+
tokenToNativeAssetRate,
546+
Number(nativeToCurrencyRate),
547+
Number(nativeToUsdRate),
548+
);
549+
}
525550
// For EVM tokens, we use the market data to get the exchange rate
526551
const tokenToNativeAssetRate =
527552
exchangeRateFromMarketData(
@@ -830,11 +855,16 @@ export const getIsToOrFromSolana = createSelector(
830855
return false;
831856
}
832857

833-
const fromChainIsSolana = isSolanaChainId(fromChain.chainId);
834-
const toChainIsSolana = isSolanaChainId(toChain.chainId);
858+
// Parse the CAIP chain IDs to get their namespaces
859+
const fromCaipChainId = formatChainIdToCaip(fromChain.chainId);
860+
const toCaipChainId = formatChainIdToCaip(toChain.chainId);
861+
862+
const { namespace: fromNamespace } = parseCaipChainId(fromCaipChainId);
863+
const { namespace: toNamespace } = parseCaipChainId(toCaipChainId);
835864

836-
// Only return true if either chain is Solana and the other is EVM
837-
return toChainIsSolana !== fromChainIsSolana;
865+
// Return true if chains are in different namespaces
866+
// This covers EVM <> non-EVM as well as non-EVM <> non-EVM (e.g., Solana <> Bitcoin)
867+
return fromNamespace !== toNamespace;
838868
},
839869
);
840870

ui/pages/bridge/transaction-details/bridge-explorer-links.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
22
import type { CaipChainId } from '@metamask/utils';
33
import {
44
formatChainIdToHex,
5-
isSolanaChainId,
5+
isNonEvmChainId,
66
} from '@metamask/bridge-controller';
77
import { CHAINID_DEFAULT_BLOCK_EXPLORER_HUMAN_READABLE_URL_MAP } from '../../../../shared/constants/common';
88
import {
@@ -23,7 +23,7 @@ const getBlockExplorerName = (
2323
blockExplorerUrl: string | undefined,
2424
) => {
2525
const hexChainId =
26-
chainId && !isSolanaChainId(chainId)
26+
chainId && !isNonEvmChainId(chainId)
2727
? formatChainIdToHex(chainId)
2828
: undefined;
2929
const humanReadableUrl = hexChainId

0 commit comments

Comments
 (0)