Skip to content

Commit 12180e5

Browse files
ghgoodreauSteP-n-s
andauthored
fix: activity list improvements when selecting nonevm (#38529)
## **Description** When a Solana account is selected, EVM swap transactions from the same account group were not showing in the activity list. Additionally, swap transactions were displaying incomplete information like "Swap to" instead of "Swap ETH to USDC". This PR fixes both issues by: - Fetching EVM transactions for the account group's EVM address when a non-EVM account is selected - Using `initialTransaction` instead of `primaryTransaction` for bridge history lookups (matching the behavior of `useTransactionDisplayData`) - Using `getAllNetworkTransactions` in the swap details page so transactions can be found regardless of which account is selected ## **Changelog** CHANGELOG entry: Fixed swap transactions not appearing in activity list when Solana account is selected ## **Related issues** Fixes: #36361 ## **Manual testing steps** 1. Create or use an account group that has both an EVM account and a Solana account 2. Do a swap from the EVM account (e.g. ETH to USDC on Base) 3. Switch to the Solana account in the same group 4. Go to the Activity tab 5. Verify the swap transaction appears with correct info (e.g. "Swap ETH to USDC") 6. Click on the swap transaction 7. Verify the swap details page shows all information (status, timestamp, amounts, gas fee, nonce) ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/b514adc4-1054-4541-a481-b865f602d5b5 ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/3e65c6eb-bb5c-4b4d-8134-1ca2dc6fa06e ## **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] > Shows EVM swap/smart transactions from the account group when a non‑EVM account is selected and fixes bridge/swap details by using initialTransaction and querying all-network transactions. > > - **Activity list (unified)**: > - Fetch and display EVM swaps/smart txs for the account group's EVM address when a non‑EVM account is selected, using `getAllNetworkTransactions` and `smartTransactions` with nonce grouping via `groupAndSortTransactionsByNonce` and `PENDING_STATUS_HASH` filtering. > - Exclude `incoming` type, dedupe against smart tx nonces, and filter by enabled networks across namespaces. > - Maintain unified list with non‑EVM items; render smart transactions via `SmartTransactionListItem`. > - **Bridge/Swap display data**: > - Switch bridge history lookups and token display fields to `initialTransaction`. > - **Tx details page**: > - Use `getAllNetworkTransactions` to locate `src`/approval txs so details load regardless of selected account. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d31df26. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: SteP-n-s <[email protected]>
1 parent ef9f3a3 commit 12180e5

File tree

3 files changed

+132
-33
lines changed

3 files changed

+132
-33
lines changed

ui/components/app/transaction-list/unified-transaction-list.component.js

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import { TransactionType as KeyringTransactionType } from '@metamask/keyring-api
1515
import {
1616
nonceSortedCompletedTransactionsSelectorAllChains,
1717
nonceSortedPendingTransactionsSelectorAllChains,
18+
getAllNetworkTransactions,
19+
groupAndSortTransactionsByNonce,
20+
smartTransactionsListSelector,
1821
} from '../../../selectors/transactions';
22+
import { getInternalAccountBySelectedAccountGroupAndCaip } from '../../../selectors/multichain-accounts/account-tree';
1923
import {
2024
getSelectedAccount,
2125
getSelectedMultichainNetworkChainId,
@@ -31,7 +35,12 @@ import SmartTransactionListItem from '../transaction-list-item/smart-transaction
3135
import {
3236
TOKEN_CATEGORY_HASH,
3337
TransactionKind,
38+
PENDING_STATUS_HASH,
3439
} from '../../../helpers/constants/transactions';
40+
import {
41+
SmartTransactionStatus,
42+
TransactionGroupCategory,
43+
} from '../../../../shared/constants/transaction';
3544
import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP } from '../../../../shared/constants/swaps';
3645
import { isEqualCaseInsensitive } from '../../../../shared/modules/string-utils';
3746
import {
@@ -75,7 +84,6 @@ import {
7584
KEYRING_TRANSACTION_STATUS_KEY,
7685
useMultichainTransactionDisplay,
7786
} from '../../../hooks/useMultichainTransactionDisplay';
78-
import { TransactionGroupCategory } from '../../../../shared/constants/transaction';
7987
///: END:ONLY_INCLUDE_IF
8088

8189
import { endTrace, TraceName } from '../../../../shared/lib/trace';
@@ -429,18 +437,115 @@ export default function UnifiedTransactionList({
429437
);
430438
///: END:ONLY_INCLUDE_IF
431439

432-
const unfilteredPendingTransactionsAllChains = useSelector(
433-
nonceSortedPendingTransactionsSelectorAllChains,
440+
const accountGroupEvmAccount = useSelector((state) =>
441+
getInternalAccountBySelectedAccountGroupAndCaip(state, 'eip155:1'),
434442
);
443+
const groupEvmAddress = accountGroupEvmAccount?.address?.toLowerCase();
435444

436-
const unfilteredPendingTransactions = useMemo(() => {
437-
return unfilteredPendingTransactionsAllChains;
438-
}, [unfilteredPendingTransactionsAllChains]);
445+
const bridgeHistoryItems = useSelector(selectBridgeHistoryForAccountGroup);
439446

440-
const unfilteredCompletedTransactionsAllChains = useSelector(
447+
const pendingFromSelectedAccount = useSelector(
448+
nonceSortedPendingTransactionsSelectorAllChains,
449+
);
450+
const completedFromSelectedAccount = useSelector(
441451
nonceSortedCompletedTransactionsSelectorAllChains,
442452
);
443453

454+
const needsGroupEvmTransactions =
455+
groupEvmAddress &&
456+
groupEvmAddress !== selectedAccount?.address?.toLowerCase();
457+
458+
const allTransactions = useSelector(getAllNetworkTransactions);
459+
460+
const allSmartTransactionsState = useSelector(
461+
(state) => state.metamask.smartTransactionsState?.smartTransactions,
462+
);
463+
const smartTransactionsForSelected = useSelector(
464+
smartTransactionsListSelector,
465+
);
466+
467+
const smartTransactions = useMemo(() => {
468+
if (!needsGroupEvmTransactions) {
469+
return smartTransactionsForSelected ?? [];
470+
}
471+
472+
if (!allSmartTransactionsState || !groupEvmAddress) {
473+
return [];
474+
}
475+
476+
const allSmartTxs = Object.values(allSmartTransactionsState).flat();
477+
478+
const filtered = allSmartTxs.filter((stx) => {
479+
const fromAddress = stx?.txParams?.from?.toLowerCase();
480+
const isSwapType =
481+
stx.type === TransactionType.swap ||
482+
stx.type === TransactionType.swapApproval;
483+
484+
return fromAddress === groupEvmAddress && isSwapType;
485+
});
486+
487+
return filtered.map((stx) => ({
488+
...stx,
489+
id: stx.uuid,
490+
isSmartTransaction: true,
491+
status: stx.status?.startsWith('cancelled')
492+
? SmartTransactionStatus.cancelled
493+
: stx.status,
494+
}));
495+
}, [
496+
needsGroupEvmTransactions,
497+
smartTransactionsForSelected,
498+
allSmartTransactionsState,
499+
groupEvmAddress,
500+
]);
501+
502+
const unfilteredPendingTransactions = useMemo(() => {
503+
if (needsGroupEvmTransactions) {
504+
const evmTxs = [...allTransactions, ...(smartTransactions ?? [])]
505+
.filter((tx) => tx.txParams?.from?.toLowerCase() === groupEvmAddress)
506+
.filter((tx) => tx.type !== TransactionType.incoming)
507+
.filter((tx) => tx.status in PENDING_STATUS_HASH);
508+
509+
return groupAndSortTransactionsByNonce(evmTxs);
510+
}
511+
512+
return pendingFromSelectedAccount;
513+
}, [
514+
needsGroupEvmTransactions,
515+
pendingFromSelectedAccount,
516+
allTransactions,
517+
smartTransactions,
518+
groupEvmAddress,
519+
]);
520+
521+
const unfilteredCompletedTransactionsAllChains = useMemo(() => {
522+
if (needsGroupEvmTransactions) {
523+
const smartTxs = smartTransactions ?? [];
524+
const smartTxNonces = new Set(
525+
smartTxs.map((tx) => tx.txParams?.nonce).filter((n) => n !== undefined),
526+
);
527+
528+
const evmTxs = [...allTransactions, ...smartTxs]
529+
.filter((tx) => tx.txParams?.from?.toLowerCase() === groupEvmAddress)
530+
.filter((tx) => tx.type !== TransactionType.incoming)
531+
.filter((tx) => !(tx.status in PENDING_STATUS_HASH))
532+
.filter(
533+
(tx) =>
534+
tx.isSmartTransaction || !smartTxNonces.has(tx.txParams?.nonce),
535+
);
536+
537+
return groupAndSortTransactionsByNonce(evmTxs);
538+
}
539+
540+
return completedFromSelectedAccount;
541+
}, [
542+
needsGroupEvmTransactions,
543+
completedFromSelectedAccount,
544+
allTransactions,
545+
smartTransactions,
546+
groupEvmAddress,
547+
]);
548+
444549
const enabledNetworksForAllNamespaces = useSelector(
445550
getAllEnabledNetworksForAllNamespaces,
446551
);
@@ -537,7 +642,6 @@ export default function UnifiedTransactionList({
537642
getSelectedMultichainNetworkConfiguration,
538643
);
539644

540-
const bridgeHistoryItems = useSelector(selectBridgeHistoryForAccountGroup);
541645
const selectedBridgeHistoryItem = useSelector((state) =>
542646
selectBridgeHistoryItemForTxMetaId(state, selectedTransaction?.id),
543647
);

ui/pages/bridge/hooks/useBridgeTokenDisplayData.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,46 +16,45 @@ import {
1616
* @param transactionGroup - A Bridge transaction group
1717
*/
1818
export function useBridgeTokenDisplayData(transactionGroup: TransactionGroup) {
19-
const { primaryTransaction } = transactionGroup;
19+
const { initialTransaction } = transactionGroup;
2020

21-
// If the primary transaction is a bridge transaction, use the bridge history item for the primary transaction id
22-
// Otherwise, assume that the primary transaction is an approval transaction and use the bridge history item that has the approvalTxId
23-
const bridgeHistoryItemForPrimaryTxId = useSelector((state) =>
24-
selectBridgeHistoryItemForTxMetaId(state, primaryTransaction.id),
21+
const bridgeHistoryItemForInitialTxId = useSelector((state) =>
22+
selectBridgeHistoryItemForTxMetaId(state, initialTransaction.id),
2523
);
2624
const bridgeHistoryItemWithApprovalTxId = useSelector((state) =>
27-
selectBridgeHistoryForApprovalTxId(state, primaryTransaction.id),
25+
selectBridgeHistoryForApprovalTxId(state, initialTransaction.id),
2826
);
27+
2928
const bridgeHistoryItem: BridgeHistoryItem | undefined =
30-
bridgeHistoryItemForPrimaryTxId ?? bridgeHistoryItemWithApprovalTxId;
29+
bridgeHistoryItemForInitialTxId ?? bridgeHistoryItemWithApprovalTxId;
3130

3231
// Display currency can be fiat or a token
3332
const displayCurrencyAmount = useTokenFiatAmount(
3433
bridgeHistoryItem?.quote.srcAsset.address ??
35-
primaryTransaction.sourceTokenAddress,
34+
initialTransaction.sourceTokenAddress,
3635
bridgeHistoryItem?.pricingData?.amountSent ??
37-
primaryTransaction.sourceTokenAmount,
36+
initialTransaction.sourceTokenAmount,
3837
bridgeHistoryItem?.quote.srcAsset.symbol ??
39-
primaryTransaction.sourceTokenSymbol,
38+
initialTransaction.sourceTokenSymbol,
4039
{},
4140
true,
42-
primaryTransaction.chainId,
41+
initialTransaction.chainId,
4342
);
4443

4544
return {
4645
category:
47-
primaryTransaction.type === TransactionType.bridge
46+
initialTransaction.type === TransactionType.bridge
4847
? TransactionGroupCategory.bridge
4948
: TransactionGroupCategory.swap,
5049
displayCurrencyAmount,
5150
sourceTokenSymbol:
5251
bridgeHistoryItem?.quote.srcAsset.symbol ??
53-
primaryTransaction.sourceTokenSymbol,
52+
initialTransaction.sourceTokenSymbol,
5453
sourceTokenAmountSent:
5554
bridgeHistoryItem?.pricingData?.amountSent ??
56-
primaryTransaction.sourceTokenAmount,
55+
initialTransaction.sourceTokenAmount,
5756
destinationTokenSymbol:
5857
bridgeHistoryItem?.quote.destAsset.symbol ??
59-
primaryTransaction.destinationTokenSymbol,
58+
initialTransaction.destinationTokenSymbol,
6059
};
6160
}

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

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,8 @@ import {
4545
import { formatDate } from '../../../helpers/utils/util';
4646
import { ConfirmInfoRowDivider as Divider } from '../../../components/app/confirm/info/row';
4747
import { useI18nContext } from '../../../hooks/useI18nContext';
48-
import {
49-
getNativeTokenInfo,
50-
selectedAddressTxListSelectorAllChain,
51-
} from '../../../selectors';
48+
import { getNativeTokenInfo } from '../../../selectors';
49+
import { getAllNetworkTransactions } from '../../../selectors/transactions';
5250
import {
5351
MetaMetricsContextProp,
5452
MetaMetricsEventCategory,
@@ -96,22 +94,20 @@ const CrossChainSwapTxDetails = ({
9694
const rootState = useSelector((state) => state);
9795

9896
const srcTxMetaId = params?.srcTxMetaId;
99-
const selectedAddressTxList = useSelector(
100-
selectedAddressTxListSelectorAllChain,
97+
const allTransactions = useSelector(
98+
getAllNetworkTransactions,
10199
) as TransactionMeta[];
102100

103101
const transactionGroup: TransactionGroup | null =
104102
location?.state?.transactionGroup || null;
105103
const isEarliestNonce: boolean | null =
106104
location?.state?.isEarliestNonce || null;
107-
const srcChainTxMeta = selectedAddressTxList.find(
108-
(tx) => tx.id === srcTxMetaId,
109-
);
105+
const srcChainTxMeta = allTransactions.find((tx) => tx.id === srcTxMetaId);
110106
// Even if user is still on /tx-details/txMetaId, we want to be able to show the bridge history item
111107
const bridgeHistoryItem = useSelector((state) =>
112108
selectBridgeHistoryItemForTxMetaId(state, srcTxMetaId),
113109
);
114-
const approvalTxMeta = selectedAddressTxList.find(
110+
const approvalTxMeta = allTransactions.find(
115111
(tx) => tx.id === bridgeHistoryItem?.approvalTxId,
116112
);
117113

0 commit comments

Comments
 (0)