diff --git a/apps/explorer/src/comps/TxTransactionCard.tsx b/apps/explorer/src/comps/TxTransactionCard.tsx
index 0c3a251c8..476f2e60d 100644
--- a/apps/explorer/src/comps/TxTransactionCard.tsx
+++ b/apps/explorer/src/comps/TxTransactionCard.tsx
@@ -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'
@@ -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]"
>
Receipt
-
+
+ View →
+
,
]}
/>
diff --git a/apps/explorer/src/lib/domain/known-events.ts b/apps/explorer/src/lib/domain/known-events.ts
index a3db80122..1115ec1d0 100644
--- a/apps/explorer/src/lib/domain/known-events.ts
+++ b/apps/explorer/src/lib/domain/known-events.ts
@@ -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 },
diff --git a/apps/explorer/src/lib/og-params.ts b/apps/explorer/src/lib/og-params.ts
index 3d42ef3ee..e5081f0aa 100644
--- a/apps/explorer/src/lib/og-params.ts
+++ b/apps/explorer/src/lib/og-params.ts
@@ -64,6 +64,8 @@ export interface AddressOgParams {
tokens?: string[]
accountType?: 'empty' | 'account' | 'contract'
methods?: string[]
+ deployer?: string
+ contractName?: string
}
// ============ Utility Functions ============
@@ -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()}`
diff --git a/apps/explorer/src/lib/og.ts b/apps/explorer/src/lib/og.ts
index dd798ab4b..6e9c22575 100644
--- a/apps/explorer/src/lib/og.ts
+++ b/apps/explorer/src/lib/og.ts
@@ -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 ''
}
@@ -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 ''
}
@@ -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,
@@ -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)
}
diff --git a/apps/explorer/src/routes/_layout/address/$address.tsx b/apps/explorer/src/routes/_layout/address/$address.tsx
index a9a1251a2..66693eeb0 100644
--- a/apps/explorer/src/routes/_layout/address/$address.tsx
+++ b/apps/explorer/src/routes/_layout/address/$address.tsx
@@ -493,6 +493,7 @@ export const Route = createFileRoute('/_layout/address/$address')({
txCount,
accountType,
lastActive,
+ contractName: loaderData?.contractInfo?.name,
})
}
diff --git a/apps/explorer/src/routes/_layout/block/$id.tsx b/apps/explorer/src/routes/_layout/block/$id.tsx
index a4876df8f..0295c5315 100644
--- a/apps/explorer/src/routes/_layout/block/$id.tsx
+++ b/apps/explorer/src/routes/_layout/block/$id.tsx
@@ -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'
@@ -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'
@@ -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
@@ -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({
@@ -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() {
diff --git a/apps/explorer/src/routes/_layout/blocks.tsx b/apps/explorer/src/routes/_layout/blocks.tsx
index b8a92fb89..d92d2c861 100644
--- a/apps/explorer/src/routes/_layout/blocks.tsx
+++ b/apps/explorer/src/routes/_layout/blocks.tsx
@@ -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' },
diff --git a/apps/explorer/src/routes/_layout/receipt/$hash.tsx b/apps/explorer/src/routes/_layout/receipt/$hash.tsx
index b98c71401..d440c2ac1 100644
--- a/apps/explorer/src/routes/_layout/receipt/$hash.tsx
+++ b/apps/explorer/src/routes/_layout/receipt/$hash.tsx
@@ -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 ??
@@ -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,
diff --git a/apps/explorer/src/routes/_layout/tokens.tsx b/apps/explorer/src/routes/_layout/tokens.tsx
index 00c40ed7d..6821c34ab 100644
--- a/apps/explorer/src/routes/_layout/tokens.tsx
+++ b/apps/explorer/src/routes/_layout/tokens.tsx
@@ -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' },
diff --git a/apps/explorer/src/routes/_layout/tx/$hash.tsx b/apps/explorer/src/routes/_layout/tx/$hash.tsx
index ab3c08851..513802479 100644
--- a/apps/explorer/src/routes/_layout/tx/$hash.tsx
+++ b/apps/explorer/src/routes/_layout/tx/$hash.tsx
@@ -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,
@@ -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,
@@ -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 },
],
}
},
diff --git a/apps/og/public/bg-default.webp b/apps/og/public/bg-default.webp
new file mode 100644
index 000000000..fce1f0a36
Binary files /dev/null and b/apps/og/public/bg-default.webp differ
diff --git a/apps/og/public/bg-list-blocks.webp b/apps/og/public/bg-list-blocks.webp
new file mode 100644
index 000000000..6308256fa
Binary files /dev/null and b/apps/og/public/bg-list-blocks.webp differ
diff --git a/apps/og/public/bg-list-tokens.webp b/apps/og/public/bg-list-tokens.webp
new file mode 100644
index 000000000..41de76deb
Binary files /dev/null and b/apps/og/public/bg-list-tokens.webp differ
diff --git a/apps/og/public/bg-template-address.webp b/apps/og/public/bg-template-address.webp
index aa31a29e7..941de66c4 100644
Binary files a/apps/og/public/bg-template-address.webp and b/apps/og/public/bg-template-address.webp differ
diff --git a/apps/og/public/bg-template-blocks.webp b/apps/og/public/bg-template-blocks.webp
new file mode 100644
index 000000000..a243d8507
Binary files /dev/null and b/apps/og/public/bg-template-blocks.webp differ
diff --git a/apps/og/public/bg-template-contract.webp b/apps/og/public/bg-template-contract.webp
index 86473401d..4d096ef8c 100644
Binary files a/apps/og/public/bg-template-contract.webp and b/apps/og/public/bg-template-contract.webp differ
diff --git a/apps/og/public/bg-template-receipt.webp b/apps/og/public/bg-template-receipt.webp
new file mode 100644
index 000000000..ff2d9b47c
Binary files /dev/null and b/apps/og/public/bg-template-receipt.webp differ
diff --git a/apps/og/public/bg-template-token.webp b/apps/og/public/bg-template-token.webp
index be8e03ccd..31e01f6ef 100644
Binary files a/apps/og/public/bg-template-token.webp and b/apps/og/public/bg-template-token.webp differ
diff --git a/apps/og/public/bg-template-transaction.webp b/apps/og/public/bg-template-transaction.webp
index 316bbbd6a..7dbe9e7a1 100644
Binary files a/apps/og/public/bg-template-transaction.webp and b/apps/og/public/bg-template-transaction.webp differ
diff --git a/apps/og/public/bg-template.webp b/apps/og/public/bg-template.webp
index db4a553ed..91385a695 100644
Binary files a/apps/og/public/bg-template.webp and b/apps/og/public/bg-template.webp differ
diff --git a/apps/og/public/fonts/Pilat-Book.otf b/apps/og/public/fonts/Pilat-Book.otf
new file mode 100644
index 000000000..1dcdd027a
Binary files /dev/null and b/apps/og/public/fonts/Pilat-Book.otf differ
diff --git a/apps/og/public/og-explorer.webp b/apps/og/public/og-explorer.webp
deleted file mode 100644
index d0bf223f9..000000000
Binary files a/apps/og/public/og-explorer.webp and /dev/null differ
diff --git a/apps/og/public/tempo-lockup.svg b/apps/og/public/tempo-lockup.svg
index d691373dc..b50aed19b 100644
--- a/apps/og/public/tempo-lockup.svg
+++ b/apps/og/public/tempo-lockup.svg
@@ -1,5 +1,7 @@
-