Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/transaction-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add `gasFeeTokens` to `TransactionMeta` ([#5524](https://github.com/MetaMask/core/pull/5524))
- Add `GasFeeToken` type.
- Add `selectedGasFeeToken` to `TransactionMeta`.
- Add `updateSelectedGasFeeToken` method.
- Support security validation of transaction batches ([#5526](https://github.com/MetaMask/core/pull/5526))
- Add `ValidateSecurityRequest` type.
- Add optional `securityAlertId` to `SecurityAlertResponse`.
Expand Down Expand Up @@ -69,7 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add additional metadata for batch metrics ([#5488](https://github.com/MetaMask/core/pull/5488))
- Add `delegationAddress` to `TransactionMetadata`.
- Add `delegationAddress` to `TransactionMeta`.
- Add `NestedTransactionMetadata` type containing `BatchTransactionParams` and `type`.
- Add optional `type` to `TransactionBatchSingleRequest`.
- Verify EIP-7702 contract address using signatures ([#5472](https://github.com/MetaMask/core/pull/5472))
Expand All @@ -80,8 +84,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **BREAKING:** Bump `@metamask/accounts-controller` peer dependency to `^26.1.0` ([#5481](https://github.com/MetaMask/core/pull/5481))
- **BREAKING:** Add additional metadata for batch metrics ([#5488](https://github.com/MetaMask/core/pull/5488))
- Change `error` in `TransactionMetadata` to optional for all statuses.
- Change `nestedTransactions` in `TransactionMetadata` to array of `NestedTransactionMetadata`.
- Change `error` in `TransactionMeta` to optional for all statuses.
- Change `nestedTransactions` in `TransactionMeta` to array of `NestedTransactionMetadata`.
- Throw if `addTransactionBatch` called with external origin and size limit exceeded ([#5489](https://github.com/MetaMask/core/pull/5489))
- Verify EIP-7702 contract address using signatures ([#5472](https://github.com/MetaMask/core/pull/5472))
- Use new `contracts` property from feature flags instead of `contractAddresses`.
Expand Down
171 changes: 150 additions & 21 deletions packages/transaction-controller/src/TransactionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ import type {
TransactionParams,
TransactionHistoryEntry,
TransactionError,
SimulationData,
GasFeeFlow,
GasFeeFlowResponse,
SubmitHistoryEntry,
InternalAccount,
PublishHook,
GasFeeToken,
} from './types';
import {
GasFeeEstimateType,
Expand All @@ -86,6 +86,7 @@ import {
getTransactionLayer1GasFee,
updateTransactionLayer1GasFee,
} from './utils/layer1-gas-fee-flow';
import type { GetSimulationDataResult } from './utils/simulation';
import { getSimulationData } from './utils/simulation';
import {
updatePostTransactionBalance,
Expand Down Expand Up @@ -448,24 +449,40 @@ const TRANSACTION_META_2_MOCK = {
},
} as TransactionMeta;

const SIMULATION_DATA_MOCK: SimulationData = {
nativeBalanceChange: {
previousBalance: '0x0',
newBalance: '0x1',
difference: '0x1',
isDecrease: false,
},
tokenBalanceChanges: [
{
address: '0x123',
standard: SimulationTokenStandard.erc721,
id: '0x456',
previousBalance: '0x1',
newBalance: '0x3',
difference: '0x2',
const SIMULATION_DATA_RESULT_MOCK: GetSimulationDataResult = {
gasFeeTokens: [],
simulationData: {
nativeBalanceChange: {
previousBalance: '0x0',
newBalance: '0x1',
difference: '0x1',
isDecrease: false,
},
],
tokenBalanceChanges: [
{
address: '0x123',
standard: SimulationTokenStandard.erc721,
id: '0x456',
previousBalance: '0x1',
newBalance: '0x3',
difference: '0x2',
isDecrease: false,
},
],
},
};

const GAS_FEE_TOKEN_MOCK: GasFeeToken = {
amount: '0x1',
balance: '0x2',
decimals: 18,
gas: '0x3',
maxFeePerGas: '0x4',
maxPriorityFeePerGas: '0x5',
rateWei: '0x6',
recipient: '0x7',
symbol: 'ETH',
tokenAddress: '0x8',
};

const GAS_FEE_ESTIMATES_MOCK: GasFeeFlowResponse = {
Expand Down Expand Up @@ -1990,7 +2007,9 @@ describe('TransactionController', () => {

describe('updates simulation data', () => {
it('by default', async () => {
getSimulationDataMock.mockResolvedValueOnce(SIMULATION_DATA_MOCK);
getSimulationDataMock.mockResolvedValueOnce(
SIMULATION_DATA_RESULT_MOCK,
);

const { controller } = setupController();

Expand Down Expand Up @@ -2021,12 +2040,14 @@ describe('TransactionController', () => {
);

expect(controller.state.transactions[0].simulationData).toStrictEqual(
SIMULATION_DATA_MOCK,
SIMULATION_DATA_RESULT_MOCK.simulationData,
);
});

it('with error if simulation disabled', async () => {
getSimulationDataMock.mockResolvedValueOnce(SIMULATION_DATA_MOCK);
getSimulationDataMock.mockResolvedValueOnce(
SIMULATION_DATA_RESULT_MOCK,
);

const { controller } = setupController({
options: { isSimulationEnabled: () => false },
Expand All @@ -2053,7 +2074,9 @@ describe('TransactionController', () => {
});

it('unless approval not required', async () => {
getSimulationDataMock.mockResolvedValueOnce(SIMULATION_DATA_MOCK);
getSimulationDataMock.mockResolvedValueOnce(
SIMULATION_DATA_RESULT_MOCK,
);

const { controller } = setupController();

Expand All @@ -2070,6 +2093,57 @@ describe('TransactionController', () => {
});
});

describe('updates gas fee tokens', () => {
it('by default', async () => {
getSimulationDataMock.mockResolvedValueOnce({
gasFeeTokens: [GAS_FEE_TOKEN_MOCK],
simulationData: {
tokenBalanceChanges: [],
},
});

const { controller } = setupController();

await controller.addTransaction(
{
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
},
{
networkClientId: NETWORK_CLIENT_ID_MOCK,
},
);

await flushPromises();

expect(controller.state.transactions[0].gasFeeTokens).toStrictEqual([
GAS_FEE_TOKEN_MOCK,
]);
});

it('unless approval not required', async () => {
getSimulationDataMock.mockResolvedValueOnce({
gasFeeTokens: [GAS_FEE_TOKEN_MOCK],
simulationData: {
tokenBalanceChanges: [],
},
});

const { controller } = setupController();

await controller.addTransaction(
{
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
},
{ requireApproval: false, networkClientId: NETWORK_CLIENT_ID_MOCK },
);

expect(getSimulationDataMock).toHaveBeenCalledTimes(0);
expect(controller.state.transactions[0].gasFeeTokens).toBeUndefined();
});
});

describe('on approve', () => {
it('submits transaction', async () => {
const { controller, messenger } = setupController({
Expand Down Expand Up @@ -6575,4 +6649,59 @@ describe('TransactionController', () => {
);
});
});

describe('updateSelectedGasFeeToken', () => {
it('updates selected gas fee token in state', () => {
const { controller } = setupController({
options: {
state: {
transactions: [
{
...TRANSACTION_META_MOCK,
gasFeeTokens: [GAS_FEE_TOKEN_MOCK],
},
],
},
},
});

controller.updateSelectedGasFeeToken(
TRANSACTION_META_MOCK.id,
GAS_FEE_TOKEN_MOCK.tokenAddress,
);

expect(controller.state.transactions[0].selectedGasFeeToken).toBe(
GAS_FEE_TOKEN_MOCK.tokenAddress,
);
});

it('throws if transaction does not exist', () => {
const { controller } = setupController();

expect(() =>
controller.updateSelectedGasFeeToken(
TRANSACTION_META_MOCK.id,
GAS_FEE_TOKEN_MOCK.tokenAddress,
),
).toThrow(
`Cannot update transaction as ID not found - ${TRANSACTION_META_MOCK.id}`,
);
});

it('throws if no matching gas fee token', () => {
const { controller } = setupController({
options: {
state: {
transactions: [
{ ...TRANSACTION_META_MOCK, gasFeeTokens: [GAS_FEE_TOKEN_MOCK] },
],
},
},
});

expect(() =>
controller.updateSelectedGasFeeToken(TRANSACTION_META_MOCK.id, '0x123'),
).toThrow('No matching gas fee token found');
});
});
});
35 changes: 34 additions & 1 deletion packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import type {
BatchTransactionParams,
PublishHook,
PublishBatchHook,
GasFeeToken,
} from './types';
import {
TransactionEnvelopeType,
Expand Down Expand Up @@ -2509,6 +2510,32 @@ export class TransactionController extends BaseController<
);
}

/**
* Update the selected gas fee token for a transaction.
*
* @param transactionId - The ID of the transaction to update.
* @param contractAddress - The contract address of the selected gas fee token.
*/
updateSelectedGasFeeToken(
transactionId: string,
contractAddress: Hex | undefined,
) {
this.#updateTransactionInternal({ transactionId }, (transactionMeta) => {
const hasMatchingGasFeeToken = transactionMeta.gasFeeTokens?.some(
(token) =>
token.tokenAddress.toLowerCase() === contractAddress?.toLowerCase(),
);

if (contractAddress && !hasMatchingGasFeeToken) {
throw new Error(
`No matching gas fee token found with address - ${contractAddress}`,
);
}

transactionMeta.selectedGasFeeToken = contractAddress;
});
}

private addMetadata(transactionMeta: TransactionMeta) {
validateTxParams(transactionMeta.txParams);
this.update((state) => {
Expand Down Expand Up @@ -3906,8 +3933,10 @@ export class TransactionController extends BaseController<
tokenBalanceChanges: [],
};

let gasFeeTokens: GasFeeToken[] = [];

if (this.#isSimulationEnabled()) {
simulationData = await this.#trace(
const result = await this.#trace(
{ name: 'Simulate', parentContext: traceContext },
() =>
getSimulationData(
Expand All @@ -3924,6 +3953,9 @@ export class TransactionController extends BaseController<
),
);

gasFeeTokens = result?.gasFeeTokens;
simulationData = result?.simulationData;

if (
blockTime &&
prevSimulationData &&
Expand Down Expand Up @@ -3956,6 +3988,7 @@ export class TransactionController extends BaseController<
skipResimulateCheck: Boolean(blockTime),
},
(txMeta) => {
txMeta.gasFeeTokens = gasFeeTokens;
txMeta.simulationData = simulationData;
},
);
Expand Down
1 change: 1 addition & 0 deletions packages/transaction-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type {
FeeMarketGasFeeEstimateForLevel,
FeeMarketGasFeeEstimates,
GasFeeEstimates,
GasFeeToken,
GasPriceGasFeeEstimates,
GasPriceValue,
InferTransactionTypeResult,
Expand Down
Loading
Loading