From 17f09d3ab0e6eb6e1c9cc0b76a94ef97c42b63d2 Mon Sep 17 00:00:00 2001 From: dan437 <80175477+dan437@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:49:16 +0100 Subject: [PATCH] Add a "transactionId" param to an STX, accept new functions in a constructor, make clientId param mandatory --- src/SmartTransactionsController.test.ts | 2 + src/SmartTransactionsController.ts | 59 ++++++++++++++++++++----- src/index.test.ts | 4 ++ src/types.ts | 13 ++++++ src/utils.ts | 19 +++++++- 5 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/SmartTransactionsController.test.ts b/src/SmartTransactionsController.test.ts index 14f0f4a..d75dbc3 100644 --- a/src/SmartTransactionsController.test.ts +++ b/src/SmartTransactionsController.test.ts @@ -1829,6 +1829,8 @@ async function withController( deviceModel: 'ledger', }); }), + getFeatureFlags: jest.fn(), + updateTransaction: jest.fn(), ...options, }); diff --git a/src/SmartTransactionsController.ts b/src/SmartTransactionsController.ts index 1251d86..fdebaac 100644 --- a/src/SmartTransactionsController.ts +++ b/src/SmartTransactionsController.ts @@ -38,6 +38,8 @@ import type { UnsignedTransaction, GetTransactionsOptions, MetaMetricsProps, + FeatureFlags, + ClientId, } from './types'; import { APIType, SmartTransactionStatuses } from './types'; import { @@ -53,11 +55,11 @@ import { getTxHash, getSmartTransactionMetricsProperties, getSmartTransactionMetricsSensitiveProperties, + getReturnTxHashAsap, } from './utils'; const SECOND = 1000; export const DEFAULT_INTERVAL = SECOND * 5; -const DEFAULT_CLIENT_ID = 'default'; const ETH_QUERY_ERROR_MSG = '`ethQuery` is not defined on SmartTransactionsController'; @@ -178,7 +180,7 @@ export type SmartTransactionsControllerMessenger = type SmartTransactionsControllerOptions = { interval?: number; - clientId?: string; + clientId: ClientId; chainId?: Hex; supportedChainIds?: Hex[]; getNonceLock: TransactionController['getNonceLock']; @@ -198,6 +200,8 @@ type SmartTransactionsControllerOptions = { messenger: SmartTransactionsControllerMessenger; getTransactions: (options?: GetTransactionsOptions) => TransactionMeta[]; getMetaMetricsProps: () => Promise; + getFeatureFlags: () => FeatureFlags; + updateTransaction: (transaction: TransactionMeta, note: string) => void; }; export type SmartTransactionsControllerPollingInput = { @@ -211,7 +215,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo > { #interval: number; - #clientId: string; + #clientId: ClientId; #chainId: Hex; @@ -233,6 +237,10 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo readonly #getMetaMetricsProps: () => Promise; + #getFeatureFlags: SmartTransactionsControllerOptions['getFeatureFlags']; + + #updateTransaction: SmartTransactionsControllerOptions['updateTransaction']; + /* istanbul ignore next */ async #fetch(request: string, options?: RequestInit) { const fetchOptions = { @@ -248,7 +256,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo constructor({ interval = DEFAULT_INTERVAL, - clientId = DEFAULT_CLIENT_ID, + clientId, chainId: InitialChainId = ChainId.mainnet, supportedChainIds = [ChainId.mainnet, ChainId.sepolia], getNonceLock, @@ -258,6 +266,8 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo messenger, getTransactions, getMetaMetricsProps, + getFeatureFlags, + updateTransaction, }: SmartTransactionsControllerOptions) { super({ name: controllerName, @@ -279,6 +289,8 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo this.#getRegularTransactions = getTransactions; this.#trackMetaMetricsEvent = trackMetaMetricsEvent; this.#getMetaMetricsProps = getMetaMetricsProps; + this.#getFeatureFlags = getFeatureFlags; + this.#updateTransaction = updateTransaction; this.initializeSmartTransactionsForChainId(); @@ -519,6 +531,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo cancelledNonceIndex > -1 ? currentSmartTransactions .slice(0, cancelledNonceIndex) + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands .concat(currentSmartTransactions.slice(cancelledNonceIndex + 1)) .concat(historifiedSmartTransaction) : currentSmartTransactions.concat(historifiedSmartTransaction); @@ -530,24 +543,47 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo return; } + const currentSmartTransaction = currentSmartTransactions[currentIndex]; + const nextSmartTransaction = { + ...currentSmartTransaction, + ...smartTransaction, + }; + // We have to emit this event here, because then a txHash is returned to the TransactionController once it's available // and the #doesTransactionNeedConfirmation function will work properly, since it will find the txHash in the regular transactions list. this.messagingSystem.publish( `SmartTransactionsController:smartTransaction`, - smartTransaction, + nextSmartTransaction, ); + if (nextSmartTransaction.status === SmartTransactionStatuses.CANCELLED) { + const returnTxHashAsap = getReturnTxHashAsap( + this.#clientId, + this.#getFeatureFlags()?.smartTransactions, + ); + if (returnTxHashAsap && nextSmartTransaction.transactionId) { + const foundTransaction = this.#getRegularTransactions().find( + (transaction) => + transaction.id === nextSmartTransaction.transactionId, + ); + if (foundTransaction) { + const updatedTransaction = { + ...foundTransaction, + status: TransactionStatus.failed, + }; + this.#updateTransaction( + updatedTransaction as TransactionMeta, + 'Smart transaction cancelled', + ); + } + } + } + if ( (smartTransaction.status === SmartTransactionStatuses.SUCCESS || smartTransaction.status === SmartTransactionStatuses.REVERTED) && !smartTransaction.confirmed ) { - // confirm smart transaction - const currentSmartTransaction = currentSmartTransactions[currentIndex]; - const nextSmartTransaction = { - ...currentSmartTransaction, - ...smartTransaction, - }; await this.#confirmSmartTransaction(nextSmartTransaction, { chainId, ethQuery, @@ -892,6 +928,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo txHash: submitTransactionResponse.txHash, cancellable: true, type: transactionMeta?.type ?? 'swap', + transactionId: transactionMeta?.id, }, { chainId, ethQuery }, ); diff --git a/src/index.test.ts b/src/index.test.ts index 3490185..d382bb3 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -8,6 +8,7 @@ import SmartTransactionsController, { type AllowedActions, type AllowedEvents, } from './SmartTransactionsController'; +import { ClientId } from './types'; describe('default export', () => { it('exports SmartTransactionsController', () => { @@ -30,6 +31,9 @@ describe('default export', () => { getMetaMetricsProps: jest.fn(async () => { return Promise.resolve({}); }), + getFeatureFlags: jest.fn(), + updateTransaction: jest.fn(), + clientId: ClientId.Extension, }); expect(controller).toBeInstanceOf(SmartTransactionsController); jest.clearAllTimers(); diff --git a/src/types.ts b/src/types.ts index d8b774e..10f124d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,6 +44,11 @@ export enum SmartTransactionStatuses { RESOLVED = 'resolved', } +export enum ClientId { + Mobile = 'mobile', + Extension = 'extension', +} + export const cancellationReasonToStatusMap = { [SmartTransactionCancellationReason.WOULD_REVERT]: SmartTransactionStatuses.CANCELLED_WOULD_REVERT, @@ -97,6 +102,7 @@ export type SmartTransaction = { accountHardwareType?: string; accountType?: string; deviceModel?: string; + transactionId?: string; // It's an ID for a regular transaction from the TransactionController. }; export type Fee = { @@ -140,3 +146,10 @@ export type MetaMetricsProps = { accountType?: string; deviceModel?: string; }; + +export type FeatureFlags = { + smartTransactions?: { + mobileReturnTxHashAsap?: boolean; + extensionReturnTxHashAsap?: boolean; + }; +}; diff --git a/src/utils.ts b/src/utils.ts index ea24fb1..e5007b5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,13 +7,18 @@ import _ from 'lodash'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment import { API_BASE_URL, SENTINEL_API_BASE_URL_MAP } from './constants'; -import type { SmartTransaction, SmartTransactionsStatus } from './types'; +import type { + SmartTransaction, + SmartTransactionsStatus, + FeatureFlags, +} from './types'; import { APIType, SmartTransactionStatuses, SmartTransactionCancellationReason, SmartTransactionMinedTx, cancellationReasonToStatusMap, + ClientId, } from './types'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -263,3 +268,15 @@ export const getSmartTransactionMetricsSensitiveProperties = ( device_model: smartTransaction.deviceModel, }; }; + +export const getReturnTxHashAsap = ( + clientId: ClientId, + smartTransactionsFeatureFlags: FeatureFlags['smartTransactions'], +) => { + if (!clientId) { + return false; + } + return clientId === ClientId.Extension + ? smartTransactionsFeatureFlags?.extensionReturnTxHashAsap + : smartTransactionsFeatureFlags?.mobileReturnTxHashAsap; +};