Skip to content

Commit dd8cb1d

Browse files
authored
chore(card): migrate Card BigInt usage to new library (#29679)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** This change migrates Card feature hooks from the legacy BN.js / `app/util/number` helpers to the native BigInt module at `app/util/number/bigint`, following the repository bigint migration guide. **Reason:** Reduce BN.js usage, align Card code with the bigint utilities, and keep behavior equivalent for balances, delegation amounts, and gas-faucet checks. **What changed:** - `useAssetBalances.tsx` — import `balanceToFiatNumber` from `util/number/bigint`; test mock updated to match. - `useCardDelegation.ts` — import `toTokenMinimalUnit` from `util/number/bigint`; tests mock `bigint` return values instead of decimal strings. - `useNeedsGasFaucet.ts` — remove `bnjs4`; use `hexToBigInt` for wei gas price and balance; compare with native `<`; multiply with `BigInt(gasLimitWithBuffer)`; cast `decGWEIToHexWEI` result to `string` for TypeScript (legacy `conversionUtil` union type). ## **Changelog** CHANGELOG entry: null ## **Related issues** No issue: internal refactor (Card BigInt burndown / migration guide). ## **Manual testing steps** ```gherkin Feature: Card hooks BigInt migration Scenario: Unit tests cover migrated hooks Given the branch is checked out with dependencies installed When the developer runs `yarn jest app/components/UI/Card/hooks/useAssetBalances.test.ts app/components/UI/Card/hooks/useCardDelegation.test.ts app/components/UI/Card/hooks/useNeedsGasFaucet.test.ts` Then all tests pass Scenario: Optional smoke on Card delegation flow Given a dev build on a test network with Card funding available When the user opens Card home and triggers a flow that uses delegation or gas faucet logic Then balances and gas-faucet behavior match expectations (no regression vs prior build) ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [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. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] 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 Card delegation amount conversion and EVM gas-faucet checks by switching from BN/string math to `bigint`, which could change edge-case numeric behavior and transaction approval amounts if not equivalent. > > **Overview** > Migrates Card hooks off legacy `app/util/number` / BN usage onto `app/util/number/bigint` utilities. > > `useAssetBalances` now uses bigint-based `balanceToFiatNumber` for fiat calculations, `useCardDelegation` switches `toTokenMinimalUnit` to return `bigint` (with tests updated to expect `bigint`), and `useNeedsGasFaucet` removes BN math in favor of `hexToBigInt` and native `bigint` arithmetic/comparisons for EVM gas-fee estimation and balance checks. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 42a64d9. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 2adf64f commit dd8cb1d

5 files changed

Lines changed: 16 additions & 17 deletions

File tree

app/components/UI/Card/hooks/useAssetBalances.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jest.mock('../../Ramp/Deposit/constants/networks', () => ({
6060
chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
6161
},
6262
}));
63-
jest.mock('../../../../util/number', () => ({
63+
jest.mock('../../../../util/number/bigint', () => ({
6464
balanceToFiatNumber: jest.fn((balance: string, rate: number) => {
6565
const bal = parseFloat(balance);
6666
return (bal * rate).toString();

app/components/UI/Card/hooks/useAssetBalances.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { useTokensWithBalance } from '../../Bridge/hooks/useTokensWithBalance';
88
import { isSolanaChainId } from '@metamask/bridge-controller';
99
import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
1010
import Engine from '../../../../core/Engine';
11-
import { balanceToFiatNumber } from '../../../../util/number';
1211
import { safeFormatChainIdToHex } from '../util/safeFormatChainIdToHex';
1312
import { TokenI } from '../../Tokens/types';
1413
import { MarketDataDetails } from '@metamask/assets-controllers';
@@ -17,6 +16,7 @@ import I18n from '../../../../../locales/i18n';
1716
import { deriveBalanceFromAssetMarketDetails } from '../../Tokens/util';
1817
import { buildTokenIconUrl } from '../util/buildTokenIconUrl';
1918
import { CARD_CHAIN_IDS } from '../constants';
19+
import { balanceToFiatNumber } from '../../../../util/number/bigint';
2020

2121
const extractTrailingCurrencyCode = (value: string): string | undefined => {
2222
const match = value.trim().match(/([A-Za-z]{3})$/);

app/components/UI/Card/hooks/useCardDelegation.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Logger from '../../../../util/Logger';
99
import { MetaMetricsEvents } from '../../../../core/Analytics';
1010
import { useAnalytics } from '../../../hooks/useAnalytics/useAnalytics';
1111
import { createMockUseAnalyticsHook } from '../../../../util/test/analyticsMock';
12-
import { toTokenMinimalUnit } from '../../../../util/number';
12+
import { toTokenMinimalUnit } from '../../../../util/number/bigint';
1313
import { safeToChecksumAddress } from '../../../../util/address';
1414
import { ARBITRARY_ALLOWANCE } from '../constants';
1515
import {
@@ -42,7 +42,7 @@ jest.mock('../../../../util/Logger', () => ({
4242
error: jest.fn(),
4343
}));
4444

45-
jest.mock('../../../../util/number', () => ({
45+
jest.mock('../../../../util/number/bigint', () => ({
4646
toTokenMinimalUnit: jest.fn(),
4747
}));
4848

@@ -223,7 +223,7 @@ describe('useCardDelegation', () => {
223223
});
224224

225225
// Setup utility mocks
226-
mockToTokenMinimalUnit.mockReturnValue('100000000000000000000');
226+
mockToTokenMinimalUnit.mockReturnValue(100000000000000000000n);
227227
mockSafeToChecksumAddress.mockImplementation(
228228
(address?: string) => (address as `0x${string}`) || undefined,
229229
);
@@ -401,7 +401,7 @@ describe('useCardDelegation', () => {
401401
const mockToken = createMockToken({ decimals: 6 });
402402
const params = createMockDelegationParams();
403403

404-
mockToTokenMinimalUnit.mockReturnValue('100000000');
404+
mockToTokenMinimalUnit.mockReturnValue(100000000n);
405405

406406
const { result } = renderHook(() => useCardDelegation(mockToken));
407407

@@ -1356,7 +1356,7 @@ describe('useCardDelegation', () => {
13561356
};
13571357

13581358
mockToTokenMinimalUnit.mockReturnValue(
1359-
'999999999999999999999999999000000000000000000',
1359+
999999999999999999999999999000000000000000000n,
13601360
);
13611361

13621362
const { result } = renderHook(() => useCardDelegation(mockToken));

app/components/UI/Card/hooks/useCardDelegation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { useAnalytics } from '../../../hooks/useAnalytics/useAnalytics';
2020
import { useEnsureCardNetworkExists } from './useEnsureCardNetworkExists';
2121
import { MetaMetricsEvents } from '../../../../core/Analytics';
2222
import { ARBITRARY_ALLOWANCE } from '../constants';
23-
import { toTokenMinimalUnit } from '../../../../util/number';
23+
import { toTokenMinimalUnit } from '../../../../util/number/bigint';
2424
import AppConstants from '../../../../core/AppConstants';
2525
import { safeToChecksumAddress } from '../../../../util/address';
2626
import { handleSnapRequest } from '../../../../core/Snaps/utils';

app/components/UI/Card/hooks/useNeedsGasFaucet.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller';
44
import { isNonEvmChainId, isSolanaChainId } from '@metamask/bridge-controller';
55
import { Hex, CaipChainId } from '@metamask/utils';
66
import { BigNumber } from 'bignumber.js';
7-
import BN from 'bnjs4';
87
import Engine from '../../../../core/Engine';
98
import { useAccountNativeBalance } from '../../../Views/confirmations/hooks/useAccountNativeBalance';
109
import { selectMinSolBalance } from '../../../../selectors/bridgeController';
1110
import { selectSelectedInternalAccountFormattedAddress } from '../../../../selectors/accountsController';
1211
import { decGWEIToHexWEI } from '../../../../util/conversions';
13-
import { hexToBN } from '../../../../util/number';
12+
import { hexToBigInt } from '../../../../util/number/bigint';
1413
import { safeFormatChainIdToHex } from '../util/safeFormatChainIdToHex';
1514
import { CardFundingToken } from '../types';
1615
import { useLatestBalance } from '../../Bridge/hooks/useLatestBalance';
@@ -93,7 +92,7 @@ export const useNeedsGasFaucet = (
9392
/**
9493
* Estimate gas fee for EVM chains
9594
*/
96-
const estimateEvmGasFee = useCallback(async (): Promise<BN> => {
95+
const estimateEvmGasFee = useCallback(async (): Promise<bigint> => {
9796
try {
9897
const { GasFeeController } = Engine.context;
9998
const result = await GasFeeController.fetchGasFeeEstimates();
@@ -120,16 +119,16 @@ export const useNeedsGasFaucet = (
120119
break;
121120
}
122121

123-
const weiGasPrice = hexToBN(decGWEIToHexWEI(gasPrice));
124-
return weiGasPrice.muln(gasLimitWithBuffer);
122+
const weiGasPrice = hexToBigInt(decGWEIToHexWEI(gasPrice) as string);
123+
return weiGasPrice * BigInt(gasLimitWithBuffer);
125124
} catch (err) {
126125
// Return a conservative fallback estimate if gas estimation fails
127126
// Assume ~20 Gwei gas price as fallback
128-
const fallbackGasPrice = new BN('20000000000'); // 20 Gwei in Wei
127+
const fallbackGasPrice = 20000000000n; // 20 Gwei in Wei
129128
const gasLimitWithBuffer = Math.ceil(
130129
ERC20_APPROVE_GAS_LIMIT * GAS_LIMIT_BUFFER,
131130
);
132-
return fallbackGasPrice.muln(gasLimitWithBuffer);
131+
return fallbackGasPrice * BigInt(gasLimitWithBuffer);
133132
}
134133
}, []);
135134

@@ -144,10 +143,10 @@ export const useNeedsGasFaucet = (
144143

145144
try {
146145
const estimatedGasFee = await estimateEvmGasFee();
147-
const balanceBN = new BN(balanceWeiInHex.replace('0x', ''), 'hex');
146+
const balance = hexToBigInt(balanceWeiInHex);
148147

149148
// User needs faucet if balance is less than estimated gas fee
150-
return balanceBN.lt(estimatedGasFee);
149+
return balance < estimatedGasFee;
151150
} catch (err) {
152151
console.error('Error checking EVM faucet need:', err);
153152
// Assume needs faucet on error

0 commit comments

Comments
 (0)