Skip to content

Commit 9b64972

Browse files
committed
fix(explorer): use Tempo API for address metadata
1 parent 54303a1 commit 9b64972

3 files changed

Lines changed: 137 additions & 275 deletions

File tree

apps/explorer/src/lib/server/address-metadata.ts

Lines changed: 51 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,8 @@ import { VirtualAddress } from 'ox/tempo'
55
import { getCode } from 'viem/actions'
66
import { type AccountType, getAccountType } from '#lib/account'
77
import { isTip20Address } from '#lib/domain/tip20'
8-
import {
9-
type ContractCreationData,
10-
fetchContractCreationData,
11-
} from '#lib/server/contract-creation'
128
import { api } from '#lib/server/tempo-api'
139
import {
14-
type ContractCreationReceiptRow,
15-
fetchAddressOldestTx,
16-
fetchAddressTxStats,
17-
fetchContractCreationReceipt,
1810
fetchTokenTransferBoundaries,
1911
fetchVirtualAddressTransferStats,
2012
} from '#lib/server/tempo-queries'
@@ -59,60 +51,69 @@ type AddressTxAggregate = {
5951
export function pickTip20CreatedTimestamp(params: {
6052
tokenCreatedTimestamp: unknown
6153
firstTransferTimestamp: unknown
62-
contractCreationTimestamp?: unknown
6354
}): number | undefined {
6455
const tokenCreatedTimestamp = parseTimestamp(params.tokenCreatedTimestamp)
6556
const firstTransferTimestamp = parseTimestamp(params.firstTransferTimestamp)
66-
const contractCreationTimestamp = parseTimestamp(
67-
params.contractCreationTimestamp,
68-
)
6957

7058
if (tokenCreatedTimestamp != null) return tokenCreatedTimestamp
71-
72-
return contractCreationTimestamp != null &&
73-
(firstTransferTimestamp == null ||
74-
contractCreationTimestamp < firstTransferTimestamp)
75-
? contractCreationTimestamp
76-
: firstTransferTimestamp
59+
return firstTransferTimestamp
7760
}
7861

79-
export function buildAddressTxMetadata(
80-
aggregate: AddressTxAggregate,
81-
creation: ContractCreationReceiptRow | ContractCreationData | undefined,
82-
): {
62+
export function buildAddressTxMetadata(aggregate: AddressTxAggregate): {
8363
txCount: number
8464
lastActivityTimestamp?: number
8565
createdTimestamp?: number
8666
createdTxHash?: string
8767
createdBy?: string
8868
} {
8969
const oldestTimestamp = parseTimestamp(aggregate.oldestTxsBlockTimestamp)
90-
const creationTimestamp = parseTimestamp(
91-
creation && 'block_timestamp' in creation
92-
? creation.block_timestamp
93-
: creation?.timestamp,
94-
)
95-
const useCreation =
96-
creationTimestamp != null &&
97-
(oldestTimestamp == null || creationTimestamp <= oldestTimestamp)
9870

9971
return {
100-
txCount: (aggregate.count ?? 0) + (creation ? 1 : 0),
72+
txCount: aggregate.count ?? 0,
10173
lastActivityTimestamp: parseTimestamp(aggregate.latestTxsBlockTimestamp),
102-
createdTimestamp:
103-
useCreation && creationTimestamp != null
104-
? creationTimestamp
105-
: oldestTimestamp,
106-
createdTxHash:
107-
useCreation && creation
108-
? 'tx_hash' in creation
109-
? creation.tx_hash
110-
: (creation.hash ?? undefined)
111-
: aggregate.oldestTxHash,
112-
createdBy:
113-
useCreation && creation
114-
? (creation.from ?? undefined)
115-
: aggregate.oldestTxFrom,
74+
createdTimestamp: oldestTimestamp,
75+
createdTxHash: aggregate.oldestTxHash,
76+
createdBy: aggregate.oldestTxFrom,
77+
}
78+
}
79+
80+
/** Address activity boundaries and count from structured Tempo API pages. */
81+
export async function fetchAddressTxMetadata(
82+
chainId: number,
83+
address: Address.Address,
84+
): Promise<AddressTxAggregate> {
85+
const [oldestPage, latestPage] = await Promise.all([
86+
parseResponse(
87+
api.v1.transactions.$get({
88+
query: {
89+
address,
90+
chainId: String(chainId),
91+
include: 'totalCount',
92+
limit: '5',
93+
order: 'asc',
94+
},
95+
}),
96+
),
97+
parseResponse(
98+
api.v1.transactions.$get({
99+
query: {
100+
address,
101+
chainId: String(chainId),
102+
limit: '5',
103+
order: 'desc',
104+
},
105+
}),
106+
),
107+
])
108+
const oldest = oldestPage.data[0]
109+
const latest = latestPage.data[0]
110+
111+
return {
112+
count: oldestPage.meta?.totalCount,
113+
latestTxsBlockTimestamp: latest?.timestamp,
114+
oldestTxsBlockTimestamp: oldest?.timestamp,
115+
oldestTxHash: oldest?.hash,
116+
oldestTxFrom: oldest?.sender,
116117
}
117118
}
118119

@@ -211,11 +212,6 @@ async function loadAddressMetadata(
211212
latestTimestamp: undefined,
212213
})),
213214
])
214-
const contractCreation =
215-
stats?.createdAt == null
216-
? await fetchContractCreationData(address).catch(() => null)
217-
: null
218-
219215
response = {
220216
address,
221217
chainId,
@@ -225,36 +221,17 @@ async function loadAddressMetadata(
225221
createdTimestamp: pickTip20CreatedTimestamp({
226222
tokenCreatedTimestamp: stats?.createdAt,
227223
firstTransferTimestamp: boundaries.oldestTimestamp,
228-
contractCreationTimestamp: contractCreation?.timestamp,
229224
}),
230225
}
231226
} else {
232-
// One aggregate (exact distinct count + boundaries) + the oldest
233-
// tx row for the "created by" stat. Creation receipt stays on
234-
// the SQL lane (D4.1) with the existing RPC bisection fallback.
235-
const [bytecode, stats, oldestTx, indexedCreation] = await Promise.all([
227+
// Structured Tempo API pages provide the first and latest indexed activity
228+
// without the historical RPC binary search previously used for contracts.
229+
const [bytecode, stats] = await Promise.all([
236230
bytecodePromise,
237-
fetchAddressTxStats(address, chainId),
238-
fetchAddressOldestTx(address, chainId).catch(() => undefined),
239-
fetchContractCreationReceipt(address, chainId).catch(() => undefined),
231+
fetchAddressTxMetadata(chainId, address),
240232
])
241233
const accountType = getAccountType(bytecode)
242-
const creation =
243-
indexedCreation ??
244-
(accountType === 'contract'
245-
? await fetchContractCreationData(address).catch(() => null)
246-
: undefined) ??
247-
undefined
248-
const metadata = buildAddressTxMetadata(
249-
{
250-
count: stats.count,
251-
latestTxsBlockTimestamp: stats.latestTimestamp,
252-
oldestTxsBlockTimestamp: stats.oldestTimestamp,
253-
oldestTxHash: oldestTx?.hash,
254-
oldestTxFrom: oldestTx?.from,
255-
},
256-
creation,
257-
)
234+
const metadata = buildAddressTxMetadata(stats)
258235

259236
response = {
260237
address,

apps/explorer/src/routes/_layout/address/$address.tsx

Lines changed: 2 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -630,65 +630,6 @@ function addressMetadataQueryOptions(address: Address.Address) {
630630
}
631631
}
632632

633-
type ContractCreationResponse = {
634-
creation: {
635-
blockNumber: string
636-
timestamp: string
637-
hash: Hex.Hex | null
638-
from: Address.Address | null
639-
to: Address.Address | null
640-
value: string | null
641-
status: 'success' | 'reverted' | null
642-
gasUsed: string | null
643-
effectiveGasPrice: string | null
644-
} | null
645-
error: string | null
646-
}
647-
648-
async function fetchContractCreation(
649-
address: Address.Address,
650-
): Promise<ContractCreationResponse> {
651-
const response = await fetch(`/api/contract/creation/${address}`)
652-
return response.json() as Promise<ContractCreationResponse>
653-
}
654-
655-
function buildContractCreationTransaction(params: {
656-
address: Address.Address
657-
creation: NonNullable<ContractCreationResponse['creation']>
658-
}): EnrichedTransaction | null {
659-
const { address, creation } = params
660-
661-
if (!creation.hash || !creation.from) return null
662-
663-
const blockNumber = parseOptionalBigInt(creation.blockNumber) ?? 0n
664-
const timestamp = parseOptionalBigInt(creation.timestamp) ?? 0n
665-
const value = parseOptionalBigInt(creation.value) ?? 0n
666-
const gasUsed = parseOptionalBigInt(creation.gasUsed) ?? 0n
667-
const effectiveGasPrice =
668-
parseOptionalBigInt(creation.effectiveGasPrice) ?? 0n
669-
670-
return {
671-
hash: creation.hash,
672-
blockNumber: Hex.fromNumber(blockNumber),
673-
timestamp: Number(timestamp),
674-
from: Address.checksum(creation.from),
675-
to: creation.to ? Address.checksum(creation.to) : null,
676-
value: Hex.fromNumber(value),
677-
status: creation.status ?? 'success',
678-
gasUsed: Hex.fromNumber(gasUsed),
679-
effectiveGasPrice: Hex.fromNumber(effectiveGasPrice),
680-
knownEvents: [
681-
{
682-
type: 'contract creation',
683-
parts: [
684-
{ type: 'action', value: 'Deploy Contract' },
685-
{ type: 'account', value: Address.checksum(address) },
686-
],
687-
},
688-
],
689-
}
690-
}
691-
692633
function AccountCardWithTimestamps(props: {
693634
address: Address.Address
694635
assetsData: AssetData[]
@@ -709,34 +650,11 @@ function AccountCardWithTimestamps(props: {
709650
} = props
710651

711652
const resolvedAccountType = addressMetadata?.accountType ?? initialAccountType
712-
const isContract = resolvedAccountType === 'contract'
713-
const missingCreated = addressMetadata?.createdTimestamp == null
714653
const isTip20 = Tip20.isTip20Address(address)
715-
716-
// Fall back to binary-search contract creation lookup when metadata has no
717-
// timestamp, and cross-check TIP-20 tokens whose first transfer can be later
718-
// than their creation block.
719-
const { data: contractCreation } = useQuery({
720-
queryKey: ['contract-creation', address],
721-
queryFn: () => fetchContractCreation(address),
722-
enabled: isContract && (missingCreated || isTip20),
723-
staleTime: 60_000,
724-
})
725-
726-
const metadataCreatedTimestamp =
654+
const createdTimestamp =
727655
addressMetadata?.createdTimestamp != null
728656
? BigInt(addressMetadata.createdTimestamp)
729657
: undefined
730-
const contractCreatedTimestamp =
731-
contractCreation?.creation?.timestamp != null
732-
? BigInt(contractCreation.creation.timestamp)
733-
: undefined
734-
const createdTimestamp =
735-
metadataCreatedTimestamp != null && contractCreatedTimestamp != null
736-
? metadataCreatedTimestamp <= contractCreatedTimestamp
737-
? metadataCreatedTimestamp
738-
: contractCreatedTimestamp
739-
: (metadataCreatedTimestamp ?? contractCreatedTimestamp)
740658

741659
const virtualAddressParts = getVirtualAddressParts(address)
742660
const { isTokenListed } = useTokenListMembership()
@@ -927,59 +845,10 @@ function SectionsWrapper(props: {
927845
: page === 1
928846
? initialData
929847
: historyQueryData
930-
const baseTransactions = historyData?.transactions ?? []
848+
const transactions = historyData?.transactions ?? []
931849
const hasMore = historyData?.hasMore ?? false
932850
const total = historyData?.total
933851
const countCapped = historyData?.countCapped ?? false
934-
const shouldFetchContractCreation =
935-
isMounted &&
936-
isContract &&
937-
isTransactionsTabActive &&
938-
historyData !== undefined &&
939-
!hasMore
940-
941-
const { data: contractCreationData } = useQuery({
942-
queryKey: ['contract-creation', address],
943-
queryFn: () => fetchContractCreation(address),
944-
enabled: shouldFetchContractCreation,
945-
staleTime: 60_000,
946-
})
947-
948-
const transactions = React.useMemo(() => {
949-
if (!isTransactionsTabActive || !isContract) return baseTransactions
950-
if (hasMore) return baseTransactions
951-
952-
const creation = contractCreationData?.creation
953-
if (!creation) return baseTransactions
954-
955-
const creationTransaction = buildContractCreationTransaction({
956-
address,
957-
creation,
958-
})
959-
if (!creationTransaction) return baseTransactions
960-
961-
// Don't append if it doesn't match the active status filter
962-
if (status && creationTransaction.status !== status) return baseTransactions
963-
964-
if (
965-
baseTransactions.some(
966-
(transaction) =>
967-
transaction.hash.toLowerCase() ===
968-
creationTransaction.hash.toLowerCase(),
969-
)
970-
)
971-
return baseTransactions
972-
973-
return [...baseTransactions, creationTransaction]
974-
}, [
975-
address,
976-
baseTransactions,
977-
contractCreationData?.creation,
978-
hasMore,
979-
isContract,
980-
isTransactionsTabActive,
981-
status,
982-
])
983852

984853
// Token transfers query
985854
const transfersPage = isTransfersTabActive ? page : 1

0 commit comments

Comments
 (0)