Skip to content

Commit 54303a1

Browse files
authored
fix(explorer): speed up address page loading (#1159)
* fix(explorer): speed up address page loading * fix(explorer): avoid token probes for accounts * fix(explorer): defer account type enrichment * fix(explorer): load address history after hydration
1 parent 905b092 commit 54303a1

4 files changed

Lines changed: 128 additions & 181 deletions

File tree

apps/explorer/src/comps/TxEventDescription.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,28 +64,26 @@ function ContractCallPart(props: {
6464
const selector = Hex.slice(input, 0, 4)
6565
const isViewingAsContract = seenAs && isAddressEqual(seenAs, address)
6666

67-
const { data: functionName, isLoading: isLoadingAbi } = useQuery({
68-
queryKey: ['contract-call-function', address, selector],
67+
const { data: abi, isLoading: isLoadingAbi } = useQuery({
68+
queryKey: ['contract-call-abi', address.toLowerCase()],
6969
queryFn: async () => {
7070
// Try known ABI first
71-
let abi = getContractAbi(address)
71+
const knownAbi = getContractAbi(address)
72+
if (knownAbi) return knownAbi
7273

7374
// Fall back to extracting from bytecode
74-
if (!abi) {
75-
abi = await extractContractAbi(address)
76-
}
77-
78-
if (!abi) return null
79-
80-
try {
81-
const decoded = decodeFunctionData({ abi, data: input })
82-
return decoded.functionName
83-
} catch {
84-
return null
85-
}
75+
return extractContractAbi(address)
8676
},
8777
staleTime: Number.POSITIVE_INFINITY,
8878
})
79+
const functionName = React.useMemo(() => {
80+
if (!abi) return null
81+
try {
82+
return decodeFunctionData({ abi, data: input }).functionName
83+
} catch {
84+
return null
85+
}
86+
}, [abi, input])
8987

9088
// Fall back to 4byte directory lookup
9189
const { data: signature, isFetched: isSignatureFetched } = useLookupSignature(

apps/explorer/src/lib/domain/contracts.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -883,13 +883,29 @@ const defaultSignatureLookup = new loaders.MultiSignatureLookup([
883883
export async function lookupSignature(
884884
selector: Hex.Hex,
885885
): Promise<string | null> {
886-
const signatures =
887-
selector.length === 10
888-
? await defaultSignatureLookup.loadFunctions(selector)
889-
: await defaultSignatureLookup.loadEvents(selector)
890-
return signatures[0] ?? null
886+
const key = selector.toLowerCase() as Hex.Hex
887+
const cached = signatureLookupCache.get(key)
888+
if (cached) return cached
889+
890+
const lookup = (async () => {
891+
const signatures =
892+
key.length === 10
893+
? await defaultSignatureLookup.loadFunctions(key)
894+
: await defaultSignatureLookup.loadEvents(key)
895+
return signatures[0] ?? null
896+
})()
897+
signatureLookupCache.set(key, lookup)
898+
899+
try {
900+
return await lookup
901+
} catch (error) {
902+
signatureLookupCache.delete(key)
903+
throw error
904+
}
891905
}
892906

907+
const signatureLookupCache = new Map<Hex.Hex, Promise<string | null>>()
908+
893909
class TempoABILoader {
894910
readonly name = 'TempoABILoader'
895911
readonly chainId: number

apps/explorer/src/lib/queries/abi.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,61 @@ export function lookupSignatureQueryOptions(args: { selector?: Hex }) {
9696
gcTime: Number.POSITIVE_INFINITY,
9797
staleTime: Number.POSITIVE_INFINITY,
9898
queryKey: ['lookup-signature', selector],
99-
queryFn: () => lookupSignature(selector as Hex),
99+
queryFn: () => lookupSignatureBatched(selector as Hex),
100100
})
101101
}
102102

103+
type SignatureRequest = {
104+
resolve: (signature: string | null) => void
105+
reject: (error: unknown) => void
106+
}
107+
108+
let pendingSignatureRequests = new Map<Hex, SignatureRequest[]>()
109+
let signatureBatchTimer: ReturnType<typeof setTimeout> | undefined
110+
111+
/**
112+
* Coalesce signature lookups started during the same render into one request.
113+
* Server-side callers use the in-process lookup directly; browser callers use
114+
* the batch endpoint so transaction lists do not fan out to OpenChain.
115+
*/
116+
function lookupSignatureBatched(selector: Hex): Promise<string | null> {
117+
if (typeof window === 'undefined') return lookupSignature(selector)
118+
119+
return new Promise((resolve, reject) => {
120+
const requests = pendingSignatureRequests.get(selector) ?? []
121+
requests.push({ resolve, reject })
122+
pendingSignatureRequests.set(selector, requests)
123+
124+
if (signatureBatchTimer) return
125+
signatureBatchTimer = setTimeout(flushSignatureBatch, 0)
126+
})
127+
}
128+
129+
async function flushSignatureBatch(): Promise<void> {
130+
const requests = pendingSignatureRequests
131+
pendingSignatureRequests = new Map()
132+
signatureBatchTimer = undefined
133+
134+
try {
135+
const response = await fetch(getApiUrl('/api/abi/batch'), {
136+
method: 'POST',
137+
headers: { 'Content-Type': 'application/json' },
138+
body: JSON.stringify({ addresses: [], selectors: [...requests.keys()] }),
139+
})
140+
if (!response.ok) throw new Error('Failed to fetch signature batch')
141+
142+
const data = (await response.json()) as BatchAbiResponse
143+
for (const [selector, callbacks] of requests) {
144+
const signature = data.signatures[selector.toLowerCase()] ?? null
145+
for (const callback of callbacks) callback.resolve(signature)
146+
}
147+
} catch (error) {
148+
for (const callbacks of requests.values()) {
149+
for (const callback of callbacks) callback.reject(error)
150+
}
151+
}
152+
}
153+
103154
export function useLookupSignature(args: {
104155
enabled?: boolean
105156
selector?: Hex

0 commit comments

Comments
 (0)