Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **BREAKING:** Wire `TransactionController` into the default wallet initialization ([#8975](https://github.com/MetaMask/core/pull/8975))
- **BREAKING:** Wire `ApprovalController` into the default wallet initialization ([#8953](https://github.com/MetaMask/core/pull/8953))
- The default `Wallet` now constructs an `ApprovalController` and registers its `ApprovalController:*` messenger actions. Consumers that pass their own `messenger` and already wire an `ApprovalController` must remove their own before upgrading, or the duplicate registration will collide.
- Adds an `approvalController` slot to `instanceOptions` with `showApprovalRequest` (the callback that surfaces pending approval requests to the user; defaults to a no-op) and `typesExcludedFromRateLimiting` (the approval types exempt from per-origin rate limiting; defaults to a baseline of EVM approval types). Both let consumers (extension, mobile, wallet-cli) inject their platform-specific values.
Expand Down
14 changes: 14 additions & 0 deletions packages/wallet/src/Wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ import * as initializationModule from './initialization/initialization';
import { importSecretRecoveryPhrase } from './utilities';
import { Wallet } from './Wallet';

jest.mock('@metamask/transaction-controller', () => ({
TransactionController: jest
.fn()
.mockImplementation(function (this: { state: unknown }) {
this.state = {
methodData: {},
transactions: [],
transactionBatches: [],
lastFetchedBlockNumbers: {},
submitHistory: [],
};
}),
}));

const TEST_SRP = 'test test test test test test test test test test test ball';
const TEST_PASSWORD = 'testpass';

Expand Down
1 change: 1 addition & 0 deletions packages/wallet/src/initialization/instances/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { approvalController } from './approval-controller/approval-controller';
export { keyringController } from './keyring-controller/keyring-controller';
export { storageService } from './storage-service/storage-service';
export { transactionController } from './transaction-controller/transaction-controller';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const TRANSACTION_CONTROLLER_EXTERNAL_ACTIONS = [
'AccountsController:getSelectedAccount',
'AccountsController:getState',
'ApprovalController:addRequest',
'GasFeeController:fetchGasFeeEstimates',
'KeyringController:getState',
'KeyringController:signEip7702Authorization',
'KeyringController:signTransaction',
'NetworkController:findNetworkClientIdByChainId',
'NetworkController:getEIP1559Compatibility',
'NetworkController:getNetworkClientById',
'NetworkController:getNetworkClientRegistry',
'NetworkController:getState',
'RemoteFeatureFlagController:getState',
] as const;

export const TRANSACTION_CONTROLLER_EXTERNAL_EVENTS = [
'AccountActivityService:statusChanged',
'AccountActivityService:transactionUpdated',
'AccountsController:selectedAccountChange',
'BackendWebSocketService:connectionStateChanged',
'NetworkController:stateChange',
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { TransactionController } from '@metamask/transaction-controller';

import { defaultConfigurations } from '../../defaults';
import { transactionController } from './transaction-controller';
import type { TransactionControllerInstanceOptions } from './types';

const MOCK_STATE = {
methodData: {},
transactions: [],
transactionBatches: [],
lastFetchedBlockNumbers: {},
submitHistory: [],
};

jest.mock('@metamask/transaction-controller', () => ({
TransactionController: jest.fn(),
}));

function buildOptions(
overrides: Partial<TransactionControllerInstanceOptions> = {},
): TransactionControllerInstanceOptions {
return {
disableSwaps: false,
hooks: {},
isFirstTimeInteractionEnabled: () => false,
isSimulationEnabled: () => false,
...overrides,
};
}

describe('transactionController', () => {
it('is registered as a default initialization configuration', () => {
expect(Object.values(defaultConfigurations)).toContain(
transactionController,
);
});

it('initializes a TransactionController with the provided state', () => {
jest.mocked(TransactionController).mockImplementation(function (this: {
state: unknown;
}) {
this.state = MOCK_STATE;
} as never);

transactionController.init({
state: MOCK_STATE,
// @ts-expect-error Messenger not needed for this assertion.
messenger: {},
options: buildOptions(),
});

expect(TransactionController).toHaveBeenCalledWith(
expect.objectContaining({ state: MOCK_STATE }),
);
});

it('disables incoming transactions', () => {
jest.mocked(TransactionController).mockImplementation(function (this: {
state: unknown;
}) {
this.state = MOCK_STATE;
} as never);

transactionController.init({
state: undefined,
// @ts-expect-error Messenger not needed for this assertion.
messenger: {},
options: buildOptions(),
});

const opts = (
TransactionController as jest.MockedClass<typeof TransactionController>
).mock.calls[0][0];

expect(opts.incomingTransactions?.isEnabled?.()).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Messenger } from '@metamask/messenger';
import type { MessengerActions, MessengerEvents } from '@metamask/messenger';
import type { TransactionControllerMessenger } from '@metamask/transaction-controller';
import { TransactionController } from '@metamask/transaction-controller';

import type { InitializationConfiguration } from '../../types';
import {
TRANSACTION_CONTROLLER_EXTERNAL_ACTIONS,
TRANSACTION_CONTROLLER_EXTERNAL_EVENTS,
} from './constants';

export type { TransactionControllerInstanceOptions } from './types';

export const transactionController: InitializationConfiguration<
TransactionController,
TransactionControllerMessenger
> = {
name: 'TransactionController',
init: ({ state, messenger, options }) =>
new TransactionController({
...options,
incomingTransactions: { isEnabled: () => false },
messenger,
state,
}),
getMessenger: (parent) => {
const messenger = new Messenger<
'TransactionController',
MessengerActions<TransactionControllerMessenger>,
MessengerEvents<TransactionControllerMessenger>,
typeof parent
>({
namespace: 'TransactionController',
parent,
});

parent.delegate({
messenger,
actions: [...TRANSACTION_CONTROLLER_EXTERNAL_ACTIONS],
events: [...TRANSACTION_CONTROLLER_EXTERNAL_EVENTS],
});

return messenger;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { TransactionControllerOptions } from '@metamask/transaction-controller';

export type TransactionControllerInstanceOptions = Omit<
TransactionControllerOptions,
'incomingTransactions' | 'messenger' | 'state'
>;
2 changes: 2 additions & 0 deletions packages/wallet/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
import type { ApprovalControllerInstanceOptions } from './initialization/instances/approval-controller/types';
import type { KeyringControllerInstanceOptions } from './initialization/instances/keyring-controller/types';
import type { StorageServiceInstanceOptions } from './initialization/instances/storage-service/types';
import type { TransactionControllerInstanceOptions } from './initialization/instances/transaction-controller/types';
import { InitializationConfiguration } from './initialization/types';

export type WalletOptions = {
Expand All @@ -24,4 +25,5 @@ export type InstanceSpecificOptions = {
approvalController?: ApprovalControllerInstanceOptions;
keyringController?: KeyringControllerInstanceOptions;
storageService: StorageServiceInstanceOptions;
transactionController?: TransactionControllerInstanceOptions;
};
Loading