Skip to content
Open
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
6 changes: 6 additions & 0 deletions packages/smart-transactions-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392))

### Fixed

- Fail the associated regular transaction via the new `TransactionController:failTransaction` action instead of `TransactionController:updateTransaction` when a smart transaction is cancelled ([#9400](https://github.com/MetaMask/core/pull/9400))
- `updateTransaction` only patches state and does not emit transaction lifecycle events, so consumers that react to `transactionFailed`/`transactionStatusUpdated` (e.g. the bridge status controller and metrics) were never notified, leaving cancelled smart transactions — such as bridges — stuck as pending indefinitely.
- This requires consumers to grant the smart transactions controller messenger access to the `TransactionController:failTransaction` action (previously `TransactionController:updateTransaction`).

## [24.2.4]

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
TransactionType,
} from '@metamask/transaction-controller';
import type {
TransactionControllerFailTransactionAction,
TransactionControllerGetNonceLockAction,
TransactionControllerGetTransactionsAction,
TransactionControllerUpdateTransactionAction,
TransactionParams,
} from '@metamask/transaction-controller';
import type { Hex } from '@metamask/utils';
Expand Down Expand Up @@ -362,7 +362,7 @@ describe('SmartTransactionsController', () => {
jest.fn(),
);
rootMessenger.registerActionHandler(
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
jest.fn(),
);
rootMessenger.registerActionHandler(
Expand Down Expand Up @@ -391,7 +391,7 @@ describe('SmartTransactionsController', () => {
'RemoteFeatureFlagController:getState',
'TransactionController:getNonceLock',
'TransactionController:getTransactions',
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
],
events: [
'NetworkController:stateChange',
Expand Down Expand Up @@ -464,7 +464,7 @@ describe('SmartTransactionsController', () => {
jest.fn(),
);
rootMessenger.registerActionHandler(
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
jest.fn(),
);
rootMessenger.registerActionHandler(
Expand Down Expand Up @@ -495,7 +495,7 @@ describe('SmartTransactionsController', () => {
'RemoteFeatureFlagController:getState',
'TransactionController:getNonceLock',
'TransactionController:getTransactions',
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
],
events: [
'NetworkController:stateChange',
Expand Down Expand Up @@ -564,7 +564,7 @@ describe('SmartTransactionsController', () => {
jest.fn(),
);
rootMessenger.registerActionHandler(
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
jest.fn(),
);
rootMessenger.registerActionHandler(
Expand Down Expand Up @@ -593,7 +593,7 @@ describe('SmartTransactionsController', () => {
'RemoteFeatureFlagController:getState',
'TransactionController:getNonceLock',
'TransactionController:getTransactions',
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
],
events: [
'NetworkController:stateChange',
Expand Down Expand Up @@ -673,7 +673,7 @@ describe('SmartTransactionsController', () => {
jest.fn(),
);
rootMessenger.registerActionHandler(
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
jest.fn(),
);
rootMessenger.registerActionHandler(
Expand All @@ -698,7 +698,7 @@ describe('SmartTransactionsController', () => {
'RemoteFeatureFlagController:getState',
'TransactionController:getNonceLock',
'TransactionController:getTransactions',
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
],
events: [
'NetworkController:stateChange',
Expand Down Expand Up @@ -777,7 +777,7 @@ describe('SmartTransactionsController', () => {
jest.fn(),
);
rootMessenger.registerActionHandler(
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
jest.fn(),
);
rootMessenger.registerActionHandler(
Expand Down Expand Up @@ -811,7 +811,7 @@ describe('SmartTransactionsController', () => {
'RemoteFeatureFlagController:getState',
'TransactionController:getNonceLock',
'TransactionController:getTransactions',
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
],
events: [
'NetworkController:stateChange',
Expand Down Expand Up @@ -1833,8 +1833,8 @@ describe('SmartTransactionsController', () => {
);
});

it('calls updateTransaction when smart transaction is cancelled and returnTxHashAsap is true', async () => {
const mockUpdateTransaction = jest.fn();
it('fails the regular transaction when smart transaction is cancelled and returnTxHashAsap is true', async () => {
const mockFailTransaction = jest.fn();
const defaultState = getDefaultSmartTransactionsControllerState();
const pendingStx = createStateAfterPending();
await withController(
Expand All @@ -1860,7 +1860,7 @@ describe('SmartTransactionsController', () => {
},
},
},
updateTransaction: mockUpdateTransaction,
failTransaction: mockFailTransaction,
getTransactions: () => [
{
id: 'test-tx-id',
Expand All @@ -1883,29 +1883,19 @@ describe('SmartTransactionsController', () => {

controller.updateSmartTransaction(smartTransaction);

expect(mockUpdateTransaction).toHaveBeenCalledWith(
{
id: 'test-tx-id',
status: TransactionStatus.failed,
chainId: '0x1',
time: 123,
txParams: {
from: '0x123',
},
networkClientId: NetworkType.mainnet,
error: {
message: 'Smart transaction failed with status: cancelled',
name: 'SmartTransactionFailed',
},
},
'Smart transaction status: cancelled',
expect(mockFailTransaction).toHaveBeenCalledWith(
'test-tx-id',
expect.objectContaining({
name: 'SmartTransactionFailed',
message: 'Smart transaction failed with status: cancelled',
}),
);
},
);
});

it('does not call updateTransaction when smart transaction is cancelled but returnTxHashAsap is false', async () => {
const mockUpdateTransaction = jest.fn();
it('does not fail the transaction when smart transaction is cancelled but returnTxHashAsap is false', async () => {
const mockFailTransaction = jest.fn();
await withController(
{
remoteFeatureFlags: {
Expand All @@ -1915,7 +1905,7 @@ describe('SmartTransactionsController', () => {
},
},
},
updateTransaction: mockUpdateTransaction,
failTransaction: mockFailTransaction,
getTransactions: () => [
{
id: 'test-tx-id',
Expand All @@ -1938,13 +1928,13 @@ describe('SmartTransactionsController', () => {

controller.updateSmartTransaction(smartTransaction);

expect(mockUpdateTransaction).not.toHaveBeenCalled();
expect(mockFailTransaction).not.toHaveBeenCalled();
},
);
});

it('does not call updateTransaction when transaction is not found in regular transactions', async () => {
const mockUpdateTransaction = jest.fn();
it('does not fail the transaction when transaction is not found in regular transactions', async () => {
const mockFailTransaction = jest.fn();

await withController(
{
Expand All @@ -1955,7 +1945,7 @@ describe('SmartTransactionsController', () => {
},
},
},
updateTransaction: mockUpdateTransaction,
failTransaction: mockFailTransaction,
getTransactions: () => [],
},
async ({ controller }) => {
Expand All @@ -1967,13 +1957,13 @@ describe('SmartTransactionsController', () => {

controller.updateSmartTransaction(smartTransaction);

expect(mockUpdateTransaction).not.toHaveBeenCalled();
expect(mockFailTransaction).not.toHaveBeenCalled();
},
);
});

it('does not call updateTransaction for non-cancelled transactions', async () => {
const mockUpdateTransaction = jest.fn();
it('does not fail the transaction for non-cancelled transactions', async () => {
const mockFailTransaction = jest.fn();
await withController(
{
remoteFeatureFlags: {
Expand All @@ -1983,7 +1973,7 @@ describe('SmartTransactionsController', () => {
},
},
},
updateTransaction: mockUpdateTransaction,
failTransaction: mockFailTransaction,
getTransactions: () => [
{
id: 'test-tx-id',
Expand All @@ -2006,7 +1996,7 @@ describe('SmartTransactionsController', () => {

controller.updateSmartTransaction(smartTransaction);

expect(mockUpdateTransaction).not.toHaveBeenCalled();
expect(mockFailTransaction).not.toHaveBeenCalled();
},
);
});
Expand Down Expand Up @@ -3184,7 +3174,7 @@ type WithControllerOptions = {
>;
getNonceLock?: TransactionControllerGetNonceLockAction['handler'];
getTransactions?: TransactionControllerGetTransactionsAction['handler'];
updateTransaction?: TransactionControllerUpdateTransactionAction['handler'];
failTransaction?: TransactionControllerFailTransactionAction['handler'];
remoteFeatureFlags?: {
smartTransactionsNetworks?: Record<string, unknown>;
};
Expand Down Expand Up @@ -3215,7 +3205,7 @@ async function withController<ReturnValue>(
releaseLock: jest.fn(),
}),
getTransactions = jest.fn(),
updateTransaction = jest.fn(),
failTransaction = jest.fn(),
remoteFeatureFlags = {},
bearerToken,
} = rest;
Expand Down Expand Up @@ -3294,8 +3284,8 @@ async function withController<ReturnValue>(
getTransactions,
);
rootMessenger.registerActionHandler(
'TransactionController:updateTransaction',
updateTransaction,
'TransactionController:failTransaction',
failTransaction,
);
rootMessenger.registerActionHandler(
'RemoteFeatureFlagController:getState',
Expand Down Expand Up @@ -3327,7 +3317,7 @@ async function withController<ReturnValue>(
'RemoteFeatureFlagController:getState',
'TransactionController:getNonceLock',
'TransactionController:getTransactions',
'TransactionController:updateTransaction',
'TransactionController:failTransaction',
],
events: [
'NetworkController:stateChange',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import type {
RemoteFeatureFlagControllerStateChangeEvent,
} from '@metamask/remote-feature-flag-controller';
import type {
TransactionControllerFailTransactionAction,
TransactionControllerGetNonceLockAction,
TransactionControllerGetTransactionsAction,
TransactionControllerUpdateTransactionAction,
TransactionMeta,
TransactionParams,
} from '@metamask/transaction-controller';
Expand Down Expand Up @@ -192,9 +192,9 @@ type AllowedActions =
| NetworkControllerGetNetworkClientByIdAction
| NetworkControllerGetStateAction
| RemoteFeatureFlagControllerGetStateAction
| TransactionControllerFailTransactionAction
| TransactionControllerGetNonceLockAction
| TransactionControllerGetTransactionsAction
| TransactionControllerUpdateTransactionAction;
| TransactionControllerGetTransactionsAction;

export type SmartTransactionsControllerStateChangeEvent =
ControllerStateChangeEvent<
Expand Down Expand Up @@ -692,11 +692,11 @@ export class SmartTransactionsController extends StaticIntervalPollingController
smartTransaction: nextSmartTransaction,
getRegularTransactions: () =>
this.messenger.call('TransactionController:getTransactions'),
updateTransaction: (transactionMeta: TransactionMeta, note: string) =>
failTransaction: (transactionId: string, error: Error) =>
this.messenger.call(
'TransactionController:updateTransaction',
transactionMeta,
note,
'TransactionController:failTransaction',
transactionId,
error,
),
});
}
Expand Down
Loading