Skip to content

Commit e51fcdb

Browse files
NateIsernclaude
andcommitted
feat(amount): show tx amount on home feed + block page
Home LatestTxList now consumes the /api/transactions feed (which carries the total-output amount) instead of deriving bare txids from recent blocks. The block API returns a parallel txValues array (bounded to <=50-tx blocks) and the block page shows the amount next to each txid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 33a977d commit e51fcdb

5 files changed

Lines changed: 64 additions & 20 deletions

File tree

server/index.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,31 @@ app.get('/api/block/:hashOrHeight', async (req, res) => {
235235
const network = parseNetwork(req.query.network)
236236
const { hashOrHeight } = req.params
237237
const block = await blockCache.getBlock(hashOrHeight, network, true)
238-
res.json({ block, network })
238+
239+
// Enrich with a parallel array of per-tx total output values (FAIR) so the
240+
// block page can show an amount next to each txid. Bounded to reasonably
241+
// sized blocks to keep RPC load in check; larger blocks omit txValues.
242+
const MAX_TX_VALUE_ENRICH = 50
243+
let txValues: Array<number | null> | undefined
244+
const txids = Array.isArray((block as { tx?: unknown }).tx)
245+
? ((block as { tx: string[] }).tx)
246+
: []
247+
if (txids.length > 0 && txids.length <= MAX_TX_VALUE_ENRICH) {
248+
txValues = await Promise.all(
249+
txids.map(async (txid) => {
250+
try {
251+
const tx = await blockCache.getTransaction(txid, network, true)
252+
const vout = (tx as { vout?: Array<{ value?: number }> }).vout
253+
if (!Array.isArray(vout)) return null
254+
return vout.reduce((sum, o) => sum + (Number(o.value) || 0), 0)
255+
} catch {
256+
return null
257+
}
258+
}),
259+
)
260+
}
261+
262+
res.json({ block, txValues, network })
239263
} catch (error) {
240264
handleRouteError(res, 'Error fetching block', error)
241265
}

src/components/block-content.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ export function BlockContent({ hashOrHeight }: { hashOrHeight: string }) {
216216
#{index}
217217
</span>
218218
<HashCell value={txid} to="tx" lead={10} tail={8} className="min-w-0 flex-1" />
219+
{typeof block.txValues?.[index] === 'number' ? (
220+
<span className="shrink-0 text-xs font-semibold tabular-nums text-foreground">
221+
{(block.txValues[index] as number).toLocaleString(undefined, {
222+
maximumFractionDigits: 8,
223+
})}{' '}
224+
FAIR
225+
</span>
226+
) : null}
219227
</li>
220228
))}
221229
</ul>

src/components/home/latest-tx-list.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { Link } from 'react-router-dom'
22
import { Receipt } from 'lucide-react'
33
import { useTranslations } from '@/lib/i18n'
4-
import { useLatestTransactions, type RecentBlock } from '@/hooks/use-recent-blocks'
4+
import { useRecentTransactions } from '@/hooks/use-recent-transactions'
55
import { Skeleton } from '@/components/ui/skeleton'
66
import { CopyButton } from '@/components/copy-button'
77
import { RelativeTime } from '@/components/detail/relative-time'
88
import { shortHash } from '@/lib/format'
99

1010
interface LatestTxListProps {
11-
blocks: RecentBlock[] | undefined
12-
isLoading: boolean
13-
isError: boolean
1411
max?: number
1512
}
1613

17-
export function LatestTxList({ blocks, isLoading, isError, max = 20 }: LatestTxListProps) {
14+
export function LatestTxList({ max = 20 }: LatestTxListProps) {
1815
const t = useTranslations('home')
19-
const transactions = useLatestTransactions(blocks, max)
16+
// Use the recent-transactions feed (includes the total output amount) rather
17+
// than deriving bare txids from recent blocks, so each row can show a value.
18+
const { data, isLoading, isError } = useRecentTransactions(max)
19+
const transactions = data?.transactions ?? []
2020

2121
return (
2222
<div className="flex h-full flex-col rounded-2xl border bg-muted/30">
@@ -43,7 +43,7 @@ export function LatestTxList({ blocks, isLoading, isError, max = 20 }: LatestTxL
4343
<ul className="divide-y">
4444
{transactions.map((tx) => (
4545
<li
46-
key={tx.txid}
46+
key={`${tx.txid}-${tx.blockHeight ?? 'mempool'}`}
4747
className="group flex items-center gap-3 px-4 py-2.5 transition-colors hover:bg-muted/40"
4848
>
4949
<div className="flex min-w-0 flex-1 items-center gap-2">
@@ -60,16 +60,24 @@ export function LatestTxList({ blocks, isLoading, isError, max = 20 }: LatestTxL
6060
</div>
6161

6262
<div className="flex shrink-0 flex-col items-end gap-0.5 text-xs">
63-
<Link
64-
to={`/block/${tx.blockHeight}`}
65-
className="font-medium text-muted-foreground tabular-nums hover:text-foreground"
66-
>
67-
#{tx.blockHeight.toLocaleString()}
68-
</Link>
69-
<RelativeTime
70-
timestamp={tx.blockTime}
71-
className="text-muted-foreground"
72-
/>
63+
{typeof tx.amount === 'number' ? (
64+
<span className="font-semibold tabular-nums text-foreground">
65+
{tx.amount.toLocaleString(undefined, { maximumFractionDigits: 8 })} FAIR
66+
</span>
67+
) : null}
68+
{tx.blockHeight !== null ? (
69+
<Link
70+
to={`/block/${tx.blockHeight}`}
71+
className="font-medium text-muted-foreground tabular-nums hover:text-foreground"
72+
>
73+
#{tx.blockHeight.toLocaleString()}
74+
</Link>
75+
) : (
76+
<span className="font-medium text-amber-700 dark:text-amber-300">
77+
{t('mempool')}
78+
</span>
79+
)}
80+
<RelativeTime timestamp={tx.time} className="text-muted-foreground" />
7381
</div>
7482
</li>
7583
))}

src/hooks/use-block.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ export interface Block {
1717
size: number
1818
weight?: number
1919
tx: string[]
20+
/** Per-tx total output value (FAIR), parallel to `tx`. From the API's
21+
* `txValues`; undefined for oversized blocks that skip enrichment. */
22+
txValues?: Array<number | null>
2023
previousblockhash?: string
2124
nextblockhash?: string
2225
confirmations: number
2326
}
2427

2528
interface BlockResponse {
2629
block: Block
30+
txValues?: Array<number | null>
2731
network: string
2832
}
2933

@@ -41,7 +45,7 @@ export function useBlock(hashOrHeight: string): UseQueryResult<Block> {
4145
throw new Error(await readErrorMessage(response, 'block'))
4246
}
4347
const data = (await response.json()) as BlockResponse
44-
return data.block
48+
return { ...data.block, txValues: data.txValues }
4549
},
4650
refetchInterval,
4751
retry: 1,

src/pages/home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default function HomePage() {
3434

3535
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
3636
<RecentBlocksList blocks={blocks} isLoading={isLoading} isError={isError} />
37-
<LatestTxList blocks={blocks} isLoading={isLoading} isError={isError} max={TX_FEED_LIMIT} />
37+
<LatestTxList max={TX_FEED_LIMIT} />
3838
</div>
3939
</div>
4040
)

0 commit comments

Comments
 (0)