diff --git a/src/index.ts b/src/index.ts index fd6a994..332ff7d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,7 @@ export { SmartTransactionMinedTx, SmartTransactionCancellationReason, SmartTransactionStatuses, + OriginalTransactionStatus, ClientId, Feature, Kind, diff --git a/src/types.ts b/src/types.ts index 43081b5..95abbfe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -76,6 +76,29 @@ export enum ClientId { Extension = 'extension', } +export enum OriginalTransactionStatus { + PENDING = 'PENDING', + PENDING_CANCELLED = 'PENDING_CANCELLED', + + VALIDATED = 'VALIDATED', + REVERTED = 'REVERTED', + NONCE_TOO_LOW = 'NONCE_TOO_LOW', + CANCELLED = 'CANCELLED', + + FAILED = 'FAILED', + FAILED_WOULD_REVERT = 'FAILED_WOULD_REVERT', + FAILED_INSUFFICIENT_FUNDS = 'FAILED_INSUFFICIENT_FUNDS', + FAILED_UNKNOWN = 'FAILED_UNKNOWN', + FAILED_TIMEOUT = 'FAILED_TIMEOUT', + FAILED_GAS_TOO_LOW = 'FAILED_GAS_TOO_LOW', + FAILED_NONCE_TOO_HIGH = 'FAILED_NONCE_TOO_HIGH', + + LEAKED_VALIDATED = 'LEAKED_VALIDATED', + LEAKED_REVERTED = 'LEAKED_REVERTED', + CANCELLED_LEAKED_VALIDATED = 'CANCELLED_LEAKED_VALIDATED', + CANCELLED_LEAKED_REVERTED = 'CANCELLED_LEAKED_REVERTED', +} + export const cancellationReasonToStatusMap = { [SmartTransactionCancellationReason.WOULD_REVERT]: SmartTransactionStatuses.CANCELLED, @@ -93,6 +116,7 @@ export type SmartTransactionsStatus = { error?: string; cancellationFeeWei: number; cancellationReason?: SmartTransactionCancellationReason; + originalTransactionStatus?: OriginalTransactionStatus; deadlineRatio: number; minedHash: string; minedTx: SmartTransactionMinedTx; diff --git a/src/utils.test.ts b/src/utils.test.ts index 04db634..8ad50b2 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -13,6 +13,7 @@ import { APIType, SmartTransactionStatuses, SmartTransactionCancellationReason, + OriginalTransactionStatus, ClientId, } from './types'; import * as utils from './utils'; @@ -578,6 +579,41 @@ describe('src/utils.js', () => { ); }); + it('includes originalTransactionStatus in error message when available', () => { + const updateTransactionMock = jest.fn(); + const smartTransaction = { + ...createSmartTransaction(SmartTransactionStatuses.CANCELLED), + statusMetadata: { + cancellationFeeWei: 0, + deadlineRatio: 0, + minedHash: '', + minedTx: SmartTransactionMinedTx.NOT_MINED, + isSettled: false, + originalTransactionStatus: + OriginalTransactionStatus.FAILED_WOULD_REVERT, + }, + }; + + utils.markRegularTransactionsAsFailed({ + smartTransaction, + getRegularTransactions: () => [mockTransaction], + updateTransaction: updateTransactionMock, + }); + + expect(updateTransactionMock).toHaveBeenCalledWith( + { + ...mockTransaction, + status: TransactionStatus.failed, + error: { + name: 'SmartTransactionFailed', + message: + 'Smart transaction failed with status: cancelled, originalTransactionStatus: FAILED_WOULD_REVERT', + }, + }, + 'Smart transaction status: cancelled', + ); + }); + it('throws error if original transaction cannot be found', () => { const updateTransactionMock = jest.fn(); const getRegularTransactionsMock = jest.fn(() => []); @@ -665,4 +701,57 @@ describe('src/utils.js', () => { ); }); }); + + describe('getSmartTransactionMetricsProperties', () => { + it('includes stx_original_transaction_status from statusMetadata', () => { + const smartTransaction = { + uuid: 'test-uuid', + status: SmartTransactionStatuses.SUCCESS, + time: Date.now(), + statusMetadata: { + cancellationFeeWei: 0, + deadlineRatio: 0.5, + minedHash: '0xabc', + minedTx: SmartTransactionMinedTx.SUCCESS, + isSettled: true, + duplicated: false, + timedOut: false, + proxied: false, + originalTransactionStatus: OriginalTransactionStatus.VALIDATED, + }, + }; + + const result = + utils.getSmartTransactionMetricsProperties(smartTransaction); + + expect(result).toStrictEqual( + expect.objectContaining({ + stx_original_transaction_status: OriginalTransactionStatus.VALIDATED, + stx_duplicated: false, + stx_timed_out: false, + stx_proxied: false, + }), + ); + }); + + it('returns undefined for stx_original_transaction_status when not in statusMetadata', () => { + const smartTransaction = { + uuid: 'test-uuid', + status: SmartTransactionStatuses.PENDING, + time: Date.now(), + statusMetadata: { + cancellationFeeWei: 0, + deadlineRatio: 0, + minedHash: '', + minedTx: SmartTransactionMinedTx.NOT_MINED, + isSettled: false, + }, + }; + + const result = + utils.getSmartTransactionMetricsProperties(smartTransaction); + + expect(result.stx_original_transaction_status).toBeUndefined(); + }); + }); }); diff --git a/src/utils.ts b/src/utils.ts index d1d1bb6..92b4aee 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -213,6 +213,8 @@ export const getSmartTransactionMetricsProperties = ( stx_duplicated: smartTransactionStatusMetadata?.duplicated, stx_timed_out: smartTransactionStatusMetadata?.timedOut, stx_proxied: smartTransactionStatusMetadata?.proxied, + stx_original_transaction_status: + smartTransactionStatusMetadata?.originalTransactionStatus, }; }; @@ -275,7 +277,8 @@ export const markRegularTransactionsAsFailed = ({ getRegularTransactions: TransactionControllerGetTransactionsAction['handler']; updateTransaction: TransactionControllerUpdateTransactionAction['handler']; }) => { - const { transactionId, status, txHashes } = smartTransaction; + const { transactionId, status, statusMetadata, txHashes } = smartTransaction; + const originalTransactionStatus = statusMetadata?.originalTransactionStatus; const transactionsToFail = getRegularTransactions().filter( (tx) => (tx.hash && txHashes?.includes(tx.hash)) || tx.id === transactionId, @@ -285,6 +288,10 @@ export const markRegularTransactionsAsFailed = ({ throw new Error('Cannot find regular transaction to mark it as failed'); } + const errorMessage = originalTransactionStatus + ? `Smart transaction failed with status: ${status}, originalTransactionStatus: ${originalTransactionStatus}` + : `Smart transaction failed with status: ${status}`; + for (const tx of transactionsToFail) { if (tx.status === TransactionStatus.failed) { continue; // Already marked as failed. @@ -294,7 +301,7 @@ export const markRegularTransactionsAsFailed = ({ status: TransactionStatus.failed, error: { name: 'SmartTransactionFailed', - message: `Smart transaction failed with status: ${status}`, + message: errorMessage, }, };