# Blockchain Values

Shows addresses, hashes, amounts, tokens, block numbers, and timestamps so you can read, compare, copy, and verify them.

## Overview

Onchain values are long and exact. An address has 42 characters, a transaction hash has 66, and a token amount can carry 6 decimal places.

You compare these values character by character, copy them between apps, and look them up in a block explorer. Format each one the same way in every view.

This page combines:

* `Hex` and `formatHex` for addresses and hashes.
* `Identicon` for a visual cue beside an address.
* `Amount` for token and currency amounts.
* `Token` for token icons.
* `CopyButton` and `Button.Link` for copying values and opening the explorer.
* `SecretValue` for secrets, which are not the same as hidden amounts.

```tsx
import { Amount, Button, Hex, Identicon, Rows, style, Surface, Tag, Token, Tooltip } from '@tempoxyz/ds'
import { ExternalLink } from '@tempoxyz/ds/icons'

export const fill = true
const hash = '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060'
const from = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
const to = '0x71C7656EC7ab88b098defB751B7401B5f6d8976F'

function Explorer(props: { label: string }) {
  return (
    <Tooltip.Root>
      <Tooltip.Trigger
        render={
          <Button.Link
            aria-label={props.label}
            href="https://explore.tempo.xyz"
            rel="noreferrer"
            shape="icon"
            size="small"
            target="_blank"
            variant="text"
          />
        }
      >
        <ExternalLink />
      </Tooltip.Trigger>
      <Tooltip.Popup>{props.label}</Tooltip.Popup>
    </Tooltip.Root>
  )
}

export function Demo() {
  return (
    <Tooltip.Provider>
      <Surface render={<section aria-labelledby="transaction-title" />} {...styles.card()}>
        <Surface.Header>
          <Surface.Title id="transaction-title">Transaction</Surface.Title>
          <Tag intent="positive">
            <Tag.Dot />
            Confirmed
          </Tag>
        </Surface.Header>
        <Rows.Root dividers="full">
          <Rows.Row>
            <Rows.Label>Hash</Rows.Label>
            <span {...styles.value()}>
              <Hex copy value={hash} />
              <Explorer label="View transaction on explorer" />
            </span>
          </Rows.Row>
          <Rows.Row>
            <Rows.Label>Block</Rows.Label>
            <span {...styles.value()}>
              <span {...styles.number()}>18,204,551</span>
              <Explorer label="View block 18204551 on explorer" />
            </span>
          </Rows.Row>
          <Rows.Row>
            <Rows.Label>Time</Rows.Label>
            <span {...styles.value()}>
              <time dateTime="2026-10-20T09:41:07Z">Oct 20, 2026, 09:41:07 UTC</time>
            </span>
          </Rows.Row>
          <Rows.Row>
            <Rows.Label>From</Rows.Label>
            <span {...styles.value()}>
              <Identicon aria-hidden address={from} size="16" />
              <Hex copy value={from} />
            </span>
          </Rows.Row>
          <Rows.Row>
            <Rows.Label>To</Rows.Label>
            <span {...styles.value()}>
              <Identicon aria-hidden address={to} size="16" />
              <span {...styles.name()}>Tempo Labs</span>
              <Hex copy value={to} />
            </span>
          </Rows.Row>
          <Rows.Row>
            <Rows.Label>Amount</Rows.Label>
            <span {...styles.value()}>
              <Token address="0x20c0000000000000000000000000000000000000" size="small" />
              <Amount symbol="PathUSD" value="1250.504211" />
            </span>
          </Rows.Row>
          <Rows.Row>
            <Rows.Label>Network fee</Rows.Label>
            <Amount currency="USD" precise value="0.00003" />
          </Rows.Row>
        </Rows.Root>
      </Surface>
    </Tooltip.Provider>
  )
}

namespace styles {
  export const card = style({
    boxSizing: 'border-box',
    marginInline: 'auto !custom',
    maxWidth: 'xl',
    width: '100% !custom',
  })

  export const name = style({ flexShrink: '0 !custom', typography: 'label.14.strong' })

  export const number = style({ fontVariantNumeric: 'tabular-nums !custom' })

  export const value = style({
    alignItems: 'center',
    columnGap: '2',
    display: 'flex',
    justifyContent: 'flex-end',
    minWidth: '0',
  })
}
```

[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/BlockchainTransaction.tsx)

## Choose a Format

| Value            | Component                   | Format                                                                                       |
| ---------------- | --------------------------- | -------------------------------------------------------------------------------------------- |
| Address          | `Hex` beside an `Identicon` | `0x` and 40 hexadecimal characters, truncated in the middle, with copy.                      |
| Transaction hash | `Hex`                       | `0x` and 64 hexadecimal characters, truncated in the middle, with copy and an explorer link. |
| Amount           | `Amount`                    | A decimal string with its token `symbol` or `currency`.                                      |
| Token            | `Token` beside its symbol   | The icon and the symbol as text, with the contract address in details.                       |
| Block number     | Text in tabular figures     | Grouped digits on screen, such as 18,204,551, and raw digits in copies and URLs.             |
| Timestamp        | A `time` element            | Relative in lists, absolute with seconds and a time zone in details.                         |

## Addresses and Hashes

### Truncate in the Middle

`Hex` keeps both ends of a value and fits it to its container, so you can still compare the prefix and the checksum ending. For plain strings, such as a `Toast` title, `formatHex` keeps four characters on each side of an ellipsis, as in 0xd8dA…6045.

**Do:** Truncate addresses in the middle with `Hex`, which keeps both ends visible.

**Don't:** Cut off the end of an address with a CSS ellipsis.

### Show the Full Value Where You Verify

Attackers create addresses that match the first and last characters of one you use, hoping you copy the wrong one from your history. Wherever you choose or confirm a recipient, show more of the address.

* On Receive, give the address the full width of its card with `Hex` and `variant="filled"`.
* On a review step, show the recipient address in full, and ask people to check it against where they got it.
* Where only a short form fits but people still compare it, keep more characters with `formatHex(value, { min: 6 })`.
* In activity lists, a short value with a contact name is enough, since you open the record to verify it.

### Pair Addresses With Names

Show a contact or account name beside the address when you know it, with an `Identicon` before both. The `Identicon` is a cue, never the identifier. Set `aria-hidden` on it when the address text is adjacent.

Keep the letter case of an address as you received it, since mixed case carries its checksum. Compare addresses without regard to case. `Identicon` renders the same colors for any letter case.

## Copy Values

* Set `copy` on `Hex` wherever you might need the full value. It copies the full value, never the truncated text.
* The copy button is named Copy and the value by default. Compose `Hex.Root`, `Hex.Text`, and `Hex.Copy` with an `aria-label` such as Copy address when the value reads poorly.
* After a copy, the button shows Copied for two seconds and announces it politely. Don't add a `Toast` as well.
* Use `CopyButton` beside values you lay out yourself, such as a full address that wraps.
* Use `SecretValue` for private keys, API keys, and recovery codes. Never show a secret with `Hex`.

## Link to the Explorer

* Link transaction hashes, block numbers, and addresses to their page on the block explorer, [explore.tempo.xyz](https://explore.tempo.xyz).
* Beside a value, use an icon `Button.Link` with `shape="icon"` and the `ExternalLink` icon. Name it by destination, such as View transaction on explorer, and add a `Tooltip` with the same text.
* In a receipt, use a text `Button.Link` labelled View on explorer.
* Keep copying and linking as separate controls. The same text should never copy on one click and navigate on another.

## Amounts and Precision

Pick the precision by context. The same values render differently below.

```tsx
import { Amount, Surface, Table } from '@tempoxyz/ds'

export const fill = true
export function Demo() {
  return (
    <Surface size="medium">
      <Table.Root>
        <Table.Caption>
          One balance of 1250.504211 PathUSD and a 0.00003 fee, by context
        </Table.Caption>
        <Table.Head>
          <Table.Row>
            <Table.HeaderCell>Context</Table.HeaderCell>
            <Table.HeaderCell>Props</Table.HeaderCell>
            <Table.HeaderCell justify="end">Shows</Table.HeaderCell>
          </Table.Row>
        </Table.Head>
        <Table.Body>
          <Table.Row>
            <Table.Cell>Balance in a list</Table.Cell>
            <Table.Cell mono>currency="USD"</Table.Cell>
            <Table.Cell numeric>
              <Amount align="right" currency="USD" value="1250.504211" />
            </Table.Cell>
          </Table.Row>
          <Table.Row>
            <Table.Cell>Receipt or detail</Table.Cell>
            <Table.Cell mono>symbol="PathUSD"</Table.Cell>
            <Table.Cell numeric>
              <Amount align="right" symbol="PathUSD" value="1250.504211" />
            </Table.Cell>
          </Table.Row>
          <Table.Row>
            <Table.Cell>Compact token amount</Table.Cell>
            <Table.Cell mono>maxDecimals={'{2}'}</Table.Cell>
            <Table.Cell numeric>
              <Amount align="right" maxDecimals={2} symbol="PathUSD" value="1250.504211" />
            </Table.Cell>
          </Table.Row>
          <Table.Row>
            <Table.Cell>Fee in dollars</Table.Cell>
            <Table.Cell mono>currency="USD"</Table.Cell>
            <Table.Cell numeric>
              <Amount align="right" currency="USD" value="0.00003" />
            </Table.Cell>
          </Table.Row>
          <Table.Row>
            <Table.Cell>Fee on a review step</Table.Cell>
            <Table.Cell mono>currency="USD" precise</Table.Cell>
            <Table.Cell numeric>
              <Amount align="right" currency="USD" precise value="0.00003" />
            </Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table.Root>
    </Surface>
  )
}
```

[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/BlockchainPrecision.tsx)

* Pass amounts as decimal strings. TIP-20 tokens use 6 decimal places, so convert base units first, such as 12500000 to 12.5.
* In lists and balances, round to cents with `currency`, or cap token amounts with `maxDecimals`.
* In receipts and details, show the full amount with `symbol`. A string keeps every fraction digit you pass, including trailing zeros.
* For a currency value below one cent, such as a network fee, set `precise` to show $0.00003 instead of `<$0.01`.
* Never round a spendable balance up. `Amount` rounds to the nearest shown digit, so truncate the string first when it shows what you can send.
* Always pass `currency` or `symbol`, so the unit never depends on the surrounding text.
* Mark direction with `sign`, using `+` for incoming and `−` for outgoing, and say Received or Sent in text as well.

## Tokens

* Show the `Token` icon beside the token's symbol. Its `alt` is empty by default, which suits an icon next to its name. Pass the name as `alt` when the icon stands alone.
* Prefer `address` over `symbol`. A symbol lookup adds a request, and symbols aren't unique.
* Show the contract address in token details, so tokens with similar names stay distinguishable.
* Wrap lists in `Token.Provider`, so tokens with the same symbol share one lookup.

**Do:** Show the token's symbol as text beside its icon and amount.

**Don't:** Rely on the icon alone to say which token an amount is in.

## Block Numbers

* Group digits with the locale separator on screen, such as 18,204,551, and set them in tabular figures.
* Use the raw digits in copied values, URLs, and API calls, such as 18204551.
* Label the value Block in details rather than relying on a `#` prefix.

## Timestamps

* Render a `time` element with a machine-readable `dateTime`, such as `2026-10-20T09:41:07Z`.
* In activity lists, show relative times for the last day, such as 5 min ago, then dates, such as Oct 18.
* In receipts and details, show the absolute time with seconds and a time zone, such as Oct 20, 2026, 09:41:07 UTC.
* Show durations in milliseconds below one second, such as 633 ms, and in seconds above it.
* Refresh relative times on a timer without announcing each change.

## Hidden Amounts

A privacy toggle hides balances when someone might see your screen. Press the eye button below.

```tsx
import { Amount, Button, style, Surface, Token } from '@tempoxyz/ds'
import { Eye, EyeOff } from '@tempoxyz/ds/icons'
import { useState } from 'react'

export const fill = true
const balances = [
  ['PathUSD', '0x20c0000000000000000000000000000000000000', '1240.50'],
  ['USDC.e', '0x20c000000000000000000000b9537d11c60e8b50', '845.00'],
  ['USDT0', '0x20c00000000000000000000014f22ca97301eb73', '12.25'],
] as const

// One fixed mask for every amount, so hidden values reveal neither their size nor their digits.
function Hidden() {
  return (
    <span>
      <span aria-hidden="true">••••••</span>
      <span {...styles.hidden()}>Hidden</span>
    </span>
  )
}

export function Demo() {
  const [hidden, setHidden] = useState(false)

  return (
    <Surface render={<section aria-labelledby="balance-title" />} {...styles.card()}>
      <Surface.Header>
        <Surface.Title id="balance-title">Balance</Surface.Title>
        <Button
          aria-label="Hide amounts"
          aria-pressed={hidden}
          onClick={() => setHidden(!hidden)}
          shape="icon"
          size="small"
          variant="text"
        >
          {hidden ? <Eye /> : <EyeOff />}
        </Button>
      </Surface.Header>
      <div {...styles.total()}>
        {hidden ? <Hidden /> : <Amount currency="USD" size="xl" value="2097.75" />}
      </div>
      <ul {...styles.list()}>
        {balances.map(([symbol, address, value]) => (
          <li key={symbol} {...styles.item()}>
            <Token address={address} />
            <span {...styles.symbol()}>{symbol}</span>
            {hidden ? <Hidden /> : <Amount symbol={symbol} value={value} />}
          </li>
        ))}
      </ul>
    </Surface>
  )
}

namespace styles {
  export const card = style({
    boxSizing: 'border-box',
    marginInline: 'auto !custom',
    maxWidth: 'sm',
    width: '100% !custom',
  })

  export const hidden = style({
    clipPath: 'inset(50%) !custom',
    height: '1px !custom',
    overflow: 'hidden',
    position: 'absolute',
    whiteSpace: 'nowrap',
    width: '1px !custom',
  })

  export const item = style({
    alignItems: 'center',
    columnGap: '3',
    display: 'flex',
    minHeight: '10',
    typography: 'copy.14.strong',
  })

  export const list = style({ listStyle: 'none !custom', margin: '0', padding: '0' })

  export const symbol = style({ flex: '1 !custom' })

  export const total = style({
    fontVariantNumeric: 'tabular-nums !custom',
    marginBlockEnd: '4',
    minHeight: '12',
    typography: 'heading.40',
  })
}
```

[View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/BlockchainHiddenAmounts.tsx)

* Use an icon `Button` with `Eye` and `EyeOff`, named Hide amounts, and report its state with `aria-pressed`.
* Replace every amount with one fixed mask of six dots. A mask shaped like the number would reveal its size.
* Hide amounts everywhere they appear, including totals, activity, and charts.
* Render the mask in place of the value instead of blurring it, so the value isn't in the page at all.
* Give screen readers the word Hidden instead of the dots.
* Remember the choice on the device, so amounts stay hidden the next time the app opens.

**Do:** Use the same fixed mask for every hidden amount.

**Don't:** Shape the mask like the number, which reveals its size.

## States

* **Loading.** Wrap a pending value in `Skeleton` so the row keeps its height. A `Token` shows a gray circle while its icon loads.
* **Unknown token.** A `Token` with an unknown address or symbol keeps the gray circle. Show the symbol and the contract address as text.
* **Pending transaction.** Show the hash as soon as you have it, with a `Tag` such as Pending, and update the block and time when it confirms.
* **Copy failed.** `CopyButton` falls back to `document.execCommand('copy')`. If both fail, keep the value visible so it can be selected by hand.

## Accessibility

* `Hex` gives screen readers the full value through visually hidden text. Pair it with a name, since 42 characters read aloud are hard to follow.
* `Identicon` renders `role="img"` named by its address. Set `aria-hidden` when the address is adjacent, so it isn't announced twice.
* `CopyButton` announces Copied through a polite status region. Name it by what it copies, such as Copy transaction hash.
* Screen readers don't announce `strikethrough`. Say in text that a struck amount is superseded.
* Name icon-only explorer links by destination, and pair them with a `Tooltip` that repeats the name.
* Use a `time` element with `dateTime` for every timestamp, so assistive technology and translation tools read the exact time.
* The hidden amounts toggle exposes `aria-pressed`, and each mask reads as Hidden.

## Related

* [Hex](/docs/components/hex) for addresses and hashes
* [CopyButton](/docs/components/copy-button) for copying values
* [Amount](/docs/components/amount) for formatting amounts
* [Token](/docs/components/token) for token icons
* [Identicon](/docs/components/identicon) for address avatars
* [SecretValue](/docs/components/secret-value) for secrets
* [Payments](/docs/patterns/payments) for send, receive, and deposit flows
* [Tables and Lists](/docs/patterns/tables-and-lists) for columns of values
