From e4722ed340f5455a54c98e56cdddc9b9ab624c43 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Wed, 25 Mar 2026 21:40:55 -0700 Subject: [PATCH] Add transaction summaries to explorer tables --- src/lib/api.ts | 31 +++++++++++- src/lib/components/TxTable.svelte | 62 ++++++++++++++++++++--- src/lib/types.ts | 10 ++++ src/routes/address/[addr]/+page.server.ts | 15 ++++-- src/routes/address/[addr]/+page.svelte | 10 ++-- src/routes/block/[hash]/+page.server.ts | 4 +- src/routes/block/[hash]/+page.svelte | 2 +- src/routes/mempool/+page.server.ts | 7 +-- src/routes/mempool/+page.svelte | 6 +-- 9 files changed, 124 insertions(+), 23 deletions(-) diff --git a/src/lib/api.ts b/src/lib/api.ts index d449e73..3cb5d1d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -2,7 +2,7 @@ import { env } from '$env/dynamic/private'; import type { ChainInfo, Block, BlockHeader, Transaction, Difficulty, StakingInfo, MiningInfo, NetworkInfo, PeerInfo, - AddressBalance, AddressUtxo, SupplyInfo, ValidationResult + AddressBalance, AddressUtxo, SupplyInfo, ValidationResult, TransactionSummary } from './types'; const BASE_URL = env.TRIANGLES_API_URL || 'http://127.0.0.1:19112'; @@ -95,6 +95,35 @@ export const getAddressTxids = (addr: string, start?: number, end?: number) => { // Validation export const validateAddress = (addr: string) => get(`validate/${addr}`); +function summarizeTransaction(tx: Transaction): TransactionSummary { + return { + txid: tx.txid, + time: tx.blocktime ?? tx.time, + confirmations: tx.confirmations, + outputCount: tx.vout.length, + totalOutput: tx.vout.reduce((sum, vout) => sum + vout.value, 0), + primaryAddress: tx.vout.flatMap((vout) => vout.scriptPubKey.addresses ?? [])[0], + isReward: tx.vin.some((vin) => Boolean(vin.coinbase)) + }; +} + +export async function getTransactionSummaries( + txids: string[], + limit = txids.length +): Promise { + const selected = limit > 0 ? txids.slice(0, limit) : txids; + + return Promise.all( + selected.map(async (txid) => { + try { + return summarizeTransaction(await getTransaction(txid)); + } catch { + return { txid }; + } + }) + ); +} + // Helper: get latest N blocks (parallel fetch for performance) export async function getLatestBlocks(count: number): Promise { const chain = await getChainInfo(); diff --git a/src/lib/components/TxTable.svelte b/src/lib/components/TxTable.svelte index b8e4d22..913b399 100644 --- a/src/lib/components/TxTable.svelte +++ b/src/lib/components/TxTable.svelte @@ -1,8 +1,19 @@
@@ -11,16 +22,55 @@ # Transaction ID + Time + Confirmations + Outputs + Total Output - {#each displayed as txid, i} + {#each displayed() as tx, i} {i + 1} - - {truncateHash(txid, 16)} + + {truncateHash(tx.txid, 16)} + {#if tx.primaryAddress} +
+ {#if tx.isReward} + Reward to + {:else} + First output + {/if} + {' '} + {truncateHash(tx.primaryAddress, 10)} +
+ {/if} + + + {#if tx.time != null} + {formatTimestamp(tx.time)} + {:else} + Unavailable + {/if} + + + {#if tx.confirmations != null} + {tx.confirmations} + {:else} + Pending + {/if} + + + {tx.outputCount ?? '—'} + + + {#if tx.totalOutput != null} + {formatAmount(tx.totalOutput)} TRI + {:else} + Unavailable + {/if} {/each} diff --git a/src/lib/types.ts b/src/lib/types.ts index 1ffca78..f0ac708 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -59,6 +59,16 @@ export interface Transaction { blocktime?: number; } +export interface TransactionSummary { + txid: string; + time?: number; + confirmations?: number; + outputCount?: number; + totalOutput?: number; + primaryAddress?: string; + isReward?: boolean; +} + export interface TxInput { txid?: string; vout?: number; diff --git a/src/routes/address/[addr]/+page.server.ts b/src/routes/address/[addr]/+page.server.ts index cdce9e3..b2077ce 100644 --- a/src/routes/address/[addr]/+page.server.ts +++ b/src/routes/address/[addr]/+page.server.ts @@ -1,7 +1,15 @@ -import { getAddressBalance, getAddressUtxos, getAddressTxids, validateAddress } from '$lib/api'; +import { + getAddressBalance, + getAddressUtxos, + getAddressTxids, + getTransactionSummaries, + validateAddress +} from '$lib/api'; import { error } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; +const TX_HISTORY_LIMIT = 50; + export const load: PageServerLoad = async ({ params }) => { try { const [validation, balance, utxos, txids] = await Promise.all([ @@ -15,11 +23,12 @@ export const load: PageServerLoad = async ({ params }) => { error(400, 'Invalid address'); } - return { address: params.addr, balance, utxos, txids }; + const txs = await getTransactionSummaries(txids, TX_HISTORY_LIMIT); + return { address: params.addr, balance, utxos, txs, totalTxs: txids.length }; } catch (e: unknown) { const msg = e instanceof Error ? e.message : String(e); if (msg.includes('Address index not enabled')) { - return { address: params.addr, balance: null, utxos: [], txids: [], indexError: true }; + return { address: params.addr, balance: null, utxos: [], txs: [], totalTxs: 0, indexError: true }; } error(404, 'Address not found'); } diff --git a/src/routes/address/[addr]/+page.svelte b/src/routes/address/[addr]/+page.svelte index 4fb2062..02ac070 100644 --- a/src/routes/address/[addr]/+page.svelte +++ b/src/routes/address/[addr]/+page.svelte @@ -68,13 +68,13 @@
-

Transactions ({data.txids.length})

+

Transactions ({data.totalTxs})

- {#if data.txids.length > 0} - - {#if data.txids.length > 50} + {#if data.txs.length > 0} + + {#if data.totalTxs > 50}
- Showing 50 of {data.txids.length} transactions + Showing 50 of {data.totalTxs} transactions
{/if} {:else} diff --git a/src/routes/block/[hash]/+page.server.ts b/src/routes/block/[hash]/+page.server.ts index ddd31fb..2c35667 100644 --- a/src/routes/block/[hash]/+page.server.ts +++ b/src/routes/block/[hash]/+page.server.ts @@ -1,4 +1,5 @@ -import { getBlock, getTransaction } from '$lib/api'; +import { getBlock, getTransactionSummaries } from '$lib/api'; +import { getTransaction } from '$lib/api'; import { error } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; @@ -26,6 +27,7 @@ export const load: PageServerLoad = async ({ params }) => { return { block, + txs: await getTransactionSummaries(block.tx), addresses: Array.from(addressSet) }; } catch { diff --git a/src/routes/block/[hash]/+page.svelte b/src/routes/block/[hash]/+page.svelte index d3d6c7d..1a43715 100644 --- a/src/routes/block/[hash]/+page.svelte +++ b/src/routes/block/[hash]/+page.svelte @@ -84,5 +84,5 @@

Transactions ({block.tx.length})

- +
diff --git a/src/routes/mempool/+page.server.ts b/src/routes/mempool/+page.server.ts index ddf1d0a..c603282 100644 --- a/src/routes/mempool/+page.server.ts +++ b/src/routes/mempool/+page.server.ts @@ -1,11 +1,12 @@ -import { getMempool } from '$lib/api'; +import { getMempool, getTransactionSummaries } from '$lib/api'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async () => { try { const txids = await getMempool(); - return { txids }; + const txs = await getTransactionSummaries(txids); + return { txs, totalTxs: txids.length }; } catch (e) { - return { txids: [], error: String(e) }; + return { txs: [], totalTxs: 0, error: String(e) }; } }; diff --git a/src/routes/mempool/+page.svelte b/src/routes/mempool/+page.svelte index aed758a..46a1fbf 100644 --- a/src/routes/mempool/+page.svelte +++ b/src/routes/mempool/+page.svelte @@ -10,12 +10,12 @@

Mempool

-

{data.txids.length} unconfirmed transaction{data.txids.length !== 1 ? 's' : ''}

+

{data.totalTxs} unconfirmed transaction{data.totalTxs !== 1 ? 's' : ''}

- {#if data.txids.length > 0} - + {#if data.txs.length > 0} + {:else}
Mempool is empty
{/if}