Skip to content

Commit 4e58e96

Browse files
authored
fix: cp-8.3.0 Fix return undefined token fiat amount instead of locking 1 (MetaMask#33461)
## **Description** In `useTokenFiatRates`, when a token's price was not found, the code was falling back to `token?.price ?? 1`, which incorrectly used `1` as a default price multiplier. This caused the fiat amount to silently return `conversionRate` instead of indicating no price is available — making it look like the token was worth 1 unit of the base currency. This fix changes the behavior to return `undefined` when `token.price` is falsy, so callers can distinguish between "price not found" and a real fiat value. The test expectation is updated accordingly. ## **Changelog** CHANGELOG entry: Fixed `useTokenFiatRates` returning an incorrect fiat amount (using conversion rate × 1) when a token price is unavailable — now returns `undefined` instead. ## **Related issues** Fixes: cherry-pick of MetaMask#33447 (cp-8.3.0) ## **Manual testing steps** 1. Open MetaMask Mobile and navigate to a confirmation screen that displays token fiat amounts (e.g. a token approval or transfer). 2. Use a token whose price is **not** available in the price feed. 3. Verify the fiat amount for that token shows as unavailable/empty rather than an incorrect non-zero value. 4. Verify tokens **with** known prices still display the correct fiat amount. ## **Screenshots/Recordings** Before: <img width="530" height="1008" alt="rc - before - send" src="https://github.com/user-attachments/assets/fb3e58b9-0ae5-43b6-bcca-c39710c4c056" /> <img width="530" height="1008" alt="rc - before - mmpay" src="https://github.com/user-attachments/assets/7778b8bd-fdd1-4203-b6d5-cfcc859b2eb0" /> After: <img width="530" height="1008" alt="rc - after - send" src="https://github.com/user-attachments/assets/f11099b7-014f-4a1a-a9ef-c64701172712" /> <img width="530" height="1008" alt="rc - after - mmpay" src="https://github.com/user-attachments/assets/bcf9adce-9b26-4fe2-b56e-9165bc9f8a3c" /> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/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] > **Medium Risk** > Touches MM Pay / confirmation amount and balance USD math when price feeds are missing; behavior is safer but users may see zero/empty conversions until prices load. > > **Overview** > Stops **confirmation pay flows** from treating a missing token price as **1** and showing a bogus fiat value (effectively `conversionRate` alone). > > **`useTokenFiatRates`** now returns **`undefined`** when `token.price` is absent instead of multiplying by a default `1`. The unit test expectation is updated from the conversion rate to **`undefined`**. > > **`useTransactionCustomAmount`** no longer falls back with `?? 1` on the pay-token fiat rate. Fiat→token conversion and USD balance helpers (`amountHuman`, `useTokenBalance` for predict / money-account withdraw) guard on a defined rate and use **`'0'`** or **`0`** when the rate is missing, while mUSD still keeps its `?? 1` fallback for money-account withdraw. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1bb4320. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 6df2cbe commit 4e58e96

4 files changed

Lines changed: 36 additions & 13 deletions

File tree

app/components/Views/confirmations/hooks/tokens/useTokenFiatRates.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe('useTokenFiatRates', () => {
9797
]);
9898
});
9999

100-
it('returns conversion rate only if token price not found', () => {
100+
it('returns undefined if token price not found', () => {
101101
const result = runHook({
102102
requests: [
103103
{
@@ -107,7 +107,7 @@ describe('useTokenFiatRates', () => {
107107
],
108108
});
109109

110-
expect(result).toEqual([CONVERSION_RATE_1_MOCK]);
110+
expect(result).toEqual([undefined]);
111111
});
112112

113113
it('returns fixed exchange rate for stablecoin when currency is USD', () => {

app/components/Views/confirmations/hooks/tokens/useTokenFiatRates.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ export function useTokenFiatRates(requests: TokenFiatRateRequest[]) {
5555
return undefined;
5656
}
5757

58-
return (token?.price ?? 1) * conversionRate;
58+
if (!token?.price) {
59+
return undefined;
60+
}
61+
62+
return token.price * conversionRate;
5963
}),
6064
[
6165
currencyRates,

app/components/Views/confirmations/hooks/tokens/useTokenWithBalance.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,25 @@ const useTransactionMetadataRequestMock = jest.mocked(
1414
useTransactionMetadataRequest,
1515
);
1616

17-
function runHook(tokenAddress: Hex, chainId: Hex) {
17+
const marketDataMock = {
18+
engine: {
19+
backgroundState: {
20+
TokenRatesController: {
21+
marketData: {
22+
'0x1': {
23+
'0x1234567890AbcdEF1234567890aBcdef12345678': { price: 1 },
24+
},
25+
},
26+
},
27+
},
28+
},
29+
};
30+
31+
function runHook(tokenAddress: Hex, chainId: Hex, extraState = {}) {
1832
return renderHookWithProvider(
1933
() => useTokenWithBalance(tokenAddress, chainId),
2034
{
21-
state: merge({}, otherControllersMock),
35+
state: merge({}, otherControllersMock, marketDataMock, extraState),
2236
},
2337
);
2438
}

app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ export function useTransactionCustomAmount({
9595
TransactionType.moneyAccountWithdraw,
9696
]);
9797
const tokenAddress = getTokenAddress(transactionMeta);
98-
const payTokenFiatRate =
99-
useTokenFiatRate(tokenAddress, chainId, currency) ?? 1;
98+
const payTokenFiatRate = useTokenFiatRate(tokenAddress, chainId, currency);
10099
const musdFiatRate =
101100
useTokenFiatRate(
102101
MUSD_TOKEN_ADDRESS,
@@ -166,7 +165,9 @@ export function useTransactionCustomAmount({
166165

167166
const amountHuman = useMemo(
168167
() =>
169-
new BigNumber(amountFiat || '0').dividedBy(tokenFiatRate).toString(10),
168+
tokenFiatRate
169+
? new BigNumber(amountFiat || '0').dividedBy(tokenFiatRate).toString(10)
170+
: '0',
170171
[amountFiat, tokenFiatRate],
171172
);
172173

@@ -360,7 +361,7 @@ export function useTransactionCustomAmount({
360361
};
361362
}
362363

363-
function useTokenBalance(tokenUsdRate: number) {
364+
function useTokenBalance(tokenUsdRate: number | undefined) {
364365
const transactionMeta = useTransactionMetadataRequest() as TransactionMeta;
365366
const transactionId = transactionMeta?.id ?? '';
366367

@@ -372,9 +373,11 @@ function useTokenBalance(tokenUsdRate: number) {
372373

373374
const { data: predictBalanceHuman = 0 } = usePredictBalance();
374375

375-
const predictBalanceUsd = new BigNumber(predictBalanceHuman ?? '0')
376-
.multipliedBy(tokenUsdRate)
377-
.toNumber();
376+
const predictBalanceUsd = tokenUsdRate
377+
? new BigNumber(predictBalanceHuman ?? '0')
378+
.multipliedBy(tokenUsdRate)
379+
.toNumber()
380+
: 0;
378381

379382
const { withdrawableMusd, withdrawableFiatRaw } = useMoneyAccountBalance();
380383

@@ -396,7 +399,9 @@ function useTokenBalance(tokenUsdRate: number) {
396399
if (withdrawableMusd === undefined) {
397400
return 0;
398401
}
399-
return withdrawableMusd.multipliedBy(tokenUsdRate).toNumber();
402+
return tokenUsdRate
403+
? withdrawableMusd.multipliedBy(tokenUsdRate).toNumber()
404+
: 0;
400405
}
401406

402407
if (hasTransactionType(transactionMeta, [TransactionType.predictWithdraw])) {

0 commit comments

Comments
 (0)