Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e7bd4fb
feat(og): dynamic OG rendering for all entity routes + block/receipt
achalvs Mar 18, 2026
21db7dc
chore(og): update background templates with new designs
achalvs Mar 24, 2026
b500468
chore(og): update background templates (v2)
achalvs Mar 24, 2026
1817479
chore(og): update background templates (v3)
achalvs Mar 24, 2026
1fae31c
feat(og): add empty/failed states for receipt events section
achalvs Mar 24, 2026
5976e9b
fix(explorer): decode contract call events in OG images + receipt UI …
achalvs Mar 24, 2026
6437d14
fix(og): replace fragment with div in events section for proper flex-…
achalvs Mar 24, 2026
219885c
Merge remote-tracking branch 'origin/main' into feat/static-og-images
achalvs Mar 25, 2026
5984263
fix(og): always show Fee row in receipt card, display "No fee" for ze…
achalvs Mar 25, 2026
0ca566b
feat(og): comprehensive OG card visual overhaul
achalvs Mar 27, 2026
73e7a37
fix(og): block card visual tweaks - smaller histogram, lighter fills,…
achalvs Mar 27, 2026
d0a95cd
Merge remote-tracking branch 'origin/main' into feat/static-og-images
achalvs Mar 27, 2026
6234520
fix(og): cdot rendering, status badge position, Call action for rever…
achalvs Mar 27, 2026
9910a5c
feat(og): SVG-based histogram + gas bar, explorer prev blocks loader
achalvs Mar 27, 2026
1b09d5f
chore: regenerate pnpm-lock.yaml with pnpm 10.32.1 to fix CI
achalvs Mar 27, 2026
7c8fd76
Merge remote-tracking branch 'origin/main' into feat/static-og-images
achalvs Apr 2, 2026
e2c9e88
chore: fix indentation after merge conflict resolution
achalvs Apr 2, 2026
477a4f2
fix(og): token card name weight, truncation length, holders display
achalvs Apr 3, 2026
e5f8277
chore(og): update background templates
achalvs Apr 3, 2026
2f8658d
Merge remote-tracking branch 'origin/main' into feat/static-og-images
achalvs Apr 3, 2026
d1daf95
fix(og): histogram polish - thinner bars, shorter height, remove divi…
achalvs Apr 3, 2026
0e5f3ed
feat(og): comprehensive visual polish pass
achalvs Apr 3, 2026
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
2 changes: 1 addition & 1 deletion apps/explorer/src/comps/Receipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function Receipt(props: Receipt.Props) {
<>
<div
data-receipt
className="flex flex-col w-[360px] bg-base-plane border border-base-border border-b-0 shadow-[0px_4px_44px_rgba(0,0,0,0.05)] rounded-[10px] rounded-br-none rounded-bl-none text-base-content"
className="flex flex-col w-[360px] bg-base-alt border border-base-border border-b-0 shadow-[0px_4px_44px_rgba(0,0,0,0.25)] rounded-[10px] rounded-br-none rounded-bl-none text-base-content"
>
<div className="flex items-start gap-[40px] px-[20px] pt-[24px] pb-[16px]">
<div className="shrink-0">
Expand Down
7 changes: 4 additions & 3 deletions apps/explorer/src/comps/TxTransactionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Link } from '@tanstack/react-router'
import type { Address, Hex } from 'ox'
import { InfoCard } from '#comps/InfoCard'
import { Midcut } from '#comps/Midcut'
import { ReceiptMark } from '#comps/ReceiptMark'
import { FormattedTimestamp, useTimeFormat } from '#comps/TimeFormat'
import { cx } from '#lib/css'
import { useCopy } from '#lib/hooks'
Expand Down Expand Up @@ -117,10 +116,12 @@ export function TxTransactionCard(props: TxTransactionCard.Props) {
key="receipt"
to="/receipt/$hash"
params={{ hash }}
className="press-down flex items-end justify-between w-full print:hidden py-[6px]"
className="press-down flex items-center justify-between w-full print:hidden py-[6px]"
>
<span className="text-[13px] text-tertiary">Receipt</span>
<ReceiptMark />
<span className="text-[12px] text-tertiary hover:text-primary px-[8px] py-[2px] border border-base-border rounded-full transition-colors">
View →
</span>
</Link>,
]}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/explorer/src/lib/domain/known-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,7 @@ function detectContractCall(
return {
type: 'contract call',
parts: [
{ type: 'action', value: failed ? 'Failed' : 'Call' },
{ type: 'action', value: 'Call' },
{
type: 'contractCall',
value: { address: Address.checksum(contractAddress), input: callInput },
Expand Down
5 changes: 5 additions & 0 deletions apps/explorer/src/lib/og-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export interface AddressOgParams {
tokens?: string[]
accountType?: 'empty' | 'account' | 'contract'
methods?: string[]
deployer?: string
contractName?: string
}

// ============ Utility Functions ============
Expand Down Expand Up @@ -177,6 +179,9 @@ export function buildAddressOgUrl(
.join(','),
)
}
if (params.deployer) search.set('deployer', params.deployer)
if (params.contractName)
search.set('contractName', truncateText(params.contractName, 64))
}

return `${baseUrl}/address/${params.address}?${search.toString()}`
Expand Down
14 changes: 14 additions & 0 deletions apps/explorer/src/lib/og.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ function formatPartForOgClient(part: KnownEventPart): string {
return HexFormatter.truncate(part.value)
case 'token':
return part.value.symbol || HexFormatter.truncate(part.value.address)
case 'contractCall': {
const selector = part.value.input.slice(0, 10)
const target = HexFormatter.truncate(part.value.address)
return `${selector} on ${target}`
}
default:
return ''
}
Expand Down Expand Up @@ -169,6 +174,11 @@ function formatEventPart(part: KnownEventPart): string {
}
case 'hex':
return HexFormatter.truncate(part.value)
case 'contractCall': {
const selector = part.value.input.slice(0, 10)
const target = HexFormatter.truncate(part.value.address)
return `${selector} on ${target}`
}
default:
return ''
}
Expand Down Expand Up @@ -342,6 +352,8 @@ export function buildAddressOgImageUrl(params: {
tokens?: string[]
accountType?: AccountType
methods?: string[]
deployer?: string
contractName?: string
}): string {
const ogParams: AddressOgParams = {
address: params.address,
Expand All @@ -356,6 +368,8 @@ export function buildAddressOgImageUrl(params: {
tokens: params.tokens,
accountType: params.accountType,
methods: params.methods,
deployer: params.deployer,
contractName: params.contractName,
}
return buildAddressOgUrl(OG_BASE_URL, ogParams)
}
Expand Down
1 change: 1 addition & 0 deletions apps/explorer/src/routes/_layout/address/$address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ export const Route = createFileRoute('/_layout/address/$address')({
txCount,
accountType,
lastActive,
contractName: loaderData?.contractInfo?.name,
})
}

Expand Down
87 changes: 85 additions & 2 deletions apps/explorer/src/routes/_layout/block/$id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as React from 'react'
import { decodeFunctionData, isHex, zeroAddress } from 'viem'
import { Abis } from 'viem/tempo'
import { useChains } from 'wagmi'
import { getBlock } from 'wagmi/actions'
import * as z from 'zod/mini'
import { Address as AddressLink } from '#comps/Address'
import { BlockCard } from '#comps/BlockCard'
Expand All @@ -26,6 +27,7 @@ import { TxEventDescription } from '#comps/TxEventDescription'
import { cx } from '#lib/css'
import type { KnownEvent } from '#lib/domain/known-events'
import { PriceFormatter } from '#lib/formatting.ts'
import { OG_BASE_URL } from '#lib/og'
import { withLoaderTiming } from '#lib/profiling'
import { useMediaQuery } from '#lib/hooks'
import { getFeeTokenForChain } from '#lib/tokenlist'
Expand All @@ -37,7 +39,7 @@ import {
TRANSACTIONS_PER_PAGE,
} from '#lib/queries'
import { fetchLatestBlock } from '#lib/server/latest-block.ts'
import { getTempoChain } from '#wagmi.config.ts'
import { getTempoChain, getWagmiConfig } from '#wagmi.config.ts'

const defaultSearchValues = { page: 1 } as const

Expand Down Expand Up @@ -88,9 +90,31 @@ export const Route = createFileRoute('/_layout/block/$id')({
blockRef = { kind: 'number', blockNumber: BigInt(parsedNumber) }
}

return await context.queryClient.ensureQueryData(
const result = await context.queryClient.ensureQueryData(
blockDetailQueryOptions(blockRef),
)

let prevBlockTxCounts: number[] | undefined
try {
if (result.block.number != null) {
const bn = result.block.number
const config = getWagmiConfig()
const prevBlocks = await Promise.all(
Array.from({ length: 7 }, (_, i) =>
getBlock(config, {
blockNumber: bn - BigInt(i + 1),
})
.then((b) => b.transactions.length)
.catch(() => 0),
),
)
prevBlockTxCounts = prevBlocks.reverse()
}
} catch {
// Ignore errors fetching prev blocks
}

return { ...result, prevBlockTxCounts }
} catch (error) {
console.error(error)
throw notFound({
Expand All @@ -101,6 +125,65 @@ export const Route = createFileRoute('/_layout/block/$id')({
})
}
}),
head: ({ params, loaderData }) => {
const blockNumber = loaderData?.block?.number
const title = blockNumber
? `Block ${blockNumber} \u22c5 Tempo Explorer`
: `Block ${params.id} \u22c5 Tempo Explorer`

const search = new URLSearchParams()
if (loaderData?.block) {
const block = loaderData.block
if (block.number != null) search.set('number', block.number.toString())

const date = new Date(Number(block.timestamp) * 1000)
const utc = `${String(date.getUTCMonth() + 1).padStart(2, '0')}/${String(date.getUTCDate()).padStart(2, '0')}/${String(date.getUTCFullYear()).slice(-2)} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}:${String(date.getUTCSeconds()).padStart(2, '0')}`
search.set('timestamp', utc)
search.set('unixTimestamp', block.timestamp.toString())
search.set('txCount', block.transactions.length.toString())
search.set('miner', block.miner)
if (block.parentHash) search.set('parentHash', block.parentHash)

const gasUsed = Number(block.gasUsed)
const gasLimit = Number(block.gasLimit)
const gasPercent =
gasLimit > 0 ? `${((gasUsed / gasLimit) * 100).toFixed(1)}%` : '0%'
search.set('gasUsage', gasPercent)

if (loaderData.prevBlockTxCounts) {
search.set('prevBlocks', loaderData.prevBlockTxCounts.join(','))
}
}

const ogImageUrl = `${OG_BASE_URL}/block/${params.id}?${search.toString()}`

let description = `View block ${params.id} on Tempo.`
if (loaderData?.block) {
const block = loaderData.block
const txCount = block.transactions.length
const gasUsed = Number(block.gasUsed)
const gasLimit = Number(block.gasLimit)
const gasPercent =
gasLimit > 0 ? `${((gasUsed / gasLimit) * 100).toFixed(1)}%` : '0%'
description = `Block ${block.number ?? params.id} · ${txCount} transaction${txCount !== 1 ? 's' : ''} · ${gasPercent} gas used. Explore block details on Tempo.`
}

return {
title,
meta: [
{ title },
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ name: 'twitter:description', content: description },
{ property: 'og:image', content: ogImageUrl },
{ property: 'og:image:type', content: 'image/webp' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:image', content: ogImageUrl },
],
}
},
})

function RouteComponent() {
Expand Down
5 changes: 4 additions & 1 deletion apps/explorer/src/routes/_layout/blocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ export const Route = createFileRoute('/_layout/blocks')({
property: 'og:description',
content: 'View the latest blocks on Tempo.',
},
{ property: 'og:image', content: `${OG_BASE_URL}/blocks` },
{
property: 'og:image',
content: `${OG_BASE_URL}/blocks`,
},
{ property: 'og:image:type', content: 'image/webp' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
Expand Down
3 changes: 1 addition & 2 deletions apps/explorer/src/routes/_layout/receipt/$hash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ export const Route = createFileRoute('/_layout/receipt/$hash')({
search.set('date', ogTimestamp.date)
search.set('time', ogTimestamp.time)

// Include fee so the OG receipt can render the Fee row.
const gasUsed = BigInt(loaderData.receipt.gasUsed ?? 0)
const gasPrice = BigInt(
loaderData.receipt.effectiveGasPrice ??
Expand All @@ -323,7 +322,7 @@ export const Route = createFileRoute('/_layout/receipt/$hash')({
)
}

const ogImageUrl = `${OG_BASE_URL}/tx/${params.hash}?${search.toString()}`
const ogImageUrl = `${OG_BASE_URL}/receipt/${params.hash}?${search.toString()}`

return {
title,
Expand Down
5 changes: 4 additions & 1 deletion apps/explorer/src/routes/_layout/tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ export const Route = createFileRoute('/_layout/tokens')({
property: 'og:description',
content: 'Browse all tokens on Tempo.',
},
{ property: 'og:image', content: `${OG_BASE_URL}/tokens` },
{
property: 'og:image',
content: `${OG_BASE_URL}/tokens`,
},
{ property: 'og:image:type', content: 'image/webp' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
Expand Down
20 changes: 8 additions & 12 deletions apps/explorer/src/routes/_layout/tx/$hash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import type { FeeBreakdownItem } from '#lib/domain/receipt'
import { isTip20Address } from '#lib/domain/tip20'
import { PriceFormatter } from '#lib/formatting'
import { useKeyboardShortcut, useMediaQuery } from '#lib/hooks'
import { buildOgImageUrl, buildTxDescription } from '#lib/og'
import { buildOgImageUrl, buildTxDescription, OG_BASE_URL } from '#lib/og'
import {
autoloadAbiQueryOptions,
LIMIT,
Expand Down Expand Up @@ -121,7 +121,7 @@ export const Route = createFileRoute('/_layout/tx/$hash')({
const title = `Transaction ${params.hash.slice(0, 10)}…${params.hash.slice(-6)} ⋅ Tempo Explorer`
const ogImageUrl = loaderData
? buildOgImageUrl(loaderData, params.hash)
: undefined
: `${OG_BASE_URL}/tx/${params.hash}`
const description = loaderData
? buildTxDescription({
timestamp: Number(loaderData.block.timestamp) * 1000,
Expand All @@ -137,16 +137,12 @@ export const Route = createFileRoute('/_layout/tx/$hash')({
{ property: 'og:title', content: title },
{ property: 'og:description', content: description },
{ name: 'twitter:description', content: description },
...(ogImageUrl
? [
{ property: 'og:image', content: ogImageUrl },
{ property: 'og:image:type', content: 'image/webp' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:image', content: ogImageUrl },
]
: []),
{ property: 'og:image', content: ogImageUrl },
{ property: 'og:image:type', content: 'image/webp' },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:image', content: ogImageUrl },
],
}
},
Expand Down
Binary file added apps/og/public/bg-default.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/og/public/bg-list-blocks.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/og/public/bg-list-tokens.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/og/public/bg-template-address.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/og/public/bg-template-blocks.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/og/public/bg-template-contract.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/og/public/bg-template-receipt.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/og/public/bg-template-token.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/og/public/bg-template-transaction.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/og/public/bg-template.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/og/public/fonts/Pilat-Book.otf
Binary file not shown.
Binary file removed apps/og/public/og-explorer.webp
Binary file not shown.
10 changes: 6 additions & 4 deletions apps/og/public/tempo-lockup.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed apps/og/public/tempo-lockup.webp
Binary file not shown.
Binary file added apps/og/public/tempo-mark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading