From 3fca1aa04338617f883cbeaefa9aeee4425f7bbd Mon Sep 17 00:00:00 2001 From: satyakwok <119509589+satyakwok@users.noreply.github.com> Date: Mon, 8 Jun 2026 12:52:11 +0200 Subject: [PATCH] feat(api): include block_timestamp in /tx/ detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The transactions row carries no timestamp — chain time lives on the blocks table — so /tx/ had no way to tell the explorer when a tx happened. The tx-detail page consuming this endpoint had to show epoch 0. Join the block's timestamp into the detail response (one extra lookup by height; the detail path is single-tx, not a hot list). Smoke asserts the fixture tx in block 2 returns its block timestamp. --- crates/api/src/routes/tx.rs | 12 +++++++++++- scripts/smoke.sh | 5 ++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/api/src/routes/tx.rs b/crates/api/src/routes/tx.rs index 546e160..6a8f75d 100644 --- a/crates/api/src/routes/tx.rs +++ b/crates/api/src/routes/tx.rs @@ -8,13 +8,18 @@ use crate::serialise::{WireLog, WireTransaction}; use axum::extract::{Path, State}; use axum::routing::get; use axum::{Json, Router}; -use indexer_db::{logs, transactions}; +use indexer_db::{blocks, logs, transactions}; use serde::Serialize; #[derive(Debug, Serialize)] struct TxResponse { tx: WireTransaction, logs: Vec, + /// Unix-seconds chain time of the tx's block. The per-tx row carries no + /// timestamp (it lives on `blocks`), so the detail view joins it here — + /// otherwise the explorer can't show when an indexed tx happened. 0 if the + /// block row is somehow missing (shouldn't occur for an indexed tx). + block_timestamp: i64, } async fn detail( @@ -26,9 +31,14 @@ async fn detail( .await? .ok_or_else(|| ApiError::NotFound("tx".into()))?; let log_rows = logs::for_tx(&state.pool, &hash).await?; + let block_timestamp = blocks::get_by_height(&state.pool, tx.block_height) + .await? + .map(|b| b.timestamp) + .unwrap_or(0); Ok(Json(TxResponse { tx: (&tx).into(), logs: log_rows.iter().map(WireLog::from).collect(), + block_timestamp, })) } diff --git a/scripts/smoke.sh b/scripts/smoke.sh index 048ae77..f97de2c 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -153,7 +153,10 @@ v=$(curl -fsS "$API_BASE/tx/0xtxcccc00000000000000000000000000000000000000000000 [[ "$v" == "0xfeedfacefeedfacefeedfacefeedfacefeedface" ]] || fail "/tx.from rename broken (got '$v')" v=$(curl -fsS "$API_BASE/tx/0xtxcccc00000000000000000000000000000000000000000000000000000000cc" | jq -r '.logs | length') [[ "$v" == "2" ]] || fail "/tx logs count != 2 (got $v)" -ok "/tx/:hash (from_addr->from, logs[2])" +# block_timestamp joined from blocks (tx cccc is in block 2 @ ts 1700086400) +v=$(curl -fsS "$API_BASE/tx/0xtxcccc00000000000000000000000000000000000000000000000000000000cc" | jq -r '.block_timestamp') +[[ "$v" == "1700086400" ]] || fail "/tx block_timestamp != 1700086400 (got '$v')" +ok "/tx/:hash (from_addr->from, logs[2], block_timestamp)" # /tx/ -> 404 code=$(curl -s -o /dev/null -w '%{http_code}' "$API_BASE/tx/0xdeadbeef")