Skip to content

Commit 2c7d28b

Browse files
committed
fix: money account withdraw should not be enabled in case there is no quote
1 parent a41d3ac commit 2c7d28b

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

app/components/Views/confirmations/hooks/alerts/useNoPayTokenQuotesAlert.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { strings } from '../../../../../../locales/i18n';
1313
import {
1414
useIsTransactionPayLoading,
1515
useTransactionPayFiatPayment,
16+
useTransactionPayIsMaxAmount,
1617
useTransactionPayIsPostQuote,
1718
useTransactionPayQuotes,
1819
useTransactionPayRequiredTokens,
@@ -78,6 +79,7 @@ describe('useNoPayTokenQuotesAlert', () => {
7879
useTransactionPayIsPostQuoteMock.mockReturnValue(false);
7980
useTransactionPayRequiredTokensMock.mockReturnValue([]);
8081
jest.mocked(useTransactionPayFiatPayment).mockReturnValue(undefined);
82+
jest.mocked(useTransactionPayIsMaxAmount).mockReturnValue(false);
8183
});
8284

8385
it('returns alert if pay token selected and no quotes available', () => {
@@ -209,4 +211,78 @@ describe('useNoPayTokenQuotesAlert', () => {
209211
}),
210212
]);
211213
});
214+
215+
// Money account withdraw MUSD -> MUSD: `calculatePostQuoteSourceAmounts`
216+
// filters out same-token/same-chain entries, so `sourceAmounts` is empty
217+
// even when the user has entered a positive amount. The alert must still
218+
// fire so the Withdraw button stays disabled.
219+
it('returns alert for post-quote when sourceAmounts is empty but a required token has a positive amount', () => {
220+
useTransactionPayIsPostQuoteMock.mockReturnValue(true);
221+
useTransactionPaySourceAmountsMock.mockReturnValue([]);
222+
useTransactionPayQuotesMock.mockReturnValue([]);
223+
224+
useTransactionPayRequiredTokensMock.mockReturnValue([
225+
{
226+
address: ADDRESS_MOCK,
227+
chainId: CHAIN_ID_MOCK,
228+
amountRaw: '10000',
229+
skipIfBalance: false,
230+
} as TransactionPayRequiredToken,
231+
]);
232+
233+
const { result } = runHook();
234+
235+
expect(result.current).toEqual([
236+
expect.objectContaining({
237+
key: AlertKeys.NoPayTokenQuotes,
238+
severity: Severity.Danger,
239+
isBlocking: true,
240+
}),
241+
]);
242+
});
243+
244+
it('returns no alerts for post-quote with empty sourceAmounts when the required token amount is zero', () => {
245+
useTransactionPayIsPostQuoteMock.mockReturnValue(true);
246+
useTransactionPaySourceAmountsMock.mockReturnValue([]);
247+
useTransactionPayQuotesMock.mockReturnValue([]);
248+
249+
useTransactionPayRequiredTokensMock.mockReturnValue([
250+
{
251+
address: ADDRESS_MOCK,
252+
chainId: CHAIN_ID_MOCK,
253+
amountRaw: '0',
254+
skipIfBalance: false,
255+
} as TransactionPayRequiredToken,
256+
]);
257+
258+
const { result } = runHook();
259+
260+
expect(result.current).toStrictEqual([]);
261+
});
262+
263+
it('returns alert for post-quote with empty sourceAmounts when isMaxAmount is true', () => {
264+
useTransactionPayIsPostQuoteMock.mockReturnValue(true);
265+
useTransactionPaySourceAmountsMock.mockReturnValue([]);
266+
useTransactionPayQuotesMock.mockReturnValue([]);
267+
jest.mocked(useTransactionPayIsMaxAmount).mockReturnValue(true);
268+
269+
useTransactionPayRequiredTokensMock.mockReturnValue([
270+
{
271+
address: ADDRESS_MOCK,
272+
chainId: CHAIN_ID_MOCK,
273+
amountRaw: '0',
274+
skipIfBalance: false,
275+
} as TransactionPayRequiredToken,
276+
]);
277+
278+
const { result } = runHook();
279+
280+
expect(result.current).toEqual([
281+
expect.objectContaining({
282+
key: AlertKeys.NoPayTokenQuotes,
283+
severity: Severity.Danger,
284+
isBlocking: true,
285+
}),
286+
]);
287+
});
212288
});

app/components/Views/confirmations/hooks/alerts/useNoPayTokenQuotesAlert.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { strings } from '../../../../../../locales/i18n';
77
import {
88
useIsTransactionPayLoading,
99
useTransactionPayFiatPayment,
10+
useTransactionPayIsMaxAmount,
1011
useTransactionPayIsPostQuote,
1112
useTransactionPayQuotes,
1213
useTransactionPayRequiredTokens,
@@ -21,6 +22,7 @@ export function useNoPayTokenQuotesAlert() {
2122
const sourceAmounts = useTransactionPaySourceAmounts();
2223
const requiredTokens = useTransactionPayRequiredTokens();
2324
const isPostQuote = useTransactionPayIsPostQuote();
25+
const isMaxAmount = useTransactionPayIsMaxAmount();
2426

2527
const fiatAmount = Number(fiatPayment?.amountFiat);
2628
const hasValidFiatAmount = Number.isFinite(fiatAmount) && fiatAmount > 0;
@@ -57,8 +59,29 @@ export function useNoPayTokenQuotesAlert() {
5759
sourceAmounts?.length === 0 &&
5860
quotes?.length === 0;
5961

62+
// Post-quote flows (e.g. money account withdraw MUSD -> MUSD) can end up with
63+
// an empty `sourceAmounts` when the source and destination tokens match and
64+
// get filtered out in `calculatePostQuoteSourceAmounts`. In that case the
65+
// non-fiat branch above never fires, so we also emit the alert whenever the
66+
// user has entered a positive input amount but no quote is available.
67+
const hasPositiveRequiredAmount = (requiredTokens ?? []).some(
68+
(t) =>
69+
!t.skipIfBalance &&
70+
(isMaxAmount || (Boolean(t.amountRaw) && t.amountRaw !== '0')),
71+
);
72+
73+
const shouldShowPostQuoteNoQuotesAlert =
74+
isPostQuote &&
75+
Boolean(payToken) &&
76+
!isQuotesLoading &&
77+
!sourceAmounts?.length &&
78+
!quotes?.length &&
79+
hasPositiveRequiredAmount;
80+
6081
const showAlert =
61-
shouldShowNonFiatNoQuotesAlert || shouldShowFiatNoQuotesAlert;
82+
shouldShowNonFiatNoQuotesAlert ||
83+
shouldShowFiatNoQuotesAlert ||
84+
shouldShowPostQuoteNoQuotesAlert;
6285

6386
return useMemo(() => {
6487
if (!showAlert) {

0 commit comments

Comments
 (0)