From 1c31b91d1412a2900e897d624bc5e3968d195f91 Mon Sep 17 00:00:00 2001 From: Edgars Date: Mon, 27 Apr 2026 21:39:03 +0100 Subject: [PATCH] fix(transactions): expose tx identifier under both `hash` and `txId` The same on-chain identifier was being exposed under different field names depending on the chain decoder: - decodeLocalnetTransaction (Studio) only set `hash` - decodeTransaction (testnet) only set `txId` This broke any consumer that didn't know which one to read. Both decoders now populate both fields. `txId` matches the on-chain Solidity struct (source of truth); `hash` is preserved for back-compat with the legacy localnet RPC shape. --- src/transactions/decoders.ts | 11 +++++++++++ src/types/transactions.ts | 4 +++- tests/transactions.test.ts | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/transactions/decoders.ts b/src/transactions/decoders.ts index d112a7c..ccc635b 100644 --- a/src/transactions/decoders.ts +++ b/src/transactions/decoders.ts @@ -85,6 +85,12 @@ export const decodeTransaction = (tx: GenLayerRawTransaction): GenLayerTransacti txData: txData, txDataDecoded: txDataDecoded, + // Same identifier exposed under both names; the on-chain Solidity struct + // uses `txId`, the legacy localnet RPC used `hash`. Populate both so + // consumers can rely on either. + hash: tx.txId, + txId: tx.txId, + currentTimestamp: tx.currentTimestamp.toString(), numOfInitialValidators: numOfInitialValidators?.toString() ?? "0", txSlot: tx.txSlot.toString(), @@ -241,6 +247,11 @@ export const simplifyTransactionReceipt = (tx: GenLayerTransaction): GenLayerTra }; export const decodeLocalnetTransaction = (tx: GenLayerTransaction): GenLayerTransaction => { + // Mirror the testnet decoder: expose the identifier under both `hash` + // (legacy localnet name) and `txId` (on-chain Solidity name). + if (tx.hash && !tx.txId) tx.txId = tx.hash; + else if (tx.txId && !tx.hash) tx.hash = tx.txId; + if (!tx.data) return tx; try { const leaderReceipt = tx.consensus_data?.leader_receipt; diff --git a/src/types/transactions.ts b/src/types/transactions.ts index db3cb4a..dda0cf4 100644 --- a/src/types/transactions.ts +++ b/src/types/transactions.ts @@ -255,7 +255,9 @@ export type GenLayerTransaction = { status?: TransactionStatus | number; statusName?: TransactionStatus; - // hash: localnet // txId: testnet// hash: localnet // txId: testnet + // The same identifier is exposed under both names: `txId` matches the + // on-chain Solidity struct field; `hash` is kept for back-compat with the + // legacy localnet RPC. Decoders populate both, so consumers can use either. hash?: TransactionHash; txId?: TransactionHash; diff --git a/tests/transactions.test.ts b/tests/transactions.test.ts index fd1e82b..5f018fb 100644 --- a/tests/transactions.test.ts +++ b/tests/transactions.test.ts @@ -322,6 +322,12 @@ describe("decodeTransaction", () => { const names = (decoded.lastRound as any)?.validatorVotesName; expect(names).toEqual(["AGREE", "AGREE", "AGREE"]); }); + + it("should expose the identifier under both `txId` and `hash`", () => { + const decoded = decodeTransaction(makeRawTx()); + expect(decoded.txId).toBe("0x" + "ff".repeat(32)); + expect(decoded.hash).toBe(decoded.txId); + }); }); describe("simplifyTransactionReceipt", () => {