Skip to content

Commit fa2f9a1

Browse files
authored
fix: Make fiat enabled transaction types remotely configurable (MetaMask#9050)
## Explanation This PR makes the set of fiat-eligible transaction types remotely configurable via feature flags, enabling updates without code releases. ## Changes ### Source changes - **`constants.ts`**: Added `moneyAccountDeposit` as a fiat-eligible type (mapped to `ETH_MAINNET_FIAT_ASSET`), removed `perpsDepositAndOrder`, and added a `FIAT_ENABLED_TYPES` array as the hardcoded default. - **`utils.ts`**: Updated `deriveFiatAssetForFiatPayment` and `resolveTransactionType` to read enabled types from feature flags via `getFiatEnabledTypes()` instead of using the static `FIAT_ASSET_ID_BY_TX_TYPE` map. When resolving batch transactions, falls back to the batch transaction's own type instead of `undefined`. - **`feature-flags.ts`**: Added `enabledTransactionTypes` to `FiatFlags` type and added `getFiatEnabledTypes()` function that reads from `RemoteFeatureFlagController`, falling back to `FIAT_ENABLED_TYPES`. ### Test changes - Added tests for `getFiatEnabledTypes` covering default fallback, missing flag, and remote override scenarios. - Added tests for feature-flag-driven batch transaction type resolution. - Added test for `getFiatAssetPerTransactionType` with `undefined` transaction type to maintain 100% coverage. ## References N/A <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes which transaction types and batch nested legs qualify for fiat pay and asset selection; incorrect flag values could enable/disable fiat flows without a release. > > **Overview** > Fiat-eligible transaction types can now be driven remotely via **`confirmations_pay_fiat.enabledTransactionTypes`**, with **`getFiatEnabledTypes()`** falling back to a new **`FIAT_ENABLED_TYPES`** default list. > > **Batch fiat asset resolution** no longer keys off “has a fiat asset mapping”; it picks the first nested child whose type is in the enabled list, and if none match it keeps the **batch** type (then the usual asset lookup / ETH mainnet fallback). > > **Default eligibility** shifts: **`moneyAccountDeposit`** is added to the asset map and enabled defaults; **`perpsDepositAndOrder`** is removed from the static asset map (still covered only if the remote flag includes it). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 88c7d3e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent db968e7 commit fa2f9a1

6 files changed

Lines changed: 139 additions & 7 deletions

File tree

packages/transaction-pay-controller/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Make fiat-eligible transaction types remotely configurable via `confirmations_pay_fiat.enabledTransactionTypes` feature flag ([#9050](https://github.com/MetaMask/core/pull/9050))
13+
1014
## [23.5.0]
1115

1216
### Changed

packages/transaction-pay-controller/src/strategy/fiat/constants.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@ export const ETH_MAINNET_FIAT_ASSET: TransactionPayFiatAsset = {
3333
export const FIAT_ASSET_ID_BY_TX_TYPE: Partial<
3434
Record<TransactionType, TransactionPayFiatAsset>
3535
> = {
36-
[TransactionType.predictDeposit]: POLYGON_POL_FIAT_ASSET,
36+
[TransactionType.moneyAccountDeposit]: ETH_MAINNET_FIAT_ASSET,
3737
[TransactionType.perpsDeposit]: ARBITRUM_ETH_FIAT_ASSET,
38-
[TransactionType.perpsDepositAndOrder]: ARBITRUM_ETH_FIAT_ASSET,
38+
[TransactionType.predictDeposit]: POLYGON_POL_FIAT_ASSET,
3939
};
40+
41+
export const FIAT_ENABLED_TYPES: TransactionType[] = [
42+
TransactionType.moneyAccountDeposit,
43+
TransactionType.perpsDeposit,
44+
TransactionType.predictDeposit,
45+
];

packages/transaction-pay-controller/src/strategy/fiat/utils.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,51 @@ describe('Fiat Utils', () => {
197197

198198
expect(result).toStrictEqual(ETH_MAINNET_FIAT_ASSET);
199199
});
200+
201+
it('uses feature flag enabled types to filter nested transactions in batch', () => {
202+
getRemoteFeatureFlagControllerStateMock.mockReturnValue({
203+
...getDefaultRemoteFeatureFlagControllerState(),
204+
remoteFeatureFlags: {
205+
confirmations_pay_fiat: {
206+
enabledTransactionTypes: [TransactionType.predictDeposit],
207+
},
208+
},
209+
});
210+
211+
const transaction = {
212+
nestedTransactions: [
213+
{ type: TransactionType.perpsDeposit },
214+
{ type: TransactionType.predictDeposit },
215+
],
216+
type: TransactionType.batch,
217+
} as TransactionMeta;
218+
219+
const result = deriveFiatAssetForFiatPayment(transaction, messenger);
220+
221+
expect(result).toStrictEqual(
222+
FIAT_ASSET_ID_BY_TX_TYPE[TransactionType.predictDeposit],
223+
);
224+
});
225+
226+
it('falls back to batch type when no nested transaction matches enabled types', () => {
227+
getRemoteFeatureFlagControllerStateMock.mockReturnValue({
228+
...getDefaultRemoteFeatureFlagControllerState(),
229+
remoteFeatureFlags: {
230+
confirmations_pay_fiat: {
231+
enabledTransactionTypes: [TransactionType.predictDeposit],
232+
},
233+
},
234+
});
235+
236+
const transaction = {
237+
nestedTransactions: [{ type: TransactionType.perpsDeposit }],
238+
type: TransactionType.batch,
239+
} as TransactionMeta;
240+
241+
const result = deriveFiatAssetForFiatPayment(transaction, messenger);
242+
243+
expect(result).toStrictEqual(ETH_MAINNET_FIAT_ASSET);
244+
});
200245
});
201246

202247
describe('resolveSourceAmountRaw', () => {

packages/transaction-pay-controller/src/strategy/fiat/utils.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,40 @@ import { BigNumber } from 'bignumber.js';
77

88
import { projectLogger } from '../../logger';
99
import type { TransactionPayControllerMessenger } from '../../types';
10-
import { getFiatAssetPerTransactionType } from '../../utils/feature-flags';
10+
import {
11+
getFiatAssetPerTransactionType,
12+
getFiatEnabledTypes,
13+
} from '../../utils/feature-flags';
1114
import { getTokenInfo } from '../../utils/token';
1215
import { getTransferredAmountFromTxHash } from '../../utils/transaction';
1316
import type { RelayQuote } from '../relay/types';
1417
import type { TransactionPayFiatAsset } from './constants';
15-
import { FIAT_ASSET_ID_BY_TX_TYPE } from './constants';
1618

1719
const log = createModuleLogger(projectLogger, 'fiat-utils');
1820

1921
export function deriveFiatAssetForFiatPayment(
2022
transaction: TransactionMeta,
2123
messenger: TransactionPayControllerMessenger,
2224
): TransactionPayFiatAsset {
23-
const txType = resolveTransactionType(transaction);
25+
const enabledTypes = getFiatEnabledTypes(messenger);
26+
const txType = resolveTransactionType(transaction, enabledTypes);
2427

2528
return getFiatAssetPerTransactionType(messenger, txType);
2629
}
2730

2831
function resolveTransactionType(
2932
transaction: TransactionMeta,
33+
enabledTypes: TransactionType[],
3034
): TransactionType | undefined {
3135
if (transaction.type !== TransactionType.batch) {
3236
return transaction.type;
3337
}
3438

35-
return transaction.nestedTransactions?.find(
36-
(tx) => tx.type && FIAT_ASSET_ID_BY_TX_TYPE[tx.type] !== undefined,
39+
const nestedType = transaction.nestedTransactions?.find(
40+
(tx) => tx.type && enabledTypes.includes(tx.type),
3741
)?.type;
42+
43+
return nestedType ?? transaction.type;
3844
}
3945

4046
/**

packages/transaction-pay-controller/src/utils/feature-flags.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Hex } from '@metamask/utils';
44
import { getDefaultRemoteFeatureFlagControllerState } from '../../../remote-feature-flag-controller/src/remote-feature-flag-controller';
55
import { TransactionPayStrategy } from '../constants';
66
import type { TransactionPayFiatAsset } from '../strategy/fiat/constants';
7+
import { FIAT_ENABLED_TYPES } from '../strategy/fiat/constants';
78
import { getMessengerMock } from '../tests/messenger-mock';
89
import {
910
DEFAULT_ACROSS_API_BASE,
@@ -17,6 +18,7 @@ import {
1718
getAssetsUnifyStateFeature,
1819
getFallbackGas,
1920
getFiatAssetPerTransactionType,
21+
getFiatEnabledTypes,
2022
getFiatFeeReserveMultiplier,
2123
getFiatMaxRateDriftPercent,
2224
DEFAULT_RELAY_EXECUTE_URL,
@@ -1423,6 +1425,15 @@ describe('Feature Flags Utils', () => {
14231425
chainId: '0x89',
14241426
};
14251427

1428+
it('returns ETH mainnet fallback when transaction type is undefined', () => {
1429+
const result = getFiatAssetPerTransactionType(messenger, undefined);
1430+
1431+
expect(result).toStrictEqual({
1432+
address: '0x0000000000000000000000000000000000000000',
1433+
chainId: '0x1',
1434+
});
1435+
});
1436+
14261437
it('returns ETH mainnet fallback when confirmations_pay_fiat flag is absent', () => {
14271438
const result = getFiatAssetPerTransactionType(
14281439
messenger,
@@ -1518,6 +1529,47 @@ describe('Feature Flags Utils', () => {
15181529
});
15191530
});
15201531

1532+
describe('getFiatEnabledTypes', () => {
1533+
it('returns hardcoded defaults when feature flag is absent', () => {
1534+
const result = getFiatEnabledTypes(messenger);
1535+
1536+
expect(result).toStrictEqual(FIAT_ENABLED_TYPES);
1537+
});
1538+
1539+
it('returns hardcoded defaults when confirmations_pay_fiat exists but enabledTransactionTypes is missing', () => {
1540+
getRemoteFeatureFlagControllerStateMock.mockReturnValue({
1541+
...getDefaultRemoteFeatureFlagControllerState(),
1542+
remoteFeatureFlags: {
1543+
confirmations_pay_fiat: {},
1544+
},
1545+
});
1546+
1547+
const result = getFiatEnabledTypes(messenger);
1548+
1549+
expect(result).toStrictEqual(FIAT_ENABLED_TYPES);
1550+
});
1551+
1552+
it('returns enabled types from feature flag when set', () => {
1553+
const customTypes = [
1554+
TransactionType.perpsDeposit,
1555+
TransactionType.predictDeposit,
1556+
];
1557+
1558+
getRemoteFeatureFlagControllerStateMock.mockReturnValue({
1559+
...getDefaultRemoteFeatureFlagControllerState(),
1560+
remoteFeatureFlags: {
1561+
confirmations_pay_fiat: {
1562+
enabledTransactionTypes: customTypes,
1563+
},
1564+
},
1565+
});
1566+
1567+
const result = getFiatEnabledTypes(messenger);
1568+
1569+
expect(result).toStrictEqual(customTypes);
1570+
});
1571+
});
1572+
15211573
describe('getFiatFeeReserveMultiplier', () => {
15221574
it('returns 1.2 when feature flag is not set', () => {
15231575
getRemoteFeatureFlagControllerStateMock.mockReturnValue({

packages/transaction-pay-controller/src/utils/feature-flags.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { TransactionPayFiatAsset } from '../strategy/fiat/constants';
99
import {
1010
ETH_MAINNET_FIAT_ASSET,
1111
FIAT_ASSET_ID_BY_TX_TYPE,
12+
FIAT_ENABLED_TYPES,
1213
} from '../strategy/fiat/constants';
1314
import {
1415
RELAY_EXECUTE_URL,
@@ -94,6 +95,7 @@ type FiatFlags = {
9495
assetPerTransactionType?: Partial<
9596
Record<TransactionType, TransactionPayFiatAsset>
9697
>;
98+
enabledTransactionTypes: TransactionType[];
9799
feeReserveMultiplier?: number;
98100
maxRateDriftPercent?: number;
99101
};
@@ -826,6 +828,23 @@ export function getFiatAssetPerTransactionType(
826828
);
827829
}
828830

831+
/**
832+
* Get the enabled fiat transaction types.
833+
*
834+
* @param messenger - Controller messenger.
835+
* @returns The enabled fiat transaction types.
836+
*/
837+
export function getFiatEnabledTypes(
838+
messenger: TransactionPayControllerMessenger,
839+
): TransactionType[] {
840+
const state = messenger.call('RemoteFeatureFlagController:getState');
841+
const fiatFlags = state.remoteFeatureFlags?.confirmations_pay_fiat as
842+
| FiatFlags
843+
| undefined;
844+
845+
return fiatFlags?.enabledTransactionTypes ?? FIAT_ENABLED_TYPES;
846+
}
847+
829848
/**
830849
* Returns the fee reserve multiplier for fiat three-phase submit.
831850
*

0 commit comments

Comments
 (0)