Skip to content

Commit ec43c5a

Browse files
chore: add timestamp to transaction event (#142)
1 parent 9e8ab8b commit ec43c5a

File tree

5 files changed

+54
-1
lines changed

5 files changed

+54
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ const txEvent = await transactionAction.getTransactionEvent({
335335
txId: '0xabcdef...'
336336
});
337337
console.log(`Method: ${txEvent.method}, Fee: ${txEvent.feeAmount} wei`);
338+
console.log(`Timestamp (ms): ${txEvent.stampMs}`);
338339

339340
// List fees paid by wallet
340341
const entries = await transactionAction.listTransactionFees({

docs/api-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ Retrieves detailed information about a specific transaction by its hash.
510510
- `Promise<TransactionEvent>` - Complete transaction details including:
511511
- `txId: string` - Transaction hash (0x-prefixed)
512512
- `blockHeight: number` - Block number where transaction was included
513+
- `stampMs: number` - Millisecond timestamp from the block header (0 when unavailable)
513514
- `method: string` - Method name (e.g., "deployStream", "insertRecords")
514515
- `caller: string` - Ethereum address of the caller (lowercase, 0x-prefixed)
515516
- `feeAmount: string` - Total fee amount as string (handles large numbers)
@@ -537,6 +538,7 @@ console.log(`Method: ${txEvent.method}`);
537538
console.log(`Caller: ${txEvent.caller}`);
538539
console.log(`Fee: ${txEvent.feeAmount} wei`);
539540
console.log(`Block: ${txEvent.blockHeight}`);
541+
console.log(`Timestamp: ${txEvent.stampMs}`);
540542

541543
// Check fee distributions
542544
for (const dist of txEvent.feeDistributions) {

src/contracts-api/transactionAction.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
1-
import { KwilSigner, NodeKwil, WebKwil, Types } from "@trufnetwork/kwil-js";
1+
import { KwilSigner, NodeKwil, WebKwil } from "@trufnetwork/kwil-js";
22
import { TransactionEvent, FeeDistribution, GetTransactionEventInput } from "../types/transaction";
33

4+
const INDEXER_BASE = "https://indexer.infra.truf.network";
5+
6+
function normalizeTransactionId(txId: string): string {
7+
const lower = txId.toLowerCase();
8+
return lower.startsWith("0x") ? lower : `0x${lower}`;
9+
}
10+
11+
async function fetchTransactionStampMs(blockHeight: number, txId: string): Promise<number> {
12+
const url = `${INDEXER_BASE}/v0/chain/transactions?from-block=${blockHeight}&to-block=${blockHeight}&order=desc`;
13+
try {
14+
const response = await fetch(url);
15+
if (!response.ok) {
16+
console.warn(`Indexer returned ${response.status} while fetching block ${blockHeight} for tx ${txId}`);
17+
return 0;
18+
}
19+
20+
const data = await response.json() as {
21+
ok: boolean;
22+
data: Array<{
23+
block_height: number;
24+
hash: string;
25+
stamp_ms: number | null;
26+
}>;
27+
};
28+
29+
if (!data.ok || !Array.isArray(data.data)) {
30+
console.warn(`Indexer payload malformed for block ${blockHeight} (tx ${txId})`);
31+
return 0;
32+
}
33+
34+
const normalizedTargetHash = normalizeTransactionId(txId);
35+
const tx = data.data.find(entry => normalizeTransactionId(entry.hash) === normalizedTargetHash);
36+
return tx?.stamp_ms ?? 0;
37+
} catch (error) {
38+
console.warn(`Failed to fetch stamp_ms for tx ${txId} at block ${blockHeight}`, error);
39+
return 0;
40+
}
41+
}
42+
443
/**
544
* Database row structure returned from get_transaction_event action
645
*/
@@ -134,9 +173,12 @@ export class TransactionAction {
134173
throw new Error(`Invalid block height: ${row.block_height} (tx: ${row.tx_id})`);
135174
}
136175

176+
const stampMs = await fetchTransactionStampMs(blockHeight, row.tx_id);
177+
137178
return {
138179
txId: row.tx_id,
139180
blockHeight,
181+
stampMs,
140182
method: row.method,
141183
caller: row.caller,
142184
feeAmount,

src/types/transaction.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export interface TransactionEvent {
4040
txId: string;
4141
/** Block height when transaction was included */
4242
blockHeight: number;
43+
/**
44+
* Millisecond timestamp from the block header via indexer lookup.
45+
* Will be 0 when the indexer is unavailable.
46+
*/
47+
stampMs: number;
4348
/** Method name (e.g., "deployStream", "insertRecords") */
4449
method: string;
4550
/** Ethereum address of caller (lowercase, 0x-prefixed) */

tests/integration/transactionAction.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ describe.sequential(
7070
expect(txEvent.method).toBe("deployStream");
7171
expect(txEvent.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
7272
expect(txEvent.blockHeight).toBeGreaterThan(0);
73+
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
7374
expect(txEvent.feeAmount).toBeDefined();
7475
// Test wallet has system:network_writer role (fee exempt)
7576
expect(txEvent.feeAmount).toBe("0");
@@ -121,6 +122,7 @@ describe.sequential(
121122
expect(txEvent.method).toBe("insertRecords");
122123
expect(txEvent.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
123124
expect(txEvent.blockHeight).toBeGreaterThan(0);
125+
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
124126
expect(txEvent.feeAmount).toBeDefined();
125127
// Test wallet has system:network_writer role (fee exempt)
126128
expect(txEvent.feeAmount).toBe("0");
@@ -155,6 +157,7 @@ describe.sequential(
155157
// Validate that txEvent.txId is normalized with 0x prefix
156158
expect(txEvent.txId).toBe(normalizeTransactionId(txHash));
157159
expect(txEvent.method).toBe("deployStream");
160+
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
158161

159162
console.log(`✅ Successfully queried transaction with hash without 0x prefix`);
160163
}

0 commit comments

Comments
 (0)