Skip to content
Open
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
128 changes: 112 additions & 16 deletions apps/explorer/src/comps/Amount.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { Link } from '@tanstack/react-router'
import { type Address, Value } from 'ox'
import { Hooks } from 'tempo.ts/wagmi'
import { maxUint256 } from 'viem'
import { Abis } from 'viem/tempo'
import { useReadContracts } from 'wagmi'
import { TokenIcon } from '#comps/TokenIcon.tsx'
import { ellipsis } from '#lib/chars'
import { isTip20Address } from '#lib/domain/tip20.ts'
import { PriceFormatter } from '#lib/formatting.ts'

export function Amount(props: Amount.Props) {
const { value, token, decimals, symbol } = props
const {
value,
token,
decimals,
symbol,
before,
after,
prefix,
suffix,
short,
maxWidth,
infinite,
} = props

const isTip20 = isTip20Address(token)

Expand Down Expand Up @@ -37,29 +51,111 @@ export function Amount(props: Amount.Props) {

const isLoading = decimals_ === undefined

const rawFormatted = isLoading ? '…' : Value.format(value, decimals_)
const formatted = isLoading ? '…' : PriceFormatter.formatAmount(rawFormatted)
if (isLoading) return <span>{ellipsis}</span>

return (
<span className="inline-flex items-center gap-1 whitespace-nowrap">
{formatted} <TokenIcon address={token} name={symbol_} />
<Link
className="text-base-content-positive press-down inline-flex"
params={{ address: token }}
title={token}
to={isTip20Address(token) ? '/token/$address' : '/address/$address'}
>
{symbol_}
</Link>
</span>
<Amount.Base
value={value}
decimals={decimals_}
before={before}
after={
<>
<TokenIcon address={token} name={symbol_} className="shrink-0" />
<Link
className="text-base-content-positive press-down inline-flex shrink-0"
params={{ address: token }}
title={token}
to={isTip20Address(token) ? '/token/$address' : '/address/$address'}
>
{symbol_}
</Link>
{after}
</>
}
prefix={prefix}
suffix={suffix}
short={short}
maxWidth={maxWidth}
infinite={infinite}
/>
)
}

export namespace Amount {
export interface Props {
value: bigint
export interface Props extends Omit<Base.Props, 'decimals'> {
token: Address.Address
decimals?: number
symbol?: string
}

export function Base(props: Base.Props) {
const {
value,
decimals,
before,
after,
prefix,
suffix,
short,
maxWidth = 24,
infinite = true,
} = props

const precisionLossTolerance = 10n ** 64n
const isInfinite =
infinite !== false &&
value > (maxUint256 / precisionLossTolerance) * precisionLossTolerance

if (isInfinite && infinite === null) return null

if (isInfinite)
return (
<span className="inline-flex items-center gap-1 min-w-0">
{before}
{infinite === true ? 'infinite' : infinite}
{after}
</span>
)

const rawFormatted = Value.format(value, decimals)
const fullFormatted = PriceFormatter.formatAmount(rawFormatted)
const formatted = short
? PriceFormatter.formatAmountShort(rawFormatted)
: fullFormatted

return (
<span className="inline-flex items-center gap-1 min-w-0">
{before}
<span
className="overflow-hidden text-ellipsis whitespace-nowrap min-w-0"
style={{ maxWidth: `${maxWidth}ch` }}
title={`${prefix ?? ''}${fullFormatted}${suffix ?? ''}`}
>
{`${prefix ?? ''}${formatted}${suffix ?? ''}`}
</span>
{after}
</span>
)
}

export namespace Base {
export interface Props {
after?: React.ReactNode
before?: React.ReactNode
decimals: number
/**
* Controls infinite value detection (uint256 max):
* - `true` (default): detect and show "infinite"
* - `false`: no detection, show the raw value
* - `ReactNode`: detect and show custom label
* - `null`: detect and render nothing
*/
infinite?: boolean | null | React.ReactNode
maxWidth?: number
prefix?: string
short?: boolean
suffix?: string
value: bigint
}
}
}
4 changes: 3 additions & 1 deletion apps/explorer/src/comps/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function DataGrid(props: DataGrid.Props) {
? `minmax(${col.minWidth}px, ${col.width})`
: col.width
if (col.minWidth) return `minmax(${col.minWidth}px, auto)`
return 'auto'
return mode === 'tabs' ? 'minmax(0, auto)' : 'auto'
})
.join(' ')

Expand All @@ -53,6 +53,7 @@ export function DataGrid(props: DataGrid.Props) {
className={cx(
'w-full text-[14px] rounded-t-[2px] grid',
flexible && 'min-w-max',
mode === 'tabs' && 'max-w-full',
)}
style={{ gridTemplateColumns }}
>
Expand Down Expand Up @@ -130,6 +131,7 @@ export function DataGrid(props: DataGrid.Props) {
: 'justify-start',
item.link &&
'pointer-events-none [&_a]:pointer-events-auto [&_a]:relative [&_a]:z-1 [&_button]:pointer-events-auto [&_button]:relative [&_button]:z-1',
mode === 'tabs' && 'min-w-0 overflow-hidden',
)}
>
{content}
Expand Down
22 changes: 10 additions & 12 deletions apps/explorer/src/comps/Receipt.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link } from '@tanstack/react-router'
import type { Address, Hex } from 'ox'
import { useState } from 'react'
import { Amount } from '#comps/Amount'
import { Midcut } from '#comps/Midcut'
import { ReceiptMark } from '#comps/ReceiptMark'
import { TxEventDescription } from '#comps/TxEventDescription'
Expand Down Expand Up @@ -148,23 +149,20 @@ export function Receipt(props: Receipt.Props) {
className="[counter-increment:event]"
>
<div className="flex flex-col gap-[8px]">
<div className="flex flex-row justify-between items-start gap-[10px]">
<div className="grid grid-cols-[1fr_minmax(0,30%)] gap-[10px]">
<div className="flex flex-row items-start gap-[4px] grow min-w-0 text-tertiary">
<div className="flex items-center text-tertiary before:content-[counter(event)_'.'] shrink-0 leading-[24px] min-w-[20px]"></div>
<TxEventDescription event={event} />
</div>
<div className="flex items-center text-right shrink-0 leading-[24px]">
<div className="flex items-start justify-end shrink-1 leading-[24px]">
{totalAmountBigInt > 0n && (
<span
title={PriceFormatter.format(totalAmountBigInt, {
decimals,
})}
>
{PriceFormatter.format(totalAmountBigInt, {
decimals,
format: 'short',
})}
</span>
<Amount.Base
decimals={decimals}
infinite={null}
prefix="$"
short
value={totalAmountBigInt}
/>
)}
</div>
</div>
Expand Down
44 changes: 34 additions & 10 deletions apps/explorer/src/comps/TxTransactionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Address, Hex, Value } from 'ox'
import * as React from 'react'
import type { RpcTransaction as Transaction, TransactionReceipt } from 'viem'
import type { getBlock } from 'wagmi/actions'
import { Amount } from '#comps/Amount'
import { FormattedTimestamp, type TimeFormat } from '#comps/TimeFormat'
import { TxEventDescription } from '#comps/TxEventDescription'
import {
Expand Down Expand Up @@ -131,26 +132,49 @@ export function TransactionTotal(props: { transaction: Transaction }) {
),
)
}, [batchData])
if (!amountParts?.length) return <>$0.00</>

const infiniteLabel = <span className="text-secondary">−</span>

if (!amountParts?.length)
return (
<Amount.Base
value={0n}
decimals={0}
prefix="$"
short
infinite={infiniteLabel}
/>
)

// Normalize all amounts to 18 decimals and sum as bigints
const normalizedDecimals = 18
const totalValue = amountParts.reduce((sum, part) => {
const decimals = part.value.decimals ?? 6
return sum + Number(Value.format(part.value.value, decimals))
}, 0)
const scale = 10n ** BigInt(normalizedDecimals - decimals)
return sum + part.value.value * scale
}, 0n)

if (totalValue === 0) {
if (totalValue === 0n) {
const value = transaction.value ? Hex.toBigInt(transaction.value) : 0n
if (value === 0n) return <span className="text-tertiary">—</span>
return (
<span className="text-primary">
{PriceFormatter.format(value, { decimals: 18, format: 'short' })}
</span>
<Amount.Base
value={value}
decimals={18}
infinite={infiniteLabel}
prefix="$"
short
/>
)
}

return (
<span className="text-primary">
{PriceFormatter.format(totalValue, { format: 'short' })}
</span>
<Amount.Base
value={totalValue}
decimals={normalizedDecimals}
infinite={infiniteLabel}
prefix="$"
short
/>
)
}
5 changes: 1 addition & 4 deletions apps/explorer/src/routes/api/tx/trace/$hash.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'
import {
fetchTraceData,
type TraceData,
} from '#lib/queries/trace'
import { fetchTraceData, type TraceData } from '#lib/queries/trace'
import { zHash } from '#lib/zod'

export type {
Expand Down