@@ -14,6 +14,118 @@ import { fetchTxFromExplorer } from '@/lib/explorer';
1414import { getServerSession } from '@/lib/auth-server' ;
1515import { getRpcUrlForChainOptimized } from '@/lib/public-network-utils' ;
1616
17+ type Pair = { key ?: string ; rpcUrl : string } ;
18+
19+ /**
20+ * Function to search transactions and contracts across multiple networks
21+ * TODO: mairjamijailovic think we do not need both GET and POST
22+ */
23+ async function searchTransactionsAndContracts (
24+ pairs : Pair [ ] ,
25+ hashAsHash : Hash ,
26+ hashAsAddress : Address | undefined ,
27+ authSession : AuthType [ 'session' ] | null
28+ ) : Promise < { transactions : SearchData [ ] ; contracts : SearchData [ ] } > {
29+ // Search transactions in parallel - RPC and explorer run concurrently
30+ const transactionPromises = pairs . map (
31+ async ( { rpcUrl, key } ) : Promise < SearchData | undefined > => {
32+ const client = createPublicClient ( { transport : http ( rpcUrl ) } ) ;
33+
34+ // Run RPC and explorer in parallel for faster results
35+ const rpcPromise = ( async ( ) => {
36+ try {
37+ const transaction = await client . getTransaction ( { hash : hashAsHash } ) ;
38+ let resolvedChainId : string ;
39+ if ( key ) {
40+ resolvedChainId = key ;
41+ } else {
42+ const numeric = await client . getChainId ( ) ;
43+ const chainKey = resolveChainId ( numeric , authSession ) ;
44+ resolvedChainId = chainKey || numeric . toString ( ) ;
45+ }
46+ return {
47+ source : { chainId : resolvedChainId , rpcUrl : undefined } ,
48+ hash : transaction . hash
49+ } ;
50+ } catch ( error ) {
51+ return undefined ;
52+ }
53+ } ) ( ) ;
54+
55+ // Explorer fallback - only if we have a key, run in parallel with RPC
56+ const explorerPromise = key
57+ ? ( async ( ) => {
58+ try {
59+ const explorer = await fetchTxFromExplorer ( key , hashAsHash ) ;
60+ if ( explorer ?. found ) {
61+ return {
62+ source : { chainId : key , rpcUrl : undefined } ,
63+ hash : hashAsHash
64+ } ;
65+ }
66+ return undefined ;
67+ } catch ( error ) {
68+ return undefined ;
69+ }
70+ } ) ( )
71+ : Promise . resolve ( undefined ) ;
72+
73+ // Race between RPC and explorer - return first successful result
74+ const [ rpcResult , explorerResult ] = await Promise . allSettled ( [ rpcPromise , explorerPromise ] ) ;
75+
76+ if ( rpcResult . status === 'fulfilled' && rpcResult . value ) {
77+ return rpcResult . value ;
78+ }
79+ if ( explorerResult . status === 'fulfilled' && explorerResult . value ) {
80+ return explorerResult . value ;
81+ }
82+
83+ return undefined ;
84+ }
85+ ) ;
86+
87+ // Use allSettled to avoid one slow network blocking others
88+ const transactionResults = await Promise . allSettled ( transactionPromises ) ;
89+ const transactions : SearchData [ ] = transactionResults
90+ . map ( ( result ) => ( result . status === 'fulfilled' ? result . value : undefined ) )
91+ . filter ( ( tx ) : tx is SearchData => tx !== undefined ) ;
92+
93+ // Search contracts in parallel
94+ const contracts : SearchData [ ] = hashAsAddress
95+ ? (
96+ await Promise . allSettled (
97+ pairs . map ( async ( { rpcUrl, key } ) : Promise < SearchData | undefined > => {
98+ const client = createPublicClient ( { transport : http ( rpcUrl ) } ) ;
99+ try {
100+ const bytecode = await client . getCode ( { address : hashAsAddress } ) ;
101+ if ( bytecode && bytecode !== '0x' ) {
102+ let resolvedChainId : string ;
103+ if ( key ) {
104+ resolvedChainId = key ;
105+ } else {
106+ const numeric = await client . getChainId ( ) ;
107+ const chainKey = resolveChainId ( numeric , authSession ) ;
108+ resolvedChainId = chainKey || numeric . toString ( ) ;
109+ }
110+ return {
111+ source : { chainId : resolvedChainId , rpcUrl : undefined } ,
112+ hash : hashAsAddress
113+ } ;
114+ }
115+ } catch ( error ) {
116+ // Ignore errors silently
117+ }
118+ return undefined ;
119+ } )
120+ )
121+ )
122+ . map ( ( result ) => ( result . status === 'fulfilled' ? result . value : undefined ) )
123+ . filter ( ( contract ) : contract is SearchData => contract !== undefined )
124+ : [ ] ;
125+
126+ return { transactions, contracts } ;
127+ }
128+
17129export const GET = async (
18130 request : NextRequest ,
19131 { params } : { params : Promise < { hash : string } > }
@@ -34,8 +146,8 @@ export const GET = async (
34146 const { hash } = await params ;
35147 const hashAsHash = hash as Hash ;
36148 const hashAsAddress = isAddress ( hash ) ? ( hash as Address ) : undefined ;
149+
37150 // Prefer chain keys sent via ?chains=KEY1,KEY2; fallback to rpc_urls; default to all enabled.
38- type Pair = { key ?: string ; rpcUrl : string } ;
39151 let pairs : Pair [ ] = [ ] ;
40152 if ( chainsParam ) {
41153 const keys = chainsParam
@@ -45,7 +157,6 @@ export const GET = async (
45157 const built : Pair [ ] = [ ] ;
46158 for ( const k of keys ) {
47159 try {
48- // Use optimized RPC URL resolution
49160 const url = getRpcUrlForChainOptimized ( k , authSession ?. session || null ) ;
50161 built . push ( { key : k , rpcUrl : url } ) ;
51162 } catch ( error ) {
@@ -98,72 +209,17 @@ export const GET = async (
98209 }
99210 }
100211
101- const transactions : ( SearchData | undefined ) [ ] = await Promise . all (
102- pairs . map ( async ( { rpcUrl, key } ) => {
103- const client = createPublicClient ( { transport : http ( rpcUrl ) } ) ;
104- try {
105- const transaction = await client . getTransaction ( { hash : hashAsHash } ) ;
106- let resolvedChainId : string ;
107- if ( key ) {
108- resolvedChainId = key ;
109- } else {
110- const numeric = await client . getChainId ( ) ;
111- // Use resolveChainId to support both static and tenant networks
112- const chainKey = resolveChainId ( numeric , authSession ?. session || null ) ;
113- resolvedChainId = chainKey || numeric . toString ( ) ;
114- }
115- return {
116- source : { chainId : resolvedChainId , rpcUrl : undefined } ,
117- hash : transaction . hash
118- } ;
119- } catch ( error ) {
120- // RPC miss → try explorer fallback for chains with explorer configured
121- if ( key ) {
122- const explorer = await fetchTxFromExplorer ( key , hashAsHash ) ;
123- if ( explorer ?. found ) {
124- return {
125- source : { chainId : key , rpcUrl : undefined } ,
126- hash : hashAsHash
127- } ;
128- }
129- }
130- const { name, message } = error as GetTransactionErrorType ;
131- console . error ( name , message ) ;
132- }
133- } )
212+ const { transactions, contracts } = await searchTransactionsAndContracts (
213+ pairs ,
214+ hashAsHash ,
215+ hashAsAddress ,
216+ authSession ?. session || null
134217 ) ;
135- const contracts : ( SearchData | undefined ) [ ] = hashAsAddress
136- ? await Promise . all (
137- pairs . map ( async ( { rpcUrl, key } ) => {
138- const client = createPublicClient ( { transport : http ( rpcUrl ) } ) ;
139- try {
140- const bytecode = await client . getCode ( { address : hashAsAddress } ) ;
141- if ( bytecode && bytecode !== '0x' ) {
142- let resolvedChainId : string ;
143- if ( key ) {
144- resolvedChainId = key ;
145- } else {
146- const numeric = await client . getChainId ( ) ;
147- // Use resolveChainId to support both static and tenant networks
148- const chainKey = resolveChainId ( numeric , authSession ?. session || null ) ;
149- resolvedChainId = chainKey || numeric . toString ( ) ;
150- }
151- return {
152- source : { chainId : resolvedChainId , rpcUrl : undefined } ,
153- hash : hashAsAddress
154- } ;
155- }
156- } catch ( error ) {
157- console . error ( 'getCode error' , error ) ;
158- }
159- } )
160- )
161- : [ ] ;
162218
163219 const response : SearchDataResponse = {
164- transactions : transactions . filter ( ( transaction : SearchData | undefined ) => ! ! transaction ) ,
220+ transactions,
165221 classes : [ ] ,
166- contracts : contracts . filter ( ( contract : SearchData | undefined ) => ! ! contract )
222+ contracts
167223 } ;
168224 return NextResponse . json ( response ) ;
169225} ;
@@ -184,7 +240,6 @@ export const POST = async (
184240 const body = ( await request . json ( ) ) as { chains ?: string [ ] } | undefined ;
185241 const chains = ( body ?. chains ?? [ ] ) . map ( ( c ) => c . trim ( ) ) . filter ( Boolean ) ;
186242
187- type Pair = { key ?: string ; rpcUrl : string } ;
188243 let pairs : Pair [ ] = [ ] ;
189244 if ( chains . length > 0 ) {
190245 const built : Pair [ ] = [ ] ;
@@ -228,73 +283,17 @@ export const POST = async (
228283 }
229284 }
230285
231- const transactions : ( SearchData | undefined ) [ ] = await Promise . all (
232- pairs . map ( async ( { rpcUrl, key } ) => {
233- const client = createPublicClient ( { transport : http ( rpcUrl ) } ) ;
234- try {
235- const transaction = await client . getTransaction ( { hash : hashAsHash } ) ;
236- let resolvedChainId : string ;
237- if ( key ) {
238- resolvedChainId = key ;
239- } else {
240- const numeric = await client . getChainId ( ) ;
241- // Use resolveChainId to support both static and tenant networks
242- const chainKey = resolveChainId ( numeric , authSession ?. session || null ) ;
243- resolvedChainId = chainKey || numeric . toString ( ) ;
244- }
245- return {
246- source : { chainId : resolvedChainId , rpcUrl : undefined } ,
247- hash : transaction . hash
248- } ;
249- } catch ( error ) {
250- if ( key ) {
251- const explorer = await fetchTxFromExplorer ( key , hashAsHash ) ;
252- if ( explorer ?. found ) {
253- return {
254- source : { chainId : key , rpcUrl : undefined } ,
255- hash : hashAsHash
256- } ;
257- }
258- }
259- const { name, message } = error as GetTransactionErrorType ;
260- console . error ( name , message ) ;
261- }
262- } )
286+ const { transactions, contracts } = await searchTransactionsAndContracts (
287+ pairs ,
288+ hashAsHash ,
289+ hashAsAddress ,
290+ authSession . session
263291 ) ;
264292
265- const contracts : ( SearchData | undefined ) [ ] =
266- hashAsAddress && pairs . length > 0
267- ? await Promise . all (
268- pairs . map ( async ( { rpcUrl, key } ) => {
269- const client = createPublicClient ( { transport : http ( rpcUrl ) } ) ;
270- try {
271- const bytecode = await client . getCode ( { address : hashAsAddress } ) ;
272- if ( bytecode && bytecode !== '0x' ) {
273- let resolvedChainId : string ;
274- if ( key ) {
275- resolvedChainId = key ;
276- } else {
277- const numeric = await client . getChainId ( ) ;
278- // Use resolveChainId to support both static and tenant networks
279- const chainKey = resolveChainId ( numeric , authSession ?. session || null ) ;
280- resolvedChainId = chainKey || numeric . toString ( ) ;
281- }
282- return {
283- source : { chainId : resolvedChainId , rpcUrl : undefined } ,
284- hash : hashAsAddress
285- } ;
286- }
287- } catch ( error ) {
288- console . error ( 'getCode error' , error ) ;
289- }
290- } )
291- )
292- : [ ] ;
293-
294293 const response : SearchDataResponse = {
295- transactions : transactions . filter ( ( transaction : SearchData | undefined ) => ! ! transaction ) ,
294+ transactions,
296295 classes : [ ] ,
297- contracts : contracts . filter ( ( contract : SearchData | undefined ) => ! ! contract )
296+ contracts
298297 } ;
299298 return NextResponse . json ( response ) ;
300299 } catch ( e ) {
0 commit comments