diff --git a/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch b/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch new file mode 100644 index 00000000000..21be6c30339 --- /dev/null +++ b/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch @@ -0,0 +1,48 @@ +diff --git a/dist/TokenBalancesController.cjs b/dist/TokenBalancesController.cjs +index 4918812dde60b8d0e24a7bded27d88f233968858..4e8018bce92b9e5d47fc40784409e16db22be615 100644 +--- a/dist/TokenBalancesController.cjs ++++ b/dist/TokenBalancesController.cjs +@@ -535,14 +535,16 @@ class TokenBalancesController extends (0, polling_controller_1.StaticIntervalPol + } + // Update with actual fetched balances only if the value has changed + aggregated.forEach(({ success, value, account, token, chainId }) => { +- var _a, _b, _c; ++ var _a, _b; + if (success && value !== undefined) { ++ // Ensure all accounts we add/update are in lower-case ++ const lowerCaseAccount = account.toLowerCase(); + const newBalance = (0, controller_utils_1.toHex)(value); + const tokenAddress = checksum(token); +- const currentBalance = d.tokenBalances[account]?.[chainId]?.[tokenAddress]; ++ const currentBalance = d.tokenBalances[lowerCaseAccount]?.[chainId]?.[tokenAddress]; + // Only update if the balance has actually changed + if (currentBalance !== newBalance) { +- ((_c = ((_a = d.tokenBalances)[_b = account] ?? (_a[_b] = {})))[chainId] ?? (_c[chainId] = {}))[tokenAddress] = newBalance; ++ ((_b = ((_a = d.tokenBalances)[lowerCaseAccount] ?? (_a[lowerCaseAccount] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = newBalance; + } + } + }); +diff --git a/dist/TokenBalancesController.mjs b/dist/TokenBalancesController.mjs +index f64d13f8de56631345a44e6ebb025e62e03f51bc..99aa7f27c574c94b26daa56091ac50d15281dd30 100644 +--- a/dist/TokenBalancesController.mjs ++++ b/dist/TokenBalancesController.mjs +@@ -531,14 +531,16 @@ export class TokenBalancesController extends StaticIntervalPollingController() { + } + // Update with actual fetched balances only if the value has changed + aggregated.forEach(({ success, value, account, token, chainId }) => { +- var _a, _b, _c; ++ var _a, _b; + if (success && value !== undefined) { ++ // Ensure all accounts we add/update are in lower-case ++ const lowerCaseAccount = account.toLowerCase(); + const newBalance = toHex(value); + const tokenAddress = checksum(token); +- const currentBalance = d.tokenBalances[account]?.[chainId]?.[tokenAddress]; ++ const currentBalance = d.tokenBalances[lowerCaseAccount]?.[chainId]?.[tokenAddress]; + // Only update if the balance has actually changed + if (currentBalance !== newBalance) { +- ((_c = ((_a = d.tokenBalances)[_b = account] ?? (_a[_b] = {})))[chainId] ?? (_c[chainId] = {}))[tokenAddress] = newBalance; ++ ((_b = ((_a = d.tokenBalances)[lowerCaseAccount] ?? (_a[lowerCaseAccount] = {})))[chainId] ?? (_b[chainId] = {}))[tokenAddress] = newBalance; + } + } + }); diff --git a/README.md b/README.md index 940055df985..9e950686925 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -![MetaMask logo](logo.png?raw=true) +MetaMask logo -# MetaMask +# MetaMask Mobile [![CI](https://github.com/MetaMask/metamask-mobile/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MetaMask/metamask-mobile/actions/workflows/ci.yml) [![CLA](https://github.com/MetaMask/metamask-mobile/actions/workflows/cla.yml/badge.svg?branch=main)](https://github.com/MetaMask/metamask-mobile/actions/workflows/cla.yml) diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index ba656a961ce..b8fb4a038f0 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -1,4 +1,10 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useSelector } from 'react-redux'; import { TouchableOpacity, @@ -9,7 +15,6 @@ import { EmitterSubscription, } from 'react-native'; import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; -import images from 'images/image-icons'; import Device from '../../../util/device'; import AvatarAccount from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount'; import { AccountRightButtonProps } from './AccountRightButton.types'; @@ -17,7 +22,10 @@ import Avatar, { AvatarVariant, AvatarSize, } from '../../../component-library/components/Avatars/Avatar'; -import { getDecimalChainId } from '../../../util/networks'; +import { + getDecimalChainId, + getNetworkImageSource, +} from '../../../util/networks'; import Routes from '../../../constants/navigation/Routes'; import { MetaMetricsEvents } from '../../../core/Analytics'; import { AccountOverviewSelectorsIDs } from '../../../../e2e/selectors/Browser/AccountOverview.selectors'; @@ -156,6 +164,12 @@ const AccountRightButton = ({ const { networkName, networkImageSource } = useNetworkInfo(hostname); + const nonEvmNetworkImageSource = useMemo(() => { + if (!isEvmSelected && selectedNonEvmNetworkChainId) { + return getNetworkImageSource({ chainId: selectedNonEvmNetworkChainId }); + } + }, [isEvmSelected, selectedNonEvmNetworkChainId]); + const renderAvatarAccount = () => ( ); @@ -179,7 +193,9 @@ const AccountRightButton = ({ : nonEvmNetworkConfigurations?.[selectedNonEvmNetworkChainId] ?.name } - imageSource={isEvmSelected ? networkImageSource : images.SOLANA} + imageSource={ + isEvmSelected ? networkImageSource : nonEvmNetworkImageSource + } /> )} diff --git a/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.styles.ts b/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.styles.ts index 8dd9f7eb6e1..ee6b89faff0 100644 --- a/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.styles.ts +++ b/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.styles.ts @@ -7,7 +7,10 @@ const styleSheet = (params: { theme: Theme }) => { return StyleSheet.create({ tradeInfoContainer: { - paddingBottom: 12, + paddingBottom: 30, + }, + emptyStateContainer: { + paddingBottom: 30, }, wrapper: { flex: 1, diff --git a/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.tsx b/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.tsx index b2b4d868f11..54296afcb6d 100644 --- a/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.tsx +++ b/app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.tsx @@ -255,11 +255,13 @@ const PerpsTabView: React.FC = () => { }} > {!isInitialLoading && hasNoPositionsOrOrders ? ( - + + + ) : ( {renderPositionsSection()} diff --git a/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.tsx b/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.tsx index 68e871b5f18..b4fa29dec3f 100644 --- a/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.tsx +++ b/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsTransactionsView.tsx @@ -343,15 +343,7 @@ const PerpsTransactionsView: React.FC = () => { ); - const filterTabs: FilterTab[] = useMemo( - () => [ - strings('perps.transactions.tabs.trades'), - strings('perps.transactions.tabs.orders'), - strings('perps.transactions.tabs.funding'), - strings('perps.transactions.tabs.deposits'), - ], - [], - ); + const filterTabs: FilterTab[] = ['Trades', 'Orders', 'Funding', 'Deposits']; const filterTabDescription = useMemo(() => { if (activeFilter === 'Funding') { diff --git a/app/components/UI/Perps/controllers/PerpsController.test.ts b/app/components/UI/Perps/controllers/PerpsController.test.ts index 26b06e7bff5..9d80caa108c 100644 --- a/app/components/UI/Perps/controllers/PerpsController.test.ts +++ b/app/components/UI/Perps/controllers/PerpsController.test.ts @@ -2166,6 +2166,7 @@ describe('PerpsController', () => { networkClientId: mockNetworkClientId, origin: 'metamask', type: 'perpsDeposit', + skipInitialGasEstimate: true, }); }); diff --git a/app/components/UI/Perps/controllers/PerpsController.ts b/app/components/UI/Perps/controllers/PerpsController.ts index cb978580726..0bbf5f1c6f0 100644 --- a/app/components/UI/Perps/controllers/PerpsController.ts +++ b/app/components/UI/Perps/controllers/PerpsController.ts @@ -1279,6 +1279,7 @@ export class PerpsController extends BaseController< networkClientId, origin: 'metamask', type: TransactionType.perpsDeposit, + skipInitialGasEstimate: true, }); // Store the transaction ID and try to get amount from transaction diff --git a/app/components/UI/Predict/controllers/PredictController.test.ts b/app/components/UI/Predict/controllers/PredictController.test.ts index d635084a50e..3f30178f3f2 100644 --- a/app/components/UI/Predict/controllers/PredictController.test.ts +++ b/app/components/UI/Predict/controllers/PredictController.test.ts @@ -2605,6 +2605,8 @@ describe('PredictController', () => { networkClientId: 'mainnet', disableHook: true, disableSequential: true, + disableUpgrade: true, + skipInitialGasEstimate: true, transactions: mockTransactions, }); }); diff --git a/app/components/UI/Predict/controllers/PredictController.ts b/app/components/UI/Predict/controllers/PredictController.ts index b6aff6f6e06..ca3612fe691 100644 --- a/app/components/UI/Predict/controllers/PredictController.ts +++ b/app/components/UI/Predict/controllers/PredictController.ts @@ -1760,6 +1760,8 @@ export class PredictController extends BaseController< networkClientId, disableHook: true, disableSequential: true, + disableUpgrade: true, + skipInitialGasEstimate: true, transactions, }); diff --git a/app/components/UI/Predict/providers/polymarket/safe/utils.ts b/app/components/UI/Predict/providers/polymarket/safe/utils.ts index 511258898e9..dde34579b94 100644 --- a/app/components/UI/Predict/providers/polymarket/safe/utils.ts +++ b/app/components/UI/Predict/providers/polymarket/safe/utils.ts @@ -389,6 +389,7 @@ export const getDeployProxyWalletTransaction = async ({ to: SAFE_FACTORY_ADDRESS as Hex, data: calldata, }, + type: TransactionType.contractInteraction, }; } catch (error) { console.error('Error creating proxy wallet', error); @@ -580,6 +581,7 @@ export const getProxyWalletAllowancesTransaction = async ({ to: safeAddress as Hex, data: callData as Hex, }, + type: TransactionType.contractInteraction, }; }; diff --git a/app/components/UI/Ramp/Aggregator/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap b/app/components/UI/Ramp/Aggregator/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap index 64c2c8aaaff..bdbe4ab2c19 100644 --- a/app/components/UI/Ramp/Aggregator/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap +++ b/app/components/UI/Ramp/Aggregator/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap @@ -231,7 +231,7 @@ exports[`BuildQuote View Balance display displays balance from useBalance for no } } > - Amount to buy + Buy @@ -3090,7 +3090,7 @@ exports[`BuildQuote View Crypto Currency Data renders a special error page if cr } } > - Amount to buy + Buy @@ -3753,7 +3753,7 @@ exports[`BuildQuote View Crypto Currency Data renders a special error page if cr } } > - Amount to sell + Sell @@ -4513,7 +4513,7 @@ exports[`BuildQuote View Crypto Currency Data renders an error page when there i } } > - Amount to buy + Buy @@ -5273,7 +5273,7 @@ exports[`BuildQuote View Crypto Currency Data renders the loading page when cryp } } > - Amount to buy + Buy @@ -7836,7 +7836,7 @@ exports[`BuildQuote View Fiat Currency Data renders an error page when there is } } > - Amount to buy + Buy @@ -8596,7 +8596,7 @@ exports[`BuildQuote View Fiat Currency Data renders the loading page when fiats } } > - Amount to buy + Buy @@ -11068,7 +11068,7 @@ exports[`BuildQuote View Payment Method Data renders an error page when there is } } > - Amount to buy + Buy @@ -11828,7 +11828,7 @@ exports[`BuildQuote View Payment Method Data renders no icons if there are no pa } } > - Amount to buy + Buy @@ -14644,7 +14644,7 @@ exports[`BuildQuote View Payment Method Data renders the loading page when payme } } > - Amount to buy + Buy @@ -17382,7 +17382,7 @@ exports[`BuildQuote View Regions data renders an error page when there is a regi } } > - Amount to buy + Buy @@ -18142,7 +18142,7 @@ exports[`BuildQuote View Regions data renders the loading page when regions are } } > - Amount to buy + Buy @@ -20578,7 +20578,7 @@ exports[`BuildQuote View renders correctly 1`] = ` } } > - Amount to buy + Buy @@ -23340,7 +23340,7 @@ exports[`BuildQuote View renders correctly 2`] = ` } } > - Amount to sell + Sell @@ -26179,7 +26179,7 @@ exports[`BuildQuote View renders correctly when sdkError is present 1`] = ` } } > - Amount to buy + Buy @@ -26842,7 +26842,7 @@ exports[`BuildQuote View renders correctly when sdkError is present 2`] = ` } } > - Amount to sell + Sell diff --git a/app/components/UI/Ramp/Deposit/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap b/app/components/UI/Ramp/Deposit/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap index 4bdcce5f13d..fdbf13a0d2e 100644 --- a/app/components/UI/Ramp/Deposit/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap +++ b/app/components/UI/Ramp/Deposit/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap @@ -231,7 +231,7 @@ exports[`BuildQuote Component Continue button functionality displays error when } } > - Deposit + Buy @@ -2105,7 +2105,7 @@ exports[`BuildQuote Component Continue button functionality displays error when } } > - Deposit + Buy @@ -3979,7 +3979,7 @@ exports[`BuildQuote Component Continue button functionality displays error when } } > - Deposit + Buy @@ -5853,7 +5853,7 @@ exports[`BuildQuote Component Keypad Functionality displays converted token amou } } > - Deposit + Buy @@ -7666,7 +7666,7 @@ exports[`BuildQuote Component Keypad Functionality updates amount when keypad is } } > - Deposit + Buy @@ -9478,7 +9478,7 @@ exports[`BuildQuote Component Payment Method Selection does not open payment met } } > - Deposit + Buy @@ -11291,7 +11291,7 @@ exports[`BuildQuote Component Payment Method Selection does not open payment met } } > - Deposit + Buy @@ -13197,7 +13197,7 @@ exports[`BuildQuote Component Payment Method Selection does not show the duratio } } > - Deposit + Buy @@ -14965,7 +14965,7 @@ exports[`BuildQuote Component Payment Method Selection shows the right duration } } > - Deposit + Buy @@ -16778,7 +16778,7 @@ exports[`BuildQuote Component Region Selection displays EUR currency when select } } > - Deposit + Buy @@ -18591,7 +18591,7 @@ exports[`BuildQuote Component Region Selection displays default US region on ini } } > - Deposit + Buy @@ -20404,7 +20404,7 @@ exports[`BuildQuote Component Region Selection does not open region modal when r } } > - Deposit + Buy @@ -22217,7 +22217,7 @@ exports[`BuildQuote Component Region Selection does not open region modal when r } } > - Deposit + Buy @@ -24123,7 +24123,7 @@ exports[`BuildQuote Component Token Selection does not open token modal when cry } } > - Deposit + Buy @@ -25934,7 +25934,7 @@ exports[`BuildQuote Component Token Selection does not open token modal when cry } } > - Deposit + Buy @@ -27838,7 +27838,7 @@ exports[`BuildQuote Component User Details Error displays user details error ale } } > - Deposit + Buy @@ -29744,7 +29744,7 @@ exports[`BuildQuote Component render matches snapshot 1`] = ` } } > - Deposit + Buy diff --git a/app/components/UI/Ramp/hooks/useRampNavigation.test.ts b/app/components/UI/Ramp/hooks/useRampNavigation.test.ts index f0fb9ef60cc..8d8330afff7 100644 --- a/app/components/UI/Ramp/hooks/useRampNavigation.test.ts +++ b/app/components/UI/Ramp/hooks/useRampNavigation.test.ts @@ -11,6 +11,8 @@ import { getRampRoutingDecision, UnifiedRampRoutingType, } from '../../../../reducers/fiatOrders'; +import { createEligibilityFailedModalNavigationDetails } from '../components/EligibilityFailedModal/EligibilityFailedModal'; +import { createRampUnsupportedModalNavigationDetails } from '../components/RampUnsupportedModal/RampUnsupportedModal'; jest.mock('@react-navigation/native'); jest.mock('@react-navigation/compat', () => ({ @@ -122,6 +124,82 @@ describe('useRampNavigation', () => { mockUseRampsUnifiedV1Enabled.mockReturnValue(true); }); + describe('error and unsupported routing', () => { + it('navigates to eligibility failed modal when routing decision is ERROR', () => { + mockGetRampRoutingDecision.mockReturnValue( + UnifiedRampRoutingType.ERROR, + ); + const navDetails = createEligibilityFailedModalNavigationDetails(); + + const { result } = renderHookWithProvider(() => useRampNavigation()); + + result.current.goToBuy(); + + expect(mockNavigate).toHaveBeenCalledWith(...navDetails); + expect(mockCreateRampNavigationDetails).not.toHaveBeenCalled(); + expect(mockCreateDepositNavigationDetails).not.toHaveBeenCalled(); + expect( + mockCreateTokenSelectionNavigationDetails, + ).not.toHaveBeenCalled(); + }); + + it('navigates to eligibility failed modal when routing decision is ERROR with intent', () => { + mockGetRampRoutingDecision.mockReturnValue( + UnifiedRampRoutingType.ERROR, + ); + const intent = { assetId: 'eip155:1/erc20:0x123' }; + const navDetails = createEligibilityFailedModalNavigationDetails(); + + const { result } = renderHookWithProvider(() => useRampNavigation()); + + result.current.goToBuy(intent); + + expect(mockNavigate).toHaveBeenCalledWith(...navDetails); + expect(mockCreateRampNavigationDetails).not.toHaveBeenCalled(); + expect(mockCreateDepositNavigationDetails).not.toHaveBeenCalled(); + expect( + mockCreateTokenSelectionNavigationDetails, + ).not.toHaveBeenCalled(); + }); + + it('navigates to unsupported modal when routing decision is UNSUPPORTED', () => { + mockGetRampRoutingDecision.mockReturnValue( + UnifiedRampRoutingType.UNSUPPORTED, + ); + const navDetails = createRampUnsupportedModalNavigationDetails(); + + const { result } = renderHookWithProvider(() => useRampNavigation()); + + result.current.goToBuy(); + + expect(mockNavigate).toHaveBeenCalledWith(...navDetails); + expect(mockCreateRampNavigationDetails).not.toHaveBeenCalled(); + expect(mockCreateDepositNavigationDetails).not.toHaveBeenCalled(); + expect( + mockCreateTokenSelectionNavigationDetails, + ).not.toHaveBeenCalled(); + }); + + it('navigates to unsupported modal when routing decision is UNSUPPORTED with intent', () => { + mockGetRampRoutingDecision.mockReturnValue( + UnifiedRampRoutingType.UNSUPPORTED, + ); + const intent = { assetId: 'eip155:1/erc20:0x123' }; + const navDetails = createRampUnsupportedModalNavigationDetails(); + + const { result } = renderHookWithProvider(() => useRampNavigation()); + + result.current.goToBuy(intent); + + expect(mockNavigate).toHaveBeenCalledWith(...navDetails); + expect(mockCreateRampNavigationDetails).not.toHaveBeenCalled(); + expect(mockCreateDepositNavigationDetails).not.toHaveBeenCalled(); + expect( + mockCreateTokenSelectionNavigationDetails, + ).not.toHaveBeenCalled(); + }); + }); + describe('token selection routing', () => { it('navigates to TokenSelection when no assetId is provided', () => { const mockNavDetails = [ diff --git a/app/components/UI/Ramp/hooks/useRampNavigation.ts b/app/components/UI/Ramp/hooks/useRampNavigation.ts index ac603fea154..dc829c1c3a7 100644 --- a/app/components/UI/Ramp/hooks/useRampNavigation.ts +++ b/app/components/UI/Ramp/hooks/useRampNavigation.ts @@ -13,6 +13,8 @@ import { getRampRoutingDecision, UnifiedRampRoutingType, } from '../../../../reducers/fiatOrders'; +import { createRampUnsupportedModalNavigationDetails } from '../components/RampUnsupportedModal/RampUnsupportedModal'; +import { createEligibilityFailedModalNavigationDetails } from '../components/EligibilityFailedModal/EligibilityFailedModal'; enum RampMode { AGGREGATOR = 'AGGREGATOR', @@ -45,6 +47,18 @@ export const useRampNavigation = () => { options || {}; if (isRampsUnifiedV1Enabled && !overrideUnifiedRouting) { + if (rampRoutingDecision === UnifiedRampRoutingType.ERROR) { + navigation.navigate( + ...createEligibilityFailedModalNavigationDetails(), + ); + return; + } + + if (rampRoutingDecision === UnifiedRampRoutingType.UNSUPPORTED) { + navigation.navigate(...createRampUnsupportedModalNavigationDetails()); + return; + } + // If no assetId is provided, route to TokenSelection if (!intent?.assetId) { navigation.navigate(...createTokenSelectionNavDetails()); diff --git a/app/components/Views/confirmations/components/pay-token-amount/pay-token-amount.tsx b/app/components/Views/confirmations/components/pay-token-amount/pay-token-amount.tsx index d310c2e9b00..2f2324ac8e5 100644 --- a/app/components/Views/confirmations/components/pay-token-amount/pay-token-amount.tsx +++ b/app/components/Views/confirmations/components/pay-token-amount/pay-token-amount.tsx @@ -44,8 +44,24 @@ export function PayTokenAmount({ amountHuman, disabled }: PayTokenAmountProps) { const fiatRates = useTokenFiatRates(fiatRequests); - const payTokenFiatRate = fiatRates[0]; - const assetFiatRate = fiatRates[1]; + const formattedAmount = useMemo(() => { + const payTokenFiatRate = fiatRates[0]; + const assetFiatRate = fiatRates[1]; + + if (disabled || !payToken || !payTokenFiatRate || !assetFiatRate) { + return undefined; + } + + const assetToPayTokenRate = new BigNumber(assetFiatRate).dividedBy( + payTokenFiatRate, + ); + + const payTokenAmount = new BigNumber(amountHuman || '0').multipliedBy( + assetToPayTokenRate, + ); + + return formatAmount(I18n.locale, payTokenAmount); + }, [amountHuman, disabled, payToken, fiatRates]); if (disabled) { return ( @@ -55,18 +71,7 @@ export function PayTokenAmount({ amountHuman, disabled }: PayTokenAmountProps) { ); } - if (!payToken || !payTokenFiatRate || !assetFiatRate) - return ; - - const assetToPayTokenRate = new BigNumber(assetFiatRate).dividedBy( - payTokenFiatRate, - ); - - const payTokenAmount = new BigNumber(amountHuman || '0').multipliedBy( - assetToPayTokenRate, - ); - - const formattedAmount = formatAmount(I18n.locale, payTokenAmount); + if (!formattedAmount) return ; return ( diff --git a/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.test.ts b/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.test.ts index 1182a79a70b..320ddc6b500 100644 --- a/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.test.ts +++ b/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.test.ts @@ -159,6 +159,39 @@ describe('useInsufficientPayTokenBalanceAlert', () => { ]); }); + it('returns alert if pay token balance shortfall is equal to total amount', () => { + useTransactionPayTokenMock.mockReturnValue({ + payToken: { + ...PAY_TOKEN_MOCK, + balanceRaw: '999', + }, + setPayToken: jest.fn(), + }); + + useTransactionPayRequiredTokensMock.mockReturnValue([ + { + ...REQUIRED_TOKEN_MOCK, + amountUsd: '0.02', + }, + ]); + + const { result } = runHook(); + + expect(result.current).toStrictEqual([ + { + key: AlertKeys.InsufficientPayTokenFees, + field: RowAlertKey.Amount, + isBlocking: true, + title: strings('alert_system.insufficient_pay_token_balance.message'), + message: strings( + 'alert_system.insufficient_pay_token_balance_fees_no_target.message', + { amount: '$1.21' }, + ), + severity: Severity.Danger, + }, + ]); + }); + it('returns alert if pay token balance is less than source amount plus source network', () => { useTransactionPayTokenMock.mockReturnValue({ payToken: { diff --git a/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.ts b/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.ts index 3d5ec92493f..e700dc4ae46 100644 --- a/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.ts +++ b/app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert.ts @@ -83,7 +83,10 @@ export function useInsufficientPayTokenBalanceAlert({ const targetAmountUsd = useMemo(() => { const shortfall = totalSourceAmountUsd.minus(balanceUsd ?? '0'); - return formatFiat(totalAmountUsd.minus(shortfall)); + const targetUsdValue = totalAmountUsd.minus(shortfall); + const targetUsd = formatFiat(targetUsdValue); + + return targetUsdValue.isLessThanOrEqualTo(0) ? undefined : targetUsd; }, [balanceUsd, formatFiat, totalAmountUsd, totalSourceAmountUsd]); const totalSourceNetworkFeeRaw = useMemo( @@ -141,10 +144,14 @@ export function useInsufficientPayTokenBalanceAlert({ ...baseAlert, key: AlertKeys.InsufficientPayTokenFees, title: strings('alert_system.insufficient_pay_token_balance.message'), - message: strings( - 'alert_system.insufficient_pay_token_balance_fees.message', - { amount: targetAmountUsd }, - ), + message: targetAmountUsd + ? strings( + 'alert_system.insufficient_pay_token_balance_fees.message', + { amount: targetAmountUsd }, + ) + : strings( + 'alert_system.insufficient_pay_token_balance_fees_no_target.message', + ), }, ]; } diff --git a/app/components/Views/confirmations/hooks/gas/useGasFeeToken.ts b/app/components/Views/confirmations/hooks/gas/useGasFeeToken.ts index 65a5336325f..da10ad725f6 100644 --- a/app/components/Views/confirmations/hooks/gas/useGasFeeToken.ts +++ b/app/components/Views/confirmations/hooks/gas/useGasFeeToken.ts @@ -17,6 +17,7 @@ import { selectNetworkConfigurationByChainId } from '../../../../../selectors/ne import { RootState } from '../../../../../reducers'; import { useEthFiatAmount } from '../useEthFiatAmount'; import { useAccountNativeBalance } from '../useAccountNativeBalance'; +import { useMemo } from 'react'; export const RATE_WEI_NATIVE = '0xDE0B6B3A7640000'; // 1x10^18 @@ -44,9 +45,9 @@ export function useGasFeeToken({ tokenAddress }: { tokenAddress?: Hex }) { decimals: 0, }; - const amountFormatted = formatAmount( - locale, - new BigNumber(amount).shiftedBy(-decimals), + const amountFormatted = useMemo( + () => formatAmount(locale, new BigNumber(amount).shiftedBy(-decimals)), + [amount, decimals, locale], ); const amountFiat = useFiatTokenValue( @@ -61,20 +62,34 @@ export function useGasFeeToken({ tokenAddress }: { tokenAddress?: Hex }) { ); const metamaskFeeFiat = useFiatTokenValue(gasFeeToken, metaMaskFee, chainId); - const transferTransaction = - tokenAddress === NATIVE_TOKEN_ADDRESS - ? getNativeTransferTransaction(gasFeeToken) - : getTokenTransferTransaction(gasFeeToken); + const transferTransaction = useMemo( + () => + tokenAddress === NATIVE_TOKEN_ADDRESS + ? getNativeTransferTransaction(gasFeeToken) + : getTokenTransferTransaction(gasFeeToken), + [gasFeeToken, tokenAddress], + ); - return { - ...gasFeeToken, - amountFormatted, - amountFiat, - balanceFiat, - metaMaskFee, - metamaskFeeFiat, - transferTransaction, - }; + return useMemo( + () => ({ + ...gasFeeToken, + amountFormatted, + amountFiat, + balanceFiat, + metaMaskFee, + metamaskFeeFiat, + transferTransaction, + }), + [ + gasFeeToken, + amountFormatted, + amountFiat, + balanceFiat, + metaMaskFee, + metamaskFeeFiat, + transferTransaction, + ], + ); } export function useSelectedGasFeeToken() { @@ -109,19 +124,29 @@ function useNativeGasFeeToken(): GasFeeToken { const { nativeCurrency } = networkConfiguration ?? {}; const { gas, maxFeePerGas, maxPriorityFeePerGas } = txParams ?? {}; - return { - amount: (estimatedFeeNativeHex as Hex) ?? '0x0', - balance, - decimals: 18, - gas: gas as Hex, - gasTransfer: '0x0', - maxFeePerGas: maxFeePerGas as Hex, - maxPriorityFeePerGas: maxPriorityFeePerGas as Hex, - rateWei: RATE_WEI_NATIVE, - recipient: NATIVE_TOKEN_ADDRESS, - symbol: nativeCurrency, - tokenAddress: NATIVE_TOKEN_ADDRESS, - }; + return useMemo( + () => ({ + amount: (estimatedFeeNativeHex as Hex) ?? '0x0', + balance, + decimals: 18, + gas: gas as Hex, + gasTransfer: '0x0', + maxFeePerGas: maxFeePerGas as Hex, + maxPriorityFeePerGas: maxPriorityFeePerGas as Hex, + rateWei: RATE_WEI_NATIVE, + recipient: NATIVE_TOKEN_ADDRESS, + symbol: nativeCurrency, + tokenAddress: NATIVE_TOKEN_ADDRESS, + }), + [ + estimatedFeeNativeHex, + balance, + gas, + maxFeePerGas, + maxPriorityFeePerGas, + nativeCurrency, + ], + ); } function useFiatTokenValue( diff --git a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts index 15ad960a94d..753717f2bc1 100644 --- a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts +++ b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.test.ts @@ -1,33 +1,26 @@ import { merge } from 'lodash'; import { renderHookWithProvider } from '../../../../../util/test/renderWithProvider'; -import { useTokensWithBalance } from '../../../../UI/Bridge/hooks/useTokensWithBalance'; import { useAutomaticTransactionPayToken } from './useAutomaticTransactionPayToken'; import { useTransactionPayToken } from './useTransactionPayToken'; import { simpleSendTransactionControllerMock } from '../../__mocks__/controllers/transaction-controller-mock'; import { transactionApprovalControllerMock } from '../../__mocks__/controllers/approval-controller-mock'; -import { selectEnabledSourceChains } from '../../../../../core/redux/slices/bridge'; -import { NATIVE_TOKEN_ADDRESS } from '../../constants/tokens'; import { isHardwareAccount } from '../../../../../util/address'; import { TransactionType } from '@metamask/transaction-controller'; import { TransactionPayRequiredToken } from '@metamask/transaction-pay-controller'; import { Hex } from '@metamask/utils'; import { useTransactionPayRequiredTokens } from './useTransactionPayData'; +import { useTransactionPayAvailableTokens } from './useTransactionPayAvailableTokens'; +import { AssetType } from '../../types/token'; jest.mock('./useTransactionPayToken'); -jest.mock('../../../../UI/Bridge/hooks/useTokensWithBalance'); jest.mock('../../../../../util/address'); jest.mock('../../../../../selectors/transactionPayController'); jest.mock('./useTransactionPayData'); - -jest.mock('../../../../../core/redux/slices/bridge', () => ({ - ...jest.requireActual('../../../../../core/redux/slices/bridge'), - selectEnabledSourceChains: jest.fn(), -})); +jest.mock('./useTransactionPayAvailableTokens'); const TOKEN_ADDRESS_1_MOCK = '0x1234567890abcdef1234567890abcdef12345678'; const TOKEN_ADDRESS_2_MOCK = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'; const TOKEN_ADDRESS_3_MOCK = '0xabc1234567890abcdef1234567890abcdef12345678'; -const REQUIRED_BALANCE_MOCK = 10; const CHAIN_ID_1_MOCK = '0x1'; const CHAIN_ID_2_MOCK = '0x2'; @@ -61,8 +54,9 @@ function runHook({ disable = false } = {}) { describe('useAutomaticTransactionPayToken', () => { const useTransactionPayTokenMock = jest.mocked(useTransactionPayToken); - const useTokensWithBalanceMock = jest.mocked(useTokensWithBalance); - const selectEnabledSourceChainsMock = jest.mocked(selectEnabledSourceChains); + const useTransactionPayAvailableTokensMock = jest.mocked( + useTransactionPayAvailableTokens, + ); const isHardwareAccountMock = jest.mocked(isHardwareAccount); const useTransactionPayRequiredTokensMock = jest.mocked( useTransactionPayRequiredTokens, @@ -75,8 +69,6 @@ describe('useAutomaticTransactionPayToken', () => { beforeEach(() => { jest.resetAllMocks(); - selectEnabledSourceChainsMock.mockReturnValue([]); - useTransactionPayTokenMock.mockReturnValue({ payToken: undefined, setPayToken: setPayTokenMock, @@ -85,6 +77,7 @@ describe('useAutomaticTransactionPayToken', () => { useTransactionPayRequiredTokensMock.mockReturnValue([ { address: TOKEN_ADDRESS_1_MOCK as Hex, + chainId: CHAIN_ID_1_MOCK as Hex, } as TransactionPayRequiredToken, ]); @@ -92,23 +85,20 @@ describe('useAutomaticTransactionPayToken', () => { }); it('selects target token if has balance', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK, - }, + useTransactionPayAvailableTokensMock.mockReturnValue([ { address: TOKEN_ADDRESS_2_MOCK, chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 10, }, { address: TOKEN_ADDRESS_3_MOCK, chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 20, }, - ] as unknown as ReturnType); + { + address: TOKEN_ADDRESS_1_MOCK, + chainId: CHAIN_ID_1_MOCK, + }, + ] as AssetType[]); runHook(); @@ -119,33 +109,20 @@ describe('useAutomaticTransactionPayToken', () => { }); it('selects token with highest balance on same chain if insufficient balance on target token', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 0, - }, - { - address: TOKEN_ADDRESS_2_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 5, - }, + useTransactionPayAvailableTokensMock.mockReturnValue([ { address: TOKEN_ADDRESS_3_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 10, + chainId: CHAIN_ID_2_MOCK, }, { - address: TOKEN_ADDRESS_3_MOCK, + address: TOKEN_ADDRESS_2_MOCK, chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 20, }, { - address: NATIVE_TOKEN_ADDRESS, + address: TOKEN_ADDRESS_3_MOCK, chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 1, }, - ] as unknown as ReturnType); + ] as AssetType[]); runHook(); @@ -156,38 +133,16 @@ describe('useAutomaticTransactionPayToken', () => { }); it('selects token with highest balance on alternate chain if insufficient balance on same chain', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 0, - }, - { - address: TOKEN_ADDRESS_2_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 0, - }, - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 10, - }, + useTransactionPayAvailableTokensMock.mockReturnValue([ { address: TOKEN_ADDRESS_3_MOCK, chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 20, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 0, }, { - address: NATIVE_TOKEN_ADDRESS, + address: TOKEN_ADDRESS_2_MOCK, chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: 1, }, - ] as unknown as ReturnType); + ] as AssetType[]); runHook(); @@ -198,28 +153,7 @@ describe('useAutomaticTransactionPayToken', () => { }); it('selects target token if insufficient balance on all chains', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 1, - }, - { - address: TOKEN_ADDRESS_2_MOCK, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 1, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 1, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: 1, - }, - ] as unknown as ReturnType); + useTransactionPayAvailableTokensMock.mockReturnValue([]); runHook(); @@ -230,19 +164,7 @@ describe('useAutomaticTransactionPayToken', () => { }); it('does nothing if no required tokens', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK, - }, - { - address: TOKEN_ADDRESS_2_MOCK, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK, - }, - ] as unknown as ReturnType); - + useTransactionPayAvailableTokensMock.mockReturnValue([]); useTransactionPayRequiredTokensMock.mockReturnValue([]); runHook(); @@ -250,81 +172,21 @@ describe('useAutomaticTransactionPayToken', () => { expect(setPayTokenMock).not.toHaveBeenCalled(); }); - it('does not select token if no native balance on chain', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 1, - }, - { - address: TOKEN_ADDRESS_2_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 5, - }, - { - address: TOKEN_ADDRESS_3_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 10, - }, - { - address: TOKEN_ADDRESS_3_MOCK, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 20, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 0, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: 1, - }, - ] as unknown as ReturnType); - - runHook(); - - expect(setPayTokenMock).toHaveBeenCalledWith({ - address: TOKEN_ADDRESS_3_MOCK, - chainId: CHAIN_ID_2_MOCK, - }); - }); - - it('always selects target token if hardware wallet', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 1, - }, + it('selects target token if hardware wallet', () => { + useTransactionPayAvailableTokensMock.mockReturnValue([ { address: TOKEN_ADDRESS_2_MOCK, chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 2, }, { address: TOKEN_ADDRESS_1_MOCK, chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 10, }, { address: TOKEN_ADDRESS_3_MOCK, chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 20, }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 1, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: 1, - }, - ] as unknown as ReturnType); + ] as AssetType[]); isHardwareAccountMock.mockReturnValue(true); @@ -336,53 +198,13 @@ describe('useAutomaticTransactionPayToken', () => { }); }); - it('returns number of tokens with balance', () => { - useTokensWithBalanceMock.mockReturnValue([ - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 1, - }, - { - address: TOKEN_ADDRESS_2_MOCK, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK - 2, - }, - { - address: TOKEN_ADDRESS_1_MOCK, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 10, - }, - { - address: TOKEN_ADDRESS_3_MOCK, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK + 20, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: 1, - }, - { - address: NATIVE_TOKEN_ADDRESS, - chainId: CHAIN_ID_2_MOCK, - tokenFiatAmount: 1, - }, - ] as unknown as ReturnType); - - const { result } = runHook(); - - expect(result.current.count).toBe(6); - }); - it('selected nothing if disabled', () => { - useTokensWithBalanceMock.mockReturnValue([ + useTransactionPayAvailableTokensMock.mockReturnValue([ { address: TOKEN_ADDRESS_1_MOCK, chainId: CHAIN_ID_1_MOCK, - tokenFiatAmount: REQUIRED_BALANCE_MOCK, }, - ] as unknown as ReturnType); + ] as AssetType[]); runHook({ disable: true }); diff --git a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts index c8fed86f528..b53456f0eba 100644 --- a/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts +++ b/app/components/Views/confirmations/hooks/pay/useAutomaticTransactionPayToken.ts @@ -1,149 +1,136 @@ -import { useSelector } from 'react-redux'; -import { useTokensWithBalance } from '../../../../UI/Bridge/hooks/useTokensWithBalance'; -import { selectEnabledSourceChains } from '../../../../../core/redux/slices/bridge'; import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest'; -import { orderBy } from 'lodash'; import { useEffect, useMemo, useRef } from 'react'; import { Hex } from 'viem'; import { createProjectLogger } from '@metamask/utils'; import { useTransactionPayToken } from './useTransactionPayToken'; -import { BridgeToken } from '../../../../UI/Bridge/types'; import { isHardwareAccount } from '../../../../../util/address'; import { TransactionMeta } from '@metamask/transaction-controller'; -import { getRequiredBalance } from '../../utils/transaction-pay'; -import { getNativeTokenAddress } from '../../utils/asset'; import { useTransactionPayRequiredTokens } from './useTransactionPayData'; +import { useTransactionPayAvailableTokens } from './useTransactionPayAvailableTokens'; +import { AssetType } from '../../types/token'; const log = createProjectLogger('transaction-pay'); -export interface BalanceOverride { - address: Hex; - balance: number; - chainId: Hex; -} - export function useAutomaticTransactionPayToken({ - countOnly = false, disable = false, }: { - countOnly?: boolean; disable?: boolean; } = {}) { const isUpdated = useRef(false); - const supportedChains = useSelector(selectEnabledSourceChains); const { setPayToken } = useTransactionPayToken(); const requiredTokens = useTransactionPayRequiredTokens(); + const tokens = useTransactionPayAvailableTokens(); + + const tokensWithBalance = useMemo( + () => tokens.filter((t) => !t.disabled), + [tokens], + ); const transactionMeta = useTransactionMetadataRequest() ?? ({ txParams: {} } as TransactionMeta); const { - chainId, txParams: { from }, } = transactionMeta; - const chainIds = useMemo( - () => (!isUpdated.current ? supportedChains.map((c) => c.chainId) : []), - [supportedChains], + const isHardwareWallet = useMemo( + () => isHardwareAccount(from ?? '') ?? false, + [from], ); - const tokens = useTokensWithBalance({ chainIds }); - const isHardwareWallet = isHardwareAccount(from ?? ''); - const requiredBalance = getRequiredBalance(transactionMeta); - - let automaticToken: { address: string; chainId?: string } | undefined; - let count = 0; - - const nativeTokenAddress = getNativeTokenAddress(chainId as Hex); - - if (!disable && (!isUpdated.current || countOnly)) { - const targetToken = - requiredTokens.find((token) => token.address !== nativeTokenAddress) ?? - requiredTokens[0]; - - const sufficientBalanceTokens = orderBy( - tokens.filter((token) => - isTokenSupported(token, tokens, requiredBalance), - ), - (token) => token?.tokenFiatAmount ?? 0, - 'desc', - ); - - count = sufficientBalanceTokens.length; - - const requiredToken = sufficientBalanceTokens.find( - (token) => - token.address === targetToken?.address && token.chainId === chainId, - ); - - const sameChainHighestBalanceToken = sufficientBalanceTokens?.find( - (token) => token.chainId === chainId, - ); - - const alternateChainHighestBalanceToken = sufficientBalanceTokens?.find( - (token) => token.chainId !== chainId, - ); - - const targetTokenFallback = targetToken - ? { - address: targetToken.address, - chainId, - } - : undefined; - - automaticToken = - requiredToken ?? - sameChainHighestBalanceToken ?? - alternateChainHighestBalanceToken ?? - targetTokenFallback; - - if (isHardwareWallet) { - automaticToken = targetTokenFallback; - } - } + const targetToken = useMemo( + () => requiredTokens.find((token) => !token.allowUnderMinimum), + [requiredTokens], + ); useEffect(() => { - if ( - isUpdated.current || - !automaticToken || - !requiredTokens?.length || - countOnly - ) { + if (disable || isUpdated.current) { + return; + } + + const automaticToken = getBestToken({ + isHardwareWallet, + targetToken, + tokens: tokensWithBalance, + }); + + if (!automaticToken) { + log('No automatic pay token found'); return; } setPayToken({ - address: automaticToken.address as Hex, - chainId: automaticToken.chainId as Hex, + address: automaticToken.address, + chainId: automaticToken.chainId, }); isUpdated.current = true; log('Automatically selected pay token', automaticToken); - }, [automaticToken, countOnly, isUpdated, requiredTokens, setPayToken]); - - return { count }; + }, [ + disable, + isHardwareWallet, + requiredTokens, + setPayToken, + targetToken, + tokensWithBalance, + ]); } -function isTokenSupported( - token: BridgeToken, - tokens: BridgeToken[], - requiredBalance: number | undefined, -): boolean { - const nativeTokenAddress = getNativeTokenAddress(token.chainId as Hex); +function getBestToken({ + isHardwareWallet, + targetToken, + tokens, +}: { + isHardwareWallet: boolean; + targetToken?: { address: Hex; chainId: Hex }; + tokens: AssetType[]; +}): { address: Hex; chainId: Hex } | undefined { + const targetTokenFallback = targetToken + ? { + address: targetToken.address, + chainId: targetToken.chainId, + } + : undefined; + + if (isHardwareWallet) { + return targetTokenFallback; + } - const nativeToken = tokens.find( - (t) => t.address === nativeTokenAddress && t.chainId === token.chainId, + const requiredToken = tokens.find( + (t) => + t.address.toLowerCase() === targetToken?.address.toLowerCase() && + t.chainId === targetToken?.chainId, ); - const tokenAmount = token?.tokenFiatAmount ?? 0; + if (requiredToken) { + return { + address: requiredToken.address as Hex, + chainId: requiredToken.chainId as Hex, + }; + } - const isTokenBalanceSufficient = - requiredBalance === undefined - ? tokenAmount > 0 - : tokenAmount >= requiredBalance; + const sameChainHighestBalanceToken = tokens.find( + (t) => t.chainId === targetToken?.chainId, + ); - const hasNativeBalance = (nativeToken?.tokenFiatAmount ?? 0) > 0; + if (sameChainHighestBalanceToken) { + return { + address: sameChainHighestBalanceToken.address as Hex, + chainId: sameChainHighestBalanceToken.chainId as Hex, + }; + } + + const alternateChainHighestBalanceToken = tokens.find( + (t) => t.chainId !== targetToken?.chainId, + ); + + if (alternateChainHighestBalanceToken) { + return { + address: alternateChainHighestBalanceToken.address as Hex, + chainId: alternateChainHighestBalanceToken.chainId as Hex, + }; + } - return isTokenBalanceSufficient && hasNativeBalance; + return targetTokenFallback; } diff --git a/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.test.ts b/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.test.ts index 191da8fe91a..dfe782e10c0 100644 --- a/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.test.ts +++ b/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.test.ts @@ -14,7 +14,6 @@ import { useTransactionPayToken } from './useTransactionPayToken'; import { act } from '@testing-library/react-native'; import { updateConfirmationMetric } from '../../../../../core/redux/slices/confirmationMetrics'; import { TransactionType } from '@metamask/transaction-controller'; -import { useAutomaticTransactionPayToken } from './useAutomaticTransactionPayToken'; import { TransactionPayQuote, TransactionPayRequiredToken, @@ -26,12 +25,14 @@ import { useTransactionPayRequiredTokens, useTransactionPayTotals, } from './useTransactionPayData'; +import { useTransactionPayAvailableTokens } from './useTransactionPayAvailableTokens'; +import { AssetType } from '../../types/token'; jest.mock('./useTransactionPayToken'); -jest.mock('./useAutomaticTransactionPayToken'); jest.mock('../useTokenAmount'); jest.mock('../../../../../selectors/transactionPayController'); jest.mock('../pay/useTransactionPayData'); +jest.mock('./useTransactionPayAvailableTokens'); jest.mock('../../../../../core/redux/slices/confirmationMetrics', () => ({ ...jest.requireActual('../../../../../core/redux/slices/confirmationMetrics'), @@ -76,12 +77,13 @@ describe('useTransactionPayMetrics', () => { const updateConfirmationMetricMock = jest.mocked(updateConfirmationMetric); const useTransactionPayQuotesMock = jest.mocked(useTransactionPayQuotes); const useTransactionPayTotalsMock = jest.mocked(useTransactionPayTotals); + const useTransactionPayRequiredTokensMock = jest.mocked( useTransactionPayRequiredTokens, ); - const useAutomaticTransactionPayTokenMock = jest.mocked( - useAutomaticTransactionPayToken, + const useTransactionPayAvailableTokensMock = jest.mocked( + useTransactionPayAvailableTokens, ); beforeEach(() => { @@ -104,9 +106,13 @@ describe('useTransactionPayMetrics', () => { useTransactionPayQuotesMock.mockReturnValue([]); - useAutomaticTransactionPayTokenMock.mockReturnValue({ - count: 5, - }); + useTransactionPayAvailableTokensMock.mockReturnValue([ + {}, + {}, + {}, + {}, + {}, + ] as AssetType[]); }); it('does not update metrics if no pay token selected', async () => { diff --git a/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.ts b/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.ts index 3f7ecf2e34d..e1e2ef0c33f 100644 --- a/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.ts +++ b/app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { updateConfirmationMetric } from '../../../../../core/redux/slices/confirmationMetrics'; import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest'; @@ -7,7 +7,6 @@ import { Hex, Json } from '@metamask/utils'; import { TransactionType } from '@metamask/transaction-controller'; import { useTransactionPayToken } from './useTransactionPayToken'; import { BridgeToken } from '../../../../UI/Bridge/types'; -import { useAutomaticTransactionPayToken } from './useAutomaticTransactionPayToken'; import { getNativeTokenAddress } from '../../utils/asset'; import { hasTransactionType } from '../../utils/transaction'; import { @@ -17,6 +16,7 @@ import { } from './useTransactionPayData'; import { TransactionPayStrategy } from '@metamask/transaction-pay-controller'; import { BigNumber } from 'bignumber.js'; +import { useTransactionPayAvailableTokens } from './useTransactionPayAvailableTokens'; export function useTransactionPayMetrics() { const dispatch = useDispatch(); @@ -26,10 +26,12 @@ export function useTransactionPayMetrics() { const automaticPayToken = useRef(); const quotes = useTransactionPayQuotes(); const totals = useTransactionPayTotals(); + const tokens = useTransactionPayAvailableTokens(); - const { count: availableTokenCount } = useAutomaticTransactionPayToken({ - countOnly: true, - }); + const availableTokens = useMemo( + () => tokens.filter((t) => !t.disabled), + [tokens], + ); const transactionId = transactionMeta?.id ?? ''; const { chainId, type } = transactionMeta ?? {}; @@ -58,7 +60,7 @@ export function useTransactionPayMetrics() { properties.mm_pay_chain_presented = automaticPayToken.current?.chainId ?? null; - properties.mm_pay_payment_token_list_size = availableTokenCount; + properties.mm_pay_payment_token_list_size = availableTokens.length; } if (payToken && type === TransactionType.perpsDeposit) { diff --git a/app/components/Views/confirmations/hooks/pay/useTransactionPayToken.ts b/app/components/Views/confirmations/hooks/pay/useTransactionPayToken.ts index 605da0ba0ba..a5bb24ec246 100644 --- a/app/components/Views/confirmations/hooks/pay/useTransactionPayToken.ts +++ b/app/components/Views/confirmations/hooks/pay/useTransactionPayToken.ts @@ -5,6 +5,8 @@ import { RootState } from '../../../../../reducers'; import Engine from '../../../../../core/Engine'; import { selectTransactionPaymentTokenByTransactionId } from '../../../../../selectors/transactionPayController'; import { Hex } from '@metamask/utils'; +import { noop } from 'lodash'; +import EngineService from '../../../../../core/EngineService'; export function useTransactionPayToken() { const { id: transactionId } = useTransactionMetadataRequest() || { id: '' }; @@ -14,7 +16,7 @@ export function useTransactionPayToken() { ); const setPayToken = useCallback( - async (newPayToken: { address: Hex; chainId: Hex }) => { + (newPayToken: { address: Hex; chainId: Hex }) => { const { GasFeeController, NetworkController, TransactionPayController } = Engine.context; @@ -22,9 +24,9 @@ export function useTransactionPayToken() { newPayToken.chainId, ); - await GasFeeController.fetchGasFeeEstimates({ + GasFeeController.fetchGasFeeEstimates({ networkClientId, - }); + }).catch(noop); try { TransactionPayController.updatePaymentToken({ @@ -32,6 +34,8 @@ export function useTransactionPayToken() { tokenAddress: newPayToken.address, chainId: newPayToken.chainId, }); + + EngineService.flushState(); } catch (e) { console.error('Error updating payment token', e); } diff --git a/app/components/Views/confirmations/hooks/transactions/useTransferRecipient.ts b/app/components/Views/confirmations/hooks/transactions/useTransferRecipient.ts index 03b428ab63b..bf95906dce8 100644 --- a/app/components/Views/confirmations/hooks/transactions/useTransferRecipient.ts +++ b/app/components/Views/confirmations/hooks/transactions/useTransferRecipient.ts @@ -51,10 +51,15 @@ function getRecipientByType( data: string, transactionTo: string, ): string | undefined { - const dataRecipient = getTransactionDataRecipient(data); - const paramsRecipient = transactionTo; - - return type === TransactionType.simpleSend ? paramsRecipient : dataRecipient; + switch (type) { + case TransactionType.simpleSend: + return transactionTo; + case TransactionType.tokenMethodTransfer: + case TransactionType.tokenMethodTransferFrom: + return getTransactionDataRecipient(data); + default: + return undefined; + } } function getTransactionDataRecipient(data: string): string | undefined { diff --git a/app/components/Views/confirmations/hooks/useTokenAmount.ts b/app/components/Views/confirmations/hooks/useTokenAmount.ts index 1787ad5eda1..7f8de624582 100644 --- a/app/components/Views/confirmations/hooks/useTokenAmount.ts +++ b/app/components/Views/confirmations/hooks/useTokenAmount.ts @@ -32,7 +32,7 @@ import { ERC20_DEFAULT_DECIMALS, fetchErc20Decimals } from '../utils/token'; import { parseStandardTokenTransactionData } from '../utils/transaction'; import { useTransactionMetadataOrThrow } from './transactions/useTransactionMetadataRequest'; import useNetworkInfo from './useNetworkInfo'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { updateEditableParams } from '../../../../util/transaction-controller'; import { selectTokensByChainIdAndAddress } from '../../../../selectors/tokensController'; import { getTokenTransferData } from '../utils/transaction-pay'; @@ -130,7 +130,11 @@ export const useTokenAmount = ({ networkClientId, ); - const transactionData = parseStandardTokenTransactionData(tokenData?.data); + const transactionData = useMemo( + () => parseStandardTokenTransactionData(tokenData?.data), + [tokenData?.data], + ); + const recipient = transactionData?.args?._to; const updateTokenAmount = useCallback( diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts index a602f55abd9..feca935df65 100644 --- a/app/core/EngineService/EngineService.ts +++ b/app/core/EngineService/EngineService.ts @@ -83,6 +83,10 @@ export class EngineService { Logger.log('keyringController vault missing for UPDATE_BG_STATE_KEY'); } this.updateBatcher.add(controllerName); + + if (controllerName === 'ApprovalController') { + this.updateBatcher.flush(); + } }; BACKGROUND_STATE_CHANGE_EVENT_NAMES.forEach((eventName) => { @@ -177,6 +181,14 @@ export class EngineService { endTrace({ name: TraceName.EngineInitialization }); }; + /** + * Flush any pending controller state updates. + * Only necessary in rare cases where immediate state consistency is required. + */ + flushState() { + this.updateBatcher.flush(); + } + /** * Sets up persistence subscriptions for all engine controllers. * diff --git a/locales/languages/en.json b/locales/languages/en.json index b7dcfafe4bf..886138d8fc9 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -86,6 +86,9 @@ "message": "Add less than {{amount}} or use a different token.", "title": "Insufficient funds" }, + "insufficient_pay_token_balance_fees_no_target": { + "message": "Add less or use a different token." + }, "insufficient_pay_token_native": { "message": "Not enough {{ticker}} to cover fees. Use a token on another network or add more {{ticker}} to continue.", "title": "Insufficient funds" @@ -628,7 +631,7 @@ "unexpectedError": "An unexpected error occurred.", "quoteFetchError": "Failed to fetch quote.", "kycFormsFetchError": "Failed to fetch KYC forms.", - "title": "Deposit", + "title": "Buy", "limitExceeded": "This deposit would exceed your {{period}} limit. Your deposit including fees must be {{remaining}} or less.", "limitError": "Failed to check your deposit limits. Please try again later." }, @@ -3025,14 +3028,14 @@ "buy_description": "Good for buying a specific token", "buy_unified": "Buy", "buy_unified_description": "Buy crypto with cash", - "sell": "Withdraw", + "sell": "Sell", "sell_description": "Sell crypto for cash" }, "asset_overview": { "send_button": "Send", "buy_button": "Buy", "token_marketplace": "Token marketplace", - "sell_button": "Withdraw", + "sell_button": "Sell", "receive_button": "Receive", "portfolio_button": "Portfolio", "deposit_button": "Deposit", @@ -4539,8 +4542,8 @@ "quotes_timeout": "Quotes timeout", "request_new_quotes": "Please request new quotes to get the latest best rate.", "terms_of_service": "Terms of Service", - "amount_to_buy": "Amount to buy", - "amount_to_sell": "Amount to sell", + "amount_to_buy": "Buy", + "amount_to_sell": "Sell", "want_to_buy": "You want to buy", "want_to_sell": "You want to sell", "current_balance": "Current balance", diff --git a/logo.png b/logo.png index a28627a26b0..5622df2bb4c 100644 Binary files a/logo.png and b/logo.png differ diff --git a/package.json b/package.json index 0627f3f7ac6..920d4cc0d1f 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@scure/bip32": "1.7.0", "@metamask/snaps-sdk": "^10.0.0", "react-native@0.76.9": "patch:react-native@npm%3A0.76.9#./.yarn/patches/react-native-npm-0.76.9-1c25352097.patch", - "@metamask/transaction-controller@npm:^62.0.0": "patch:@metamask/transaction-controller@npm%3A62.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch" + "@metamask/transaction-controller@npm:^62.1.0": "patch:@metamask/transaction-controller@npm%3A62.1.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch" }, "dependencies": { "@config-plugins/detox": "^9.0.0", @@ -197,7 +197,7 @@ "@metamask/address-book-controller": "^7.0.0", "@metamask/app-metadata-controller": "^2.0.0", "@metamask/approval-controller": "^8.0.0", - "@metamask/assets-controllers": "^89.0.1", + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A89.0.1#~/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch", "@metamask/base-controller": "^9.0.0", "@metamask/bitcoin-wallet-snap": "^1.6.0", "@metamask/bridge-controller": "^61.0.0", @@ -286,9 +286,9 @@ "@metamask/swappable-obj-proxy": "^2.1.0", "@metamask/swaps-controller": "^15.0.0", "@metamask/token-search-discovery-controller": "^4.0.0", - "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch", + "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.1.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch", "@metamask/transaction-pay-controller": "^10.0.0", - "@metamask/tron-wallet-snap": "^1.9.1", + "@metamask/tron-wallet-snap": "^1.10.0", "@metamask/utils": "^11.8.1", "@ngraveio/bc-ur": "^1.1.6", "@nktkas/hyperliquid": "^0.25.9", diff --git a/yarn.lock b/yarn.lock index 9eab6b6d2ba..1c2a7db6bd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7326,7 +7326,7 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@npm:^89.0.1": +"@metamask/assets-controllers@npm:89.0.1": version: 89.0.1 resolution: "@metamask/assets-controllers@npm:89.0.1" dependencies: @@ -7378,6 +7378,58 @@ __metadata: languageName: node linkType: hard +"@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A89.0.1#~/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch": + version: 89.0.1 + resolution: "@metamask/assets-controllers@patch:@metamask/assets-controllers@npm%3A89.0.1#~/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch::version=89.0.1&hash=6be0d3" + dependencies: + "@ethereumjs/util": "npm:^9.1.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/providers": "npm:^5.7.0" + "@metamask/abi-utils": "npm:^2.0.3" + "@metamask/base-controller": "npm:^9.0.0" + "@metamask/contract-metadata": "npm:^2.4.0" + "@metamask/controller-utils": "npm:^11.15.0" + "@metamask/eth-query": "npm:^4.0.0" + "@metamask/keyring-api": "npm:^21.0.0" + "@metamask/messenger": "npm:^0.3.0" + "@metamask/metamask-eth-abis": "npm:^3.1.1" + "@metamask/polling-controller": "npm:^15.0.0" + "@metamask/rpc-errors": "npm:^7.0.2" + "@metamask/snaps-sdk": "npm:^9.0.0" + "@metamask/snaps-utils": "npm:^11.0.0" + "@metamask/utils": "npm:^11.8.1" + "@types/bn.js": "npm:^5.1.5" + "@types/uuid": "npm:^8.3.0" + async-mutex: "npm:^0.5.0" + bitcoin-address-validation: "npm:^2.2.3" + bn.js: "npm:^5.2.1" + immer: "npm:^9.0.6" + lodash: "npm:^4.17.21" + multiformats: "npm:^9.9.0" + reselect: "npm:^5.1.1" + single-call-balance-checker-abi: "npm:^1.0.0" + uuid: "npm:^8.3.2" + peerDependencies: + "@metamask/account-tree-controller": ^3.0.0 + "@metamask/accounts-controller": ^34.0.0 + "@metamask/approval-controller": ^8.0.0 + "@metamask/core-backend": ^4.1.0 + "@metamask/keyring-controller": ^24.0.0 + "@metamask/network-controller": ^25.0.0 + "@metamask/permission-controller": ^12.0.0 + "@metamask/phishing-controller": ^15.0.0 + "@metamask/preferences-controller": ^21.0.0 + "@metamask/providers": ^22.0.0 + "@metamask/snaps-controllers": ^14.0.0 + "@metamask/transaction-controller": ^61.0.0 + webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0 + checksum: 10/b936b09bc22944626b3332844070c0fab559b7e3973873cc96c8321618e6879c1ee1215a588bb1f8f38029e4a69f796d01141ad1cb0726fd590df54ca111355b + languageName: node + linkType: hard + "@metamask/auth-network-utils@npm:^0.3.0": version: 0.3.1 resolution: "@metamask/auth-network-utils@npm:0.3.1" @@ -9227,9 +9279,9 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@npm:62.0.0": - version: 62.0.0 - resolution: "@metamask/transaction-controller@npm:62.0.0" +"@metamask/transaction-controller@npm:62.1.0": + version: 62.1.0 + resolution: "@metamask/transaction-controller@npm:62.1.0" dependencies: "@ethereumjs/common": "npm:^4.4.0" "@ethereumjs/tx": "npm:^5.4.0" @@ -9261,7 +9313,7 @@ __metadata: "@metamask/gas-fee-controller": ^26.0.0 "@metamask/network-controller": ^26.0.0 "@metamask/remote-feature-flag-controller": ^2.0.0 - checksum: 10/885217c920c29e953aec06c5d9e21ecd58847d5593e9e1f60a3e8e52f7de7869a087797cdf60c5022f12f0c87822b19c860c4ec7a9df1b2a0f140c7dfdaa25e3 + checksum: 10/2de5d4f36e2b5ddf5cee14a17484d0694fc7dd81bb568a51c712fde9eb0ab8395cae49efa66d5a9daefa86a2429e09561445e6dddc0281e9e645364a9aeeffed languageName: node linkType: hard @@ -9303,9 +9355,9 @@ __metadata: languageName: node linkType: hard -"@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch": - version: 62.0.0 - resolution: "@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch::version=62.0.0&hash=1a3342" +"@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.1.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch": + version: 62.1.0 + resolution: "@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.1.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch::version=62.1.0&hash=1a3342" dependencies: "@ethereumjs/common": "npm:^4.4.0" "@ethereumjs/tx": "npm:^5.4.0" @@ -9337,7 +9389,7 @@ __metadata: "@metamask/gas-fee-controller": ^26.0.0 "@metamask/network-controller": ^26.0.0 "@metamask/remote-feature-flag-controller": ^2.0.0 - checksum: 10/9caf3dfa6d88dded658f7902e42c8c20b6916c21804a8c7f593cf37b88764732738e6379443a1faefed81ea0d58f4fbac269c85fc240fa98a61f7551ec7465c9 + checksum: 10/f21f02550da1230a7e0f51ebd5a828d7cbbdfbb5d5d036ecb3e5f7d903b8dff15fb5627b675fd477f158a52722ab1daa23a103bef47be08f4a96391a7fa4dcda languageName: node linkType: hard @@ -9368,10 +9420,10 @@ __metadata: languageName: node linkType: hard -"@metamask/tron-wallet-snap@npm:^1.9.1": - version: 1.9.1 - resolution: "@metamask/tron-wallet-snap@npm:1.9.1" - checksum: 10/9880fce865211ed2d58b162f1ba0e41a7aa43c0180319d008b0a8a247a678284009592aeef50c25c8d4eb6393fdc9663d48a2261d7d03658e0850366052d6719 +"@metamask/tron-wallet-snap@npm:^1.10.0": + version: 1.10.0 + resolution: "@metamask/tron-wallet-snap@npm:1.10.0" + checksum: 10/c9b2f9cae0a2f9dcfd43934bd4321ed029e395122b61f1f3a8c3780dd4b44ac8669139b382da8b9230123639bf7a7554206223e3127d3e3317dfb802190cda46 languageName: node linkType: hard @@ -35291,7 +35343,7 @@ __metadata: "@metamask/address-book-controller": "npm:^7.0.0" "@metamask/app-metadata-controller": "npm:^2.0.0" "@metamask/approval-controller": "npm:^8.0.0" - "@metamask/assets-controllers": "npm:^89.0.1" + "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A89.0.1#~/.yarn/patches/@metamask-assets-controllers-npm-89.0.1-02fa7acd54.patch" "@metamask/auto-changelog": "npm:^5.1.0" "@metamask/base-controller": "npm:^9.0.0" "@metamask/bitcoin-wallet-snap": "npm:^1.6.0" @@ -35392,9 +35444,9 @@ __metadata: "@metamask/test-dapp-multichain": "npm:^0.17.1" "@metamask/test-dapp-solana": "npm:^0.3.0" "@metamask/token-search-discovery-controller": "npm:^4.0.0" - "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.0.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch" + "@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.1.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch" "@metamask/transaction-pay-controller": "npm:^10.0.0" - "@metamask/tron-wallet-snap": "npm:^1.9.1" + "@metamask/tron-wallet-snap": "npm:^1.10.0" "@metamask/utils": "npm:^11.8.1" "@ngraveio/bc-ur": "npm:^1.1.6" "@nktkas/hyperliquid": "npm:^0.25.9"