From b26aac1e659cb60eb45dee7fac8ca899681d2743 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:17:20 +0000 Subject: [PATCH 1/3] fix(transactions): handle all DECIDED states in waitForTransactionReceipt - Add DECIDED_STATES constant with all terminal transaction states - Add isDecidedState utility function for status checking - Update waitForTransactionReceipt to accept any decided state when ACCEPTED is requested - Maintains backward compatibility while fixing DXP-667 Fixes: DXP-667 Co-Authored-By: daniel@genlayerlabs.com --- src/transactions/actions.ts | 3 ++- src/types/transactions.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/transactions/actions.ts b/src/transactions/actions.ts index b5593cf..3410112 100644 --- a/src/transactions/actions.ts +++ b/src/transactions/actions.ts @@ -5,6 +5,7 @@ import { GenLayerTransaction, GenLayerRawTransaction, transactionsStatusNameToNumber, + isDecidedState, } from "../types/transactions"; import {transactionsConfig} from "../config/transactions"; import {sleep} from "../utils/async"; @@ -41,7 +42,7 @@ export const receiptActions = (client: GenLayerClient, publicClie const requestedStatus = transactionsStatusNameToNumber[status]; if ( transactionStatusString === requestedStatus || - (status === TransactionStatus.ACCEPTED && transactionStatusString === transactionStatusFinalized) + (status === TransactionStatus.ACCEPTED && isDecidedState(transactionStatusString)) ) { let finalTransaction = transaction; if (client.chain.id === localnet.id) { diff --git a/src/types/transactions.ts b/src/types/transactions.ts index b0e84b3..173f2e3 100644 --- a/src/types/transactions.ts +++ b/src/types/transactions.ts @@ -71,6 +71,21 @@ export const transactionsStatusNameToNumber = { [TransactionStatus.LEADER_TIMEOUT]: "13", }; +export const DECIDED_STATES = [ + TransactionStatus.ACCEPTED, + TransactionStatus.UNDETERMINED, + TransactionStatus.LEADER_TIMEOUT, + TransactionStatus.VALIDATORS_TIMEOUT, + TransactionStatus.CANCELED, + TransactionStatus.FINALIZED +]; + +export function isDecidedState(status: string): boolean { + return DECIDED_STATES.some(state => + transactionsStatusNameToNumber[state] === status + ); +} + export const transactionResultNumberToName = { "0": TransactionResult.IDLE, "1": TransactionResult.AGREE, From d07219e13592bce9e6793a94e9741628a8efaa82 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:20:42 +0000 Subject: [PATCH 2/3] fix: remove unused transactionStatusFinalized variable Fixes ESLint error for unused variable after refactoring to use isDecidedState utility function. Co-Authored-By: daniel@genlayerlabs.com --- src/transactions/actions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/transactions/actions.ts b/src/transactions/actions.ts index 3410112..b6f225e 100644 --- a/src/transactions/actions.ts +++ b/src/transactions/actions.ts @@ -38,7 +38,6 @@ export const receiptActions = (client: GenLayerClient, publicClie throw new Error("Transaction not found"); } const transactionStatusString = String(transaction.status); - const transactionStatusFinalized = transactionsStatusNameToNumber[TransactionStatus.FINALIZED]; const requestedStatus = transactionsStatusNameToNumber[status]; if ( transactionStatusString === requestedStatus || From dcd169ddec5a4c86f982ef64425f91dc0d1f8064 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:55:19 +0000 Subject: [PATCH 3/3] test: add comprehensive tests for DECIDED_STATES functionality - Add tests for DECIDED_STATES constant and isDecidedState utility function - Test waitForTransactionReceipt accepts all decided states when ACCEPTED requested - Test backward compatibility for non-ACCEPTED status requests - Test edge cases with invalid statuses - Comprehensive test coverage for new decided states logic Co-Authored-By: daniel@genlayerlabs.com --- tests/transactions.test.ts | 142 +++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/transactions.test.ts diff --git a/tests/transactions.test.ts b/tests/transactions.test.ts new file mode 100644 index 0000000..d501547 --- /dev/null +++ b/tests/transactions.test.ts @@ -0,0 +1,142 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { TransactionStatus, DECIDED_STATES, isDecidedState } from "../src/types/transactions"; +import { receiptActions } from "../src/transactions/actions"; +import { localnet } from "../src/chains/localnet"; + +const mockFetch = vi.fn(); +vi.stubGlobal("fetch", mockFetch); + +describe("DECIDED_STATES constant", () => { + it("should contain all expected decided states", () => { + const expectedStates = [ + TransactionStatus.ACCEPTED, + TransactionStatus.UNDETERMINED, + TransactionStatus.LEADER_TIMEOUT, + TransactionStatus.VALIDATORS_TIMEOUT, + TransactionStatus.CANCELED, + TransactionStatus.FINALIZED + ]; + + expect(DECIDED_STATES).toEqual(expectedStates); + }); +}); + +describe("isDecidedState utility function", () => { + it("should return true for all decided states", () => { + const decidedStatusNumbers = ["5", "6", "13", "12", "8", "7"]; // ACCEPTED, UNDETERMINED, LEADER_TIMEOUT, VALIDATORS_TIMEOUT, CANCELED, FINALIZED + + decidedStatusNumbers.forEach(statusNum => { + expect(isDecidedState(statusNum)).toBe(true); + }); + }); + + it("should return false for non-decided states", () => { + const nonDecidedStatusNumbers = ["0", "1", "2", "3", "4", "9", "10", "11"]; // UNINITIALIZED, PENDING, PROPOSING, COMMITTING, REVEALING, APPEAL_REVEALING, APPEAL_COMMITTING, READY_TO_FINALIZE + + nonDecidedStatusNumbers.forEach(statusNum => { + expect(isDecidedState(statusNum)).toBe(false); + }); + }); + + it("should return false for invalid statuses", () => { + const invalidStatuses = ["999", "invalid", ""]; + + invalidStatuses.forEach(status => { + expect(isDecidedState(status)).toBe(false); + }); + }); +}); + +describe("waitForTransactionReceipt with DECIDED_STATES", () => { + beforeEach(() => { + mockFetch.mockReset(); + }); + + it("should accept all decided states when waiting for ACCEPTED", async () => { + const decidedStatusNumbers = ["5", "6", "13", "12", "8", "7"]; // All decided states + + for (const statusNum of decidedStatusNumbers) { + const mockTransaction = { + hash: "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6", + status: statusNum, + from_address: "0x123", + to_address: "0x456", + value: "0", + gaslimit: "1000000", + nonce: "1", + created_at: "2023-01-01T00:00:00Z", + }; + + const mockClient = { + chain: localnet, + getTransaction: vi.fn().mockResolvedValue(mockTransaction) + }; + + const mockPublicClient = {} as any; + + const actions = receiptActions(mockClient as any, mockPublicClient); + const result = await actions.waitForTransactionReceipt({ + hash: "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6" as any, + status: TransactionStatus.ACCEPTED, + }); + + expect(result).toEqual(mockTransaction); + } + }); + + it("should not affect waiting for specific non-ACCEPTED statuses", async () => { + const mockTransaction = { + hash: "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6", + status: "7", // FINALIZED + from_address: "0x123", + to_address: "0x456", + value: "0", + gaslimit: "1000000", + nonce: "1", + created_at: "2023-01-01T00:00:00Z", + }; + + const mockClient = { + chain: localnet, + getTransaction: vi.fn().mockResolvedValue(mockTransaction) + }; + + const mockPublicClient = {} as any; + + const actions = receiptActions(mockClient as any, mockPublicClient); + const result = await actions.waitForTransactionReceipt({ + hash: "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6" as any, + status: TransactionStatus.FINALIZED, + }); + + expect(result).toEqual(mockTransaction); + }); + + it("should maintain backward compatibility", async () => { + const mockTransaction = { + hash: "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6", + status: "5", // ACCEPTED + from_address: "0x123", + to_address: "0x456", + value: "0", + gaslimit: "1000000", + nonce: "1", + created_at: "2023-01-01T00:00:00Z", + }; + + const mockClient = { + chain: localnet, + getTransaction: vi.fn().mockResolvedValue(mockTransaction) + }; + + const mockPublicClient = {} as any; + + const actions = receiptActions(mockClient as any, mockPublicClient); + const result = await actions.waitForTransactionReceipt({ + hash: "0x4b8037744adab7ea8335b4f839979d20031d83a8ccdf706e0ae61312930335f6" as any, + status: TransactionStatus.ACCEPTED, + }); + + expect(result).toEqual(mockTransaction); + }); +});