Skip to content

Commit 3e0a296

Browse files
fix: update selectedGasFeeToken when payment token is selected for ga… (#25273)
…sless flow cp-7.62.1 cherry-pick of #25209 <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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] > Improves gasless deposit handling and simplifies fee token plumbing. > > - Updates `useTransactionPayToken` to set `selectedGasFeeToken` (and `isGasFeeTokenIgnoredIfBalance`) when a pay token is chosen for `predictDeposit`, with tests > - Removes `gasFeeToken` usage across Predict/Perps deposit flows: deletes field from provider types, Polymarket provider return, controller submissions, and related tests > - Adds a guard in `PerpsController.depositWithConfirmation` to throw when `NetworkController.findNetworkClientIdByChainId` returns no client > - Test scaffolding updated (Engine mocks, transaction submission) accordingly > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5126914. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent a29e20b commit 3e0a296

8 files changed

Lines changed: 191 additions & 96 deletions

File tree

app/components/UI/Perps/controllers/PerpsController.test.ts

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,6 @@ import {
2020
import type { IPerpsProvider } from './types';
2121
import { HyperLiquidProvider } from './providers/HyperLiquidProvider';
2222
import { createMockHyperLiquidProvider } from '../__mocks__/providerMocks';
23-
import Logger from '../../../../util/Logger';
24-
import { FeatureFlagConfigurationService } from './services/FeatureFlagConfigurationService';
25-
import { DepositService } from './services/DepositService';
26-
import { MarketDataService } from './services/MarketDataService';
27-
import { TradingService } from './services/TradingService';
28-
import { AccountService } from './services/AccountService';
29-
import { DataLakeService } from './services/DataLakeService';
30-
import {
31-
ARBITRUM_MAINNET_CHAIN_ID_HEX,
32-
USDC_ARBITRUM_MAINNET_ADDRESS,
33-
} from '../constants/hyperLiquidConfig';
3423
import Engine from '../../../../core/Engine';
3524

3625
jest.mock('./providers/HyperLiquidProvider');
@@ -102,6 +91,7 @@ jest.mock('../../../../core/Engine', () => {
10291
getNetworkClientById: jest.fn().mockReturnValue({
10392
configuration: { chainId: '0x1' },
10493
}),
94+
findNetworkClientIdByChainId: jest.fn().mockReturnValue('mainnet'),
10595
};
10696

10797
const mockAccountTreeController = {
@@ -116,6 +106,10 @@ jest.mock('../../../../core/Engine', () => {
116106
const mockTransactionController = {
117107
estimateGasFee: jest.fn(),
118108
estimateGas: jest.fn(),
109+
addTransaction: jest.fn().mockResolvedValue({
110+
result: Promise.resolve('0xmocktxhash'),
111+
transactionMeta: { id: 'mock-tx-id', hash: '0xmocktxhash' },
112+
}),
119113
};
120114

121115
const mockAccountTrackerController = {
@@ -286,6 +280,15 @@ jest.mock('./services/FeatureFlagConfigurationService', () => ({
286280
},
287281
}));
288282

283+
// Import mocked modules - these imports get the mocked versions
284+
import Logger from '../../../../util/Logger';
285+
import { DepositService } from './services/DepositService';
286+
import { MarketDataService } from './services/MarketDataService';
287+
import { TradingService } from './services/TradingService';
288+
import { AccountService } from './services/AccountService';
289+
import { DataLakeService } from './services/DataLakeService';
290+
import { FeatureFlagConfigurationService } from './services/FeatureFlagConfigurationService';
291+
289292
/**
290293
* Testable version of PerpsController that exposes protected methods for testing.
291294
* This follows the pattern used in RewardsController.test.ts
@@ -2348,50 +2351,9 @@ describe('PerpsController', () => {
23482351
origin: 'metamask',
23492352
type: 'perpsDeposit',
23502353
skipInitialGasEstimate: true,
2351-
gasFeeToken: undefined,
23522354
});
23532355
});
23542356

2355-
it('adds gasFeeToken for Arbitrum USDC deposits', async () => {
2356-
markControllerAsInitialized();
2357-
controller.testSetProviders(new Map([['hyperliquid', mockProvider]]));
2358-
2359-
Engine.context.AccountTrackerController.state.accountsByChainId = {
2360-
[ARBITRUM_MAINNET_CHAIN_ID_HEX]: {
2361-
[mockTransaction.from.toLowerCase()]: {
2362-
balance: '0x0',
2363-
},
2364-
},
2365-
};
2366-
2367-
jest.spyOn(DepositService, 'prepareTransaction').mockResolvedValueOnce({
2368-
transaction: {
2369-
...mockTransaction,
2370-
to: USDC_ARBITRUM_MAINNET_ADDRESS,
2371-
},
2372-
assetChainId: ARBITRUM_MAINNET_CHAIN_ID_HEX,
2373-
currentDepositId: mockDepositId,
2374-
});
2375-
2376-
await controller.depositWithConfirmation('100');
2377-
2378-
expect(
2379-
Engine.context.TransactionController.addTransaction,
2380-
).toHaveBeenCalledWith(
2381-
{
2382-
...mockTransaction,
2383-
to: USDC_ARBITRUM_MAINNET_ADDRESS,
2384-
},
2385-
{
2386-
networkClientId: mockNetworkClientId,
2387-
origin: 'metamask',
2388-
type: 'perpsDeposit',
2389-
skipInitialGasEstimate: true,
2390-
gasFeeToken: USDC_ARBITRUM_MAINNET_ADDRESS,
2391-
},
2392-
);
2393-
});
2394-
23952357
it('throws error when controller not initialized', async () => {
23962358
controller.testSetInitialized(false);
23972359

app/components/UI/Perps/controllers/PerpsController.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,13 @@ import {
1313
TransactionControllerTransactionSubmittedEvent,
1414
TransactionType,
1515
} from '@metamask/transaction-controller';
16-
import { Hex } from '@metamask/utils';
17-
import Engine from '../../../../core/Engine';
18-
import {
19-
ARBITRUM_MAINNET_CHAIN_ID_HEX,
20-
USDC_ARBITRUM_MAINNET_ADDRESS,
21-
USDC_SYMBOL,
22-
} from '../constants/hyperLiquidConfig';
16+
import { USDC_SYMBOL } from '../constants/hyperLiquidConfig';
2317
import {
2418
LastTransactionResult,
2519
TransactionStatus,
2620
} from '../types/transactionTypes';
2721
import { DevLogger } from '../../../../core/SDKConnect/utils/DevLogger';
22+
import Engine from '../../../../core/Engine';
2823
import Logger, { type LoggerErrorOptions } from '../../../../util/Logger';
2924
import { MetaMetrics, MetaMetricsEvents } from '../../../../core/Analytics';
3025
import { MetricsEventBuilder } from '../../../../core/Analytics/MetricsEventBuilder';
@@ -1390,23 +1385,20 @@ export class PerpsController extends BaseController<
13901385
const networkClientId =
13911386
NetworkController.findNetworkClientIdByChainId(assetChainId);
13921387

1393-
const gasFeeToken =
1394-
transaction.to &&
1395-
assetChainId.toLowerCase() === ARBITRUM_MAINNET_CHAIN_ID_HEX &&
1396-
transaction.to.toLowerCase() ===
1397-
USDC_ARBITRUM_MAINNET_ADDRESS.toLowerCase()
1398-
? (transaction.to as Hex)
1399-
: undefined;
1400-
1388+
if (!networkClientId) {
1389+
throw new Error(
1390+
`No network client found for chain ${assetChainId}. Please add the network first.`,
1391+
);
1392+
}
14011393
// addTransaction shows the confirmation screen and returns a promise
1394+
// submit shows the confirmation screen and returns a promise
14021395
// The promise will resolve when transaction completes or reject if cancelled/failed
14031396
const { result, transactionMeta } =
14041397
await TransactionController.addTransaction(transaction, {
14051398
networkClientId,
14061399
origin: 'metamask',
14071400
type: TransactionType.perpsDeposit,
14081401
skipInitialGasEstimate: true,
1409-
gasFeeToken,
14101402
});
14111403

14121404
// Store the transaction ID and try to get amount from transaction

app/components/UI/Predict/controllers/PredictController.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
} from '@metamask/transaction-controller';
1414
import type { NetworkState } from '@metamask/network-controller';
1515
import type { InternalAccount } from '@metamask/keyring-internal-api';
16-
import type { Hex } from '@metamask/utils';
1716

1817
import Engine from '../../../../core/Engine';
1918
import DevLogger from '../../../../core/SDKConnect/utils/DevLogger';
@@ -22,7 +21,6 @@ import {
2221
addTransactionBatch,
2322
} from '../../../../util/transaction-controller';
2423
import { PolymarketProvider } from '../providers/polymarket/PolymarketProvider';
25-
import { MATIC_CONTRACTS } from '../providers/polymarket/constants';
2624
import type { OrderPreview } from '../providers/types';
2725
import {
2826
PredictBalance,
@@ -3005,7 +3003,6 @@ describe('PredictController', () => {
30053003
mockPolymarketProvider.prepareDeposit.mockResolvedValue({
30063004
transactions: mockTransactions,
30073005
chainId: mockChainId,
3008-
gasFeeToken: MATIC_CONTRACTS.collateral as Hex,
30093006
});
30103007

30113008
(addTransactionBatch as jest.Mock).mockResolvedValue({
@@ -3073,7 +3070,6 @@ describe('PredictController', () => {
30733070
disableUpgrade: true,
30743071
skipInitialGasEstimate: true,
30753072
transactions: mockTransactions,
3076-
gasFeeToken: MATIC_CONTRACTS.collateral,
30773073
});
30783074
});
30793075
});

app/components/UI/Predict/controllers/PredictController.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,7 +1863,7 @@ export class PredictController extends BaseController<
18631863
throw new Error('Deposit preparation returned undefined');
18641864
}
18651865

1866-
const { transactions, chainId, gasFeeToken } = depositPreparation;
1866+
const { transactions, chainId } = depositPreparation;
18671867

18681868
if (!transactions || transactions.length === 0) {
18691869
throw new Error('No transactions returned from deposit preparation');
@@ -1896,7 +1896,6 @@ export class PredictController extends BaseController<
18961896
disableUpgrade: true,
18971897
skipInitialGasEstimate: true,
18981898
transactions,
1899-
gasFeeToken,
19001899
});
19011900

19021901
if (!batchResult?.batchId) {

app/components/UI/Predict/providers/polymarket/PolymarketProvider.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,15 +1512,9 @@ export class PolymarketProvider implements PredictProvider {
15121512
type: TransactionType.predictDeposit,
15131513
});
15141514

1515-
const chainId = CHAIN_IDS.POLYGON;
1516-
const isPolygonChain =
1517-
chainId.toLowerCase() ===
1518-
numberToHex(POLYGON_MAINNET_CHAIN_ID).toLowerCase();
1519-
15201515
return {
1521-
chainId,
1516+
chainId: CHAIN_IDS.POLYGON,
15221517
transactions,
1523-
gasFeeToken: isPolygonChain ? (collateral as Hex) : undefined,
15241518
};
15251519
}
15261520

app/components/UI/Predict/providers/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ export interface PrepareDepositResponse {
180180
};
181181
type?: TransactionType;
182182
}[];
183-
gasFeeToken?: Hex;
184183
}
185184

186185
export interface GetPredictWalletParams {

app/components/Views/confirmations/hooks/pay/useTransactionPayToken.test.ts

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ import {
1010
tokenAddress1Mock,
1111
} from '../../__mocks__/controllers/other-controllers-mock';
1212
import { TransactionType } from '@metamask/transaction-controller';
13-
import { TransactionPaymentToken } from '@metamask/transaction-pay-controller';
13+
import {
14+
TransactionPaymentToken,
15+
TransactionPayRequiredToken,
16+
} from '@metamask/transaction-pay-controller';
1417
import Engine from '../../../../../core/Engine';
1518
import { flushPromises } from '../../../../../util/test/utils';
19+
import { updateTransaction } from '../../../../../util/transaction-controller';
1620

1721
jest.mock('../../../../../core/Engine', () => ({
1822
context: {
@@ -28,6 +32,10 @@ jest.mock('../../../../../core/Engine', () => ({
2832
},
2933
}));
3034

35+
jest.mock('../../../../../util/transaction-controller', () => ({
36+
updateTransaction: jest.fn(),
37+
}));
38+
3139
const STATE_MOCK = merge(
3240
simpleSendTransactionControllerMock,
3341
transactionApprovalControllerMock,
@@ -50,14 +58,22 @@ const PAY_TOKEN_MOCK = {
5058
symbol: 'TST',
5159
} as TransactionPaymentToken;
5260

61+
const REQUIRED_TOKEN_MOCK = {
62+
address: tokenAddress1Mock,
63+
chainId: ChainId.mainnet,
64+
skipIfBalance: false,
65+
} as unknown as TransactionPayRequiredToken;
66+
5367
function runHook({
5468
currency,
5569
payToken,
5670
type,
71+
requiredTokens,
5772
}: {
5873
currency?: string;
5974
payToken?: TransactionPaymentToken;
6075
type?: TransactionType;
76+
requiredTokens?: TransactionPayRequiredToken[];
6177
} = {}) {
6278
const mockState = cloneDeep(STATE_MOCK);
6379

@@ -66,7 +82,7 @@ function runHook({
6682
[TRANSACTION_ID_MOCK]: {
6783
isLoading: false,
6884
paymentToken: payToken,
69-
tokens: [],
85+
tokens: requiredTokens ?? [],
7086
},
7187
},
7288
};
@@ -157,4 +173,104 @@ describe('useTransactionPayToken', () => {
157173

158174
expect(result.current.isNative).toBe(true);
159175
});
176+
177+
describe('selectedGasFeeToken update for predictDeposit', () => {
178+
const updateTransactionMock = jest.mocked(updateTransaction);
179+
180+
beforeEach(() => {
181+
updateTransactionMock.mockClear();
182+
});
183+
184+
it('updates transaction with selectedGasFeeToken for predictDeposit when pay token matches required token', async () => {
185+
const { result } = runHook({
186+
payToken: PAY_TOKEN_MOCK,
187+
type: TransactionType.predictDeposit,
188+
requiredTokens: [REQUIRED_TOKEN_MOCK],
189+
});
190+
191+
result.current.setPayToken({
192+
address: PAY_TOKEN_MOCK.address,
193+
chainId: PAY_TOKEN_MOCK.chainId as ChainId,
194+
});
195+
196+
await flushPromises();
197+
198+
expect(updateTransactionMock).toHaveBeenCalledWith(
199+
expect.objectContaining({
200+
selectedGasFeeToken: PAY_TOKEN_MOCK.address,
201+
isGasFeeTokenIgnoredIfBalance: true,
202+
}),
203+
TRANSACTION_ID_MOCK,
204+
);
205+
});
206+
207+
it('does not update transaction for non-predictDeposit transaction types', async () => {
208+
const { result } = runHook({
209+
payToken: PAY_TOKEN_MOCK,
210+
type: TransactionType.simpleSend,
211+
requiredTokens: [REQUIRED_TOKEN_MOCK],
212+
});
213+
214+
result.current.setPayToken({
215+
address: PAY_TOKEN_MOCK.address,
216+
chainId: PAY_TOKEN_MOCK.chainId as ChainId,
217+
});
218+
219+
await flushPromises();
220+
221+
expect(updateTransactionMock).not.toHaveBeenCalled();
222+
});
223+
224+
it('resets selectedGasFeeToken when pay token does not match required token', async () => {
225+
const differentToken = {
226+
address: '0xDifferentTokenAddress1234567890123456789012',
227+
chainId: ChainId.mainnet,
228+
skipIfBalance: false,
229+
} as unknown as TransactionPayRequiredToken;
230+
231+
const { result } = runHook({
232+
payToken: PAY_TOKEN_MOCK,
233+
type: TransactionType.predictDeposit,
234+
requiredTokens: [differentToken],
235+
});
236+
237+
result.current.setPayToken({
238+
address: PAY_TOKEN_MOCK.address,
239+
chainId: PAY_TOKEN_MOCK.chainId as ChainId,
240+
});
241+
242+
await flushPromises();
243+
244+
expect(updateTransactionMock).toHaveBeenCalledWith(
245+
expect.objectContaining({
246+
selectedGasFeeToken: undefined,
247+
isGasFeeTokenIgnoredIfBalance: undefined,
248+
}),
249+
TRANSACTION_ID_MOCK,
250+
);
251+
});
252+
253+
it('resets selectedGasFeeToken when no required tokens exist', async () => {
254+
const { result } = runHook({
255+
payToken: PAY_TOKEN_MOCK,
256+
type: TransactionType.predictDeposit,
257+
requiredTokens: [],
258+
});
259+
260+
result.current.setPayToken({
261+
address: PAY_TOKEN_MOCK.address,
262+
chainId: PAY_TOKEN_MOCK.chainId as ChainId,
263+
});
264+
265+
await flushPromises();
266+
267+
expect(updateTransactionMock).toHaveBeenCalledWith(
268+
expect.objectContaining({
269+
selectedGasFeeToken: undefined,
270+
isGasFeeTokenIgnoredIfBalance: undefined,
271+
}),
272+
TRANSACTION_ID_MOCK,
273+
);
274+
});
275+
});
160276
});

0 commit comments

Comments
 (0)