diff --git a/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.test.ts b/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.test.ts index 9d91fb890e7e..268d9fa528c8 100644 --- a/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.test.ts +++ b/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.test.ts @@ -1434,6 +1434,37 @@ describe('useTransactionCustomAmount', () => { expect(result.current.amountFiat).toBe('750.50'); }); + it('sets isMaxAmount for Max when payment override is MoneyAccount (e.g. Send to Perps)', async () => { + // With TPC MoneyAccount max support (MetaMask/core#9707), isMaxAmount=true + // uses the typed required amount (full withdrawable) rather than the pay + // token's bare on-chain mUSD balance, enabling EXACT_INPUT Max quotes. + useMoneyAccountBalanceMock.mockReturnValue({ + withdrawableFiatRaw: '4.70', + } as ReturnType); + + const { result } = runHook({ + transactionMeta: { + type: TransactionType.perpsDeposit, + id: transactionIdMock, + chainId: '0x1' as Hex, + txParams: { from: '0xabc' }, + } as unknown as Partial, + stateOverrides: moneyAccountStateOverrides, + }); + + await act(async () => { + result.current.updatePendingAmountPercentage(100); + }); + + // Full withdrawable balance is shown, not the reduced bare-mUSD figure. + expect(result.current.amountFiat).toBe('4.70'); + expect(setTransactionConfigMock).toHaveBeenCalledTimes(1); + + const config = { isMaxAmount: false }; + setTransactionConfigMock.mock.calls[0][1](config); + expect(config.isMaxAmount).toBe(true); + }); + it('returns 0 when payment override is MoneyAccount but withdrawableFiatRaw is undefined', async () => { useMoneyAccountBalanceMock.mockReturnValue({ withdrawableFiatRaw: undefined, diff --git a/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.ts b/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.ts index 0871e8f058cc..02b84836eee3 100644 --- a/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.ts +++ b/app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.ts @@ -292,12 +292,16 @@ export function useTransactionCustomAmount({ }, }); - // Do NOT set isMaxAmount=true for perps or money-account withdraw. TPC's - // calculatePostQuoteSourceAmounts substitutes `token.balanceRaw` when - // isMaxAmount is true: wrong for HyperLiquid (wallet USDC vs typed HL - // balance) and wrong for money account (on-chain mUSD only vs mUSD + - // vmUSD fiat total). Keeping isMaxAmount false routes the typed - // amount through as token.amountRaw. + // Do NOT set isMaxAmount=true for perps/money-account withdraw or money + // account deposit flows. For those, TPC would substitute an on-chain + // balanceRaw that does not match the typed balance (HyperLiquid wallet + // USDC vs HL balance; money-account on-chain mUSD vs mUSD + vmUSD). + // + // Deposits funded from the money account (e.g. Send to Perps) DO set + // isMaxAmount=true so quotes use EXACT_INPUT. That is safe once TPC uses + // the typed required amount for MoneyAccount max instead of the pay + // token's bare mUSD balance (MetaMask/core#9707). Mobile still types the + // full withdrawableFiatRaw amount into the required token. const shouldSetMax = percentage === 100 && !isPerpsWithdraw &&