diff --git a/app/components/UI/TransactionElement/index.js b/app/components/UI/TransactionElement/index.js index e0a35415e5b0..ae7597c6e8e7 100644 --- a/app/components/UI/TransactionElement/index.js +++ b/app/components/UI/TransactionElement/index.js @@ -26,7 +26,10 @@ import { } from '@metamask/transaction-controller'; import { ThemeContext, mockTheme } from '../../../util/theme'; import { selectTickerByChainId } from '../../../selectors/networkController'; -import { selectSelectedInternalAccount } from '../../../selectors/accountsController'; +import { + selectSelectedInternalAccount, + selectSelectedInternalAccountAddress, +} from '../../../selectors/accountsController'; import { selectSelectedAccountGroupInternalAccounts } from '../../../selectors/multichainAccounts/accountTreeController'; import { selectPrimaryCurrency } from '../../../selectors/settings'; import { @@ -56,7 +59,7 @@ import { selectCurrencyRates, } from '../../../selectors/currencyRateController'; import { selectContractExchangeRatesByChainId } from '../../../selectors/tokenRatesController'; -import { selectTokensByChainIdAndAddress } from '../../../selectors/tokensController'; +import { selectTokensByChainIdAndWalletAddress } from '../../../selectors/tokensController'; import Routes from '../../../constants/navigation/Routes'; import { hasGasFeeTokenSelected, @@ -224,6 +227,10 @@ class TransactionElement extends PureComponent { * Chain Id */ txChainId: PropTypes.string, + /** + * Selected wallet address for decoding and token map (optional override from parent) + */ + selectedAddress: PropTypes.string, /** * Ticker */ @@ -278,7 +285,8 @@ class TransactionElement extends PureComponent { componentDidUpdate(prevProps) { if ( prevProps.txChainId !== this.props.txChainId || - prevProps.swapsTransactions !== this.props.swapsTransactions + prevProps.swapsTransactions !== this.props.swapsTransactions || + prevProps.selectedAddress !== this.props.selectedAddress ) { this.componentDidMount(); } @@ -738,21 +746,29 @@ class TransactionElement extends PureComponent { } } -const mapStateToProps = (state, ownProps) => ({ - selectedInternalAccount: selectSelectedInternalAccount(state), - selectSelectedAccountGroupInternalAccounts: - selectSelectedAccountGroupInternalAccounts(state), - primaryCurrency: selectPrimaryCurrency(state), - swapsTransactions: selectSwapsTransactions(state), - ticker: selectTickerByChainId(state, ownProps.txChainId), - conversionRate: selectConversionRateByChainId(state, ownProps.txChainId), - currencyRates: selectCurrencyRates(state), - contractExchangeRates: selectContractExchangeRatesByChainId( - state, - ownProps.txChainId, - ), - tokens: selectTokensByChainIdAndAddress(state, ownProps.txChainId), -}); +const mapStateToProps = (state, ownProps) => { + const walletAddressForTokens = + ownProps.selectedAddress ?? selectSelectedInternalAccountAddress(state); + return { + selectedInternalAccount: selectSelectedInternalAccount(state), + selectSelectedAccountGroupInternalAccounts: + selectSelectedAccountGroupInternalAccounts(state), + primaryCurrency: selectPrimaryCurrency(state), + swapsTransactions: selectSwapsTransactions(state), + ticker: selectTickerByChainId(state, ownProps.txChainId), + conversionRate: selectConversionRateByChainId(state, ownProps.txChainId), + currencyRates: selectCurrencyRates(state), + contractExchangeRates: selectContractExchangeRatesByChainId( + state, + ownProps.txChainId, + ), + tokens: selectTokensByChainIdAndWalletAddress( + state, + ownProps.txChainId, + walletAddressForTokens, + ), + }; +}; TransactionElement.contextType = ThemeContext; diff --git a/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx b/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx index 491048c8e527..6ee79da9efbc 100644 --- a/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx +++ b/app/components/Views/UnifiedTransactionsView/UnifiedTransactionsView.tsx @@ -632,7 +632,9 @@ const UnifiedTransactionsView = ({ i={index} navigation={navigation} txChainId={getEvmChainId(item.tx)} - selectedAddress={selectedInternalAccount?.address} + selectedAddress={ + selectedAccountGroupEvmAddress || selectedInternalAccount?.address + } onSpeedUpAction={onSpeedUpAction} onCancelAction={onCancelAction} signQRTransaction={signQRTransaction} diff --git a/app/selectors/tokensController.test.ts b/app/selectors/tokensController.test.ts index 0eee0680df8f..1c453f6bb86d 100644 --- a/app/selectors/tokensController.test.ts +++ b/app/selectors/tokensController.test.ts @@ -10,6 +10,7 @@ import { selectAllDetectedTokensForSelectedAddress, selectAllDetectedTokensFlat, selectTokensByChainIdAndAddress, + selectTokensByChainIdAndWalletAddress, getChainIdsToPoll, selectSingleTokenByAddressAndChainId, } from './tokensController'; @@ -337,6 +338,34 @@ describe('TokensController Selectors', () => { }); }); + describe('selectTokensByChainIdAndWalletAddress', () => { + it('returns tokens for the given chain and explicit wallet address', () => { + expect( + selectTokensByChainIdAndWalletAddress( + mockRootState, + '0x1', + '0xAddress2', + ), + ).toStrictEqual({ '0xToken2': mockToken2 }); + }); + + it('returns empty object when wallet address has no tokens on that chain', () => { + expect( + selectTokensByChainIdAndWalletAddress( + mockRootState, + '0x2', + '0xAddress1', + ), + ).toStrictEqual({}); + }); + + it('returns empty object when wallet address is undefined', () => { + expect( + selectTokensByChainIdAndWalletAddress(mockRootState, '0x1', undefined), + ).toStrictEqual({}); + }); + }); + describe('getChainIdsToPoll', () => { const mockNetworkConfigurations = { '0x1': { chainId: '0x1' } as unknown as NetworkConfiguration, diff --git a/app/selectors/tokensController.ts b/app/selectors/tokensController.ts index a1f1148fcb45..25d2b2d88fad 100644 --- a/app/selectors/tokensController.ts +++ b/app/selectors/tokensController.ts @@ -53,6 +53,34 @@ export const selectTokensByChainIdAndAddress = createDeepEqualSelector( ) ?? {}, ); +/** + * Like {@link selectTokensByChainIdAndAddress} but uses an explicit account + * address (e.g. the EVM address for the account group) instead of the globally + * selected account. Needed when the UI shows EVM activity while a non-EVM + * account is still selected. + */ +export const selectTokensByChainIdAndWalletAddress = createDeepEqualSelector( + getTokensControllerAllTokens, + (_state: RootState, chainId: Hex, _walletAddress: Hex | string | undefined) => + chainId, + (_state: RootState, _chainId: Hex, walletAddress: Hex | string | undefined) => + walletAddress, + ( + allTokens: TokensControllerState['allTokens'], + chainId: Hex, + walletAddress: Hex | string | undefined, + ) => + !walletAddress + ? {} + : (allTokens[chainId]?.[walletAddress as Hex]?.reduce( + (tokensMap: { [address: string]: Token }, token: Token) => ({ + ...tokensMap, + [token.address]: token, + }), + {}, + ) ?? {}), +); + export const selectTokensByAddress = createSelector( selectTokens, (tokens: Token[]) =>