Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ const txEvent = await transactionAction.getTransactionEvent({
txId: '0xabcdef...'
});
console.log(`Method: ${txEvent.method}, Fee: ${txEvent.feeAmount} wei`);
console.log(`Timestamp (ms): ${txEvent.stampMs}`);

// List fees paid by wallet
const entries = await transactionAction.listTransactionFees({
Expand Down
2 changes: 2 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ Retrieves detailed information about a specific transaction by its hash.
- `Promise<TransactionEvent>` - Complete transaction details including:
- `txId: string` - Transaction hash (0x-prefixed)
- `blockHeight: number` - Block number where transaction was included
- `stampMs: number` - Millisecond timestamp from the block header (0 when unavailable)
- `method: string` - Method name (e.g., "deployStream", "insertRecords")
- `caller: string` - Ethereum address of the caller (lowercase, 0x-prefixed)
- `feeAmount: string` - Total fee amount as string (handles large numbers)
Expand Down Expand Up @@ -537,6 +538,7 @@ console.log(`Method: ${txEvent.method}`);
console.log(`Caller: ${txEvent.caller}`);
console.log(`Fee: ${txEvent.feeAmount} wei`);
console.log(`Block: ${txEvent.blockHeight}`);
console.log(`Timestamp: ${txEvent.stampMs}`);

// Check fee distributions
for (const dist of txEvent.feeDistributions) {
Expand Down
44 changes: 43 additions & 1 deletion src/contracts-api/transactionAction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,45 @@
import { KwilSigner, NodeKwil, WebKwil, Types } from "@trufnetwork/kwil-js";
import { KwilSigner, NodeKwil, WebKwil } from "@trufnetwork/kwil-js";
import { TransactionEvent, FeeDistribution, GetTransactionEventInput } from "../types/transaction";

const INDEXER_BASE = "https://indexer.infra.truf.network";

function normalizeTransactionId(txId: string): string {
const lower = txId.toLowerCase();
return lower.startsWith("0x") ? lower : `0x${lower}`;
}

async function fetchTransactionStampMs(blockHeight: number, txId: string): Promise<number> {
const url = `${INDEXER_BASE}/v0/chain/transactions?from-block=${blockHeight}&to-block=${blockHeight}&order=desc`;
try {
const response = await fetch(url);
if (!response.ok) {
console.warn(`Indexer returned ${response.status} while fetching block ${blockHeight} for tx ${txId}`);
return 0;
}

const data = await response.json() as {
ok: boolean;
data: Array<{
block_height: number;
hash: string;
stamp_ms: number | null;
}>;
};

if (!data.ok || !Array.isArray(data.data)) {
console.warn(`Indexer payload malformed for block ${blockHeight} (tx ${txId})`);
return 0;
}

const normalizedTargetHash = normalizeTransactionId(txId);
const tx = data.data.find(entry => normalizeTransactionId(entry.hash) === normalizedTargetHash);
return tx?.stamp_ms ?? 0;
} catch (error) {
console.warn(`Failed to fetch stamp_ms for tx ${txId} at block ${blockHeight}`, error);
return 0;
}
}

/**
* Database row structure returned from get_transaction_event action
*/
Expand Down Expand Up @@ -134,9 +173,12 @@ export class TransactionAction {
throw new Error(`Invalid block height: ${row.block_height} (tx: ${row.tx_id})`);
}

const stampMs = await fetchTransactionStampMs(blockHeight, row.tx_id);

return {
txId: row.tx_id,
blockHeight,
stampMs,
method: row.method,
caller: row.caller,
feeAmount,
Expand Down
5 changes: 5 additions & 0 deletions src/types/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export interface TransactionEvent {
txId: string;
/** Block height when transaction was included */
blockHeight: number;
/**
* Millisecond timestamp from the block header via indexer lookup.
* Will be 0 when the indexer is unavailable.
*/
stampMs: number;
/** Method name (e.g., "deployStream", "insertRecords") */
method: string;
/** Ethereum address of caller (lowercase, 0x-prefixed) */
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/transactionAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe.sequential(
expect(txEvent.method).toBe("deployStream");
expect(txEvent.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
expect(txEvent.blockHeight).toBeGreaterThan(0);
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
expect(txEvent.feeAmount).toBeDefined();
// Test wallet has system:network_writer role (fee exempt)
expect(txEvent.feeAmount).toBe("0");
Expand Down Expand Up @@ -121,6 +122,7 @@ describe.sequential(
expect(txEvent.method).toBe("insertRecords");
expect(txEvent.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
expect(txEvent.blockHeight).toBeGreaterThan(0);
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
expect(txEvent.feeAmount).toBeDefined();
// Test wallet has system:network_writer role (fee exempt)
expect(txEvent.feeAmount).toBe("0");
Expand Down Expand Up @@ -155,6 +157,7 @@ describe.sequential(
// Validate that txEvent.txId is normalized with 0x prefix
expect(txEvent.txId).toBe(normalizeTransactionId(txHash));
expect(txEvent.method).toBe("deployStream");
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);

console.log(`✅ Successfully queried transaction with hash without 0x prefix`);
}
Expand Down
Loading