11import { Context , Effect , Either , RequestResolver , Request , Array , pipe } from 'effect'
2- import { ContractABI , GetContractABIStrategy } from './abi-strategy/request-model.js'
2+ import { ContractABI , ContractAbiResolverStrategy , GetContractABIStrategy } from './abi-strategy/request-model.js'
33
44const STRATEGY_TIMEOUT = 5000
55export interface AbiParams {
@@ -28,7 +28,7 @@ export type ContractAbiResult = ContractAbiSuccess | ContractAbiNotFound | Contr
2828
2929type ChainOrDefault = number | 'default'
3030export interface AbiStore < Key = AbiParams , Value = ContractAbiResult > {
31- readonly strategies : Record < ChainOrDefault , readonly RequestResolver . RequestResolver < GetContractABIStrategy > [ ] >
31+ readonly strategies : Record < ChainOrDefault , readonly ContractAbiResolverStrategy [ ] >
3232 readonly set : ( key : Key , value : Value ) => Effect . Effect < void , never >
3333 readonly get : ( arg : Key ) => Effect . Effect < Value , never >
3434 readonly getMany ?: ( arg : Array < Key > ) => Effect . Effect < Array < Value > , never >
@@ -46,7 +46,7 @@ export interface AbiLoader extends Request.Request<string | null, unknown> {
4646
4747const AbiLoader = Request . tagged < AbiLoader > ( 'AbiLoader' )
4848
49- function makeKey ( key : AbiLoader ) {
49+ function makeRequestKey ( key : AbiLoader ) {
5050 return `abi::${ key . chainID } :${ key . address } :${ key . event } :${ key . signature } `
5151}
5252
@@ -121,7 +121,7 @@ const getBestMatch = (abi: ContractABI | null) => {
121121 * To optimize concurrent requests, the AbiLoader uses the RequestResolver
122122 * to batch and cache requests. However, out-of-the-box, the RequestResolver does not
123123 * perform request deduplication. To address this, we implement request deduplication
124- * inside the resolver's body. We use the `makeKey ` function to generate a unique key
124+ * inside the resolver's body. We use the `makeRequestKey ` function to generate a unique key
125125 * for each request and group them by that key. We then load the ABI for the unique
126126 * requests and resolve the pending requests in a group with the same result.
127127 *
@@ -133,12 +133,11 @@ const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: Array<Ab
133133 if ( requests . length === 0 ) return
134134
135135 const { strategies } = yield * AbiStore
136- // NOTE: We can further optimize if we have match by Address by avoid extra requests for each signature
137- // but might need to update the Loader public API
138- const groups = Array . groupBy ( requests , makeKey )
139- const uniqueRequests = Object . values ( groups ) . map ( ( group ) => group [ 0 ] )
140136
141- const [ remaining , results ] = yield * pipe (
137+ const requestGroups = Array . groupBy ( requests , makeRequestKey )
138+ const uniqueRequests = Object . values ( requestGroups ) . map ( ( group ) => group [ 0 ] )
139+
140+ const [ remaining , cachedResults ] = yield * pipe (
142141 getMany ( uniqueRequests ) ,
143142 Effect . map (
144143 Array . partitionMap ( ( resp , i ) => {
@@ -152,9 +151,9 @@ const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: Array<Ab
152151
153152 // Resolve ABI from the store
154153 yield * Effect . forEach (
155- results ,
154+ cachedResults ,
156155 ( [ request , result ] ) => {
157- const group = groups [ makeKey ( request ) ]
156+ const group = requestGroups [ makeRequestKey ( request ) ]
158157 const abi = result ?. abi ?? null
159158 return Effect . forEach ( group , ( req ) => Request . succeed ( req , abi ) , { discard : true } )
160159 } ,
@@ -163,32 +162,64 @@ const AbiLoaderRequestResolver = RequestResolver.makeBatched((requests: Array<Ab
163162 } ,
164163 )
165164
166- // Load the ABI from the strategies
167- const strategyResults = yield * Effect . forEach ( remaining , ( { chainID, address, event, signature } ) => {
165+ // NOTE: Firstly we batch strategies by address because in a transaction most of events and traces are from the same abi
166+ const response = yield * Effect . forEach ( remaining , ( req ) => {
167+ const strategyRequest = GetContractABIStrategy ( {
168+ address : req . address ,
169+ chainID : req . chainID ,
170+ } )
171+
172+ const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ req . chainID ] ?? [ ] ) . filter (
173+ ( strategy ) => strategy . type === 'address' ,
174+ )
175+
176+ return Effect . validateFirst ( allAvailableStrategies , ( strategy ) =>
177+ pipe (
178+ Effect . request ( strategyRequest , strategy . resolver ) ,
179+ Effect . withRequestCaching ( true ) ,
180+ Effect . timeout ( STRATEGY_TIMEOUT ) ,
181+ ) ,
182+ ) . pipe (
183+ Effect . map ( Either . left ) ,
184+ Effect . orElseSucceed ( ( ) => Either . right ( req ) ) ,
185+ )
186+ } )
187+
188+ const [ addressStrategyResults , notFound ] = Array . partitionMap ( response , ( res ) => res )
189+
190+ // NOTE: Secondly we request strategies to fetch fragments
191+ const fragmentStrategyResults = yield * Effect . forEach ( notFound , ( { chainID, address, event, signature } ) => {
168192 const strategyRequest = GetContractABIStrategy ( {
169193 address,
194+ chainID,
170195 event,
171196 signature,
172- chainID,
173197 } )
174198
175- const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ chainID ] ?? [ ] )
199+ const allAvailableStrategies = Array . prependAll ( strategies . default , strategies [ chainID ] ?? [ ] ) . filter (
200+ ( strategy ) => strategy . type === 'fragment' ,
201+ )
176202
177203 // TODO: Distinct the errors and missing data, so we can retry on errors
178- return Effect . validateFirst ( allAvailableStrategies , ( strategy ) => Effect . request ( strategyRequest , strategy ) ) . pipe (
179- Effect . timeout ( STRATEGY_TIMEOUT ) ,
180- Effect . orElseSucceed ( ( ) => null ) ,
181- )
204+ return Effect . validateFirst ( allAvailableStrategies , ( strategy ) =>
205+ pipe (
206+ Effect . request ( strategyRequest , strategy . resolver ) ,
207+ Effect . withRequestCaching ( true ) ,
208+ Effect . timeout ( STRATEGY_TIMEOUT ) ,
209+ ) ,
210+ ) . pipe ( Effect . orElseSucceed ( ( ) => null ) )
182211 } )
183212
213+ const strategyResults = Array . appendAll ( addressStrategyResults , fragmentStrategyResults )
214+
184215 // Store results and resolve pending requests
185216 yield * Effect . forEach (
186217 strategyResults ,
187218 ( abi , i ) => {
188219 const request = remaining [ i ]
189220 const result = getBestMatch ( abi )
190221
191- const group = groups [ makeKey ( request ) ]
222+ const group = requestGroups [ makeRequestKey ( request ) ]
192223
193224 return Effect . zipRight (
194225 setValue ( request , abi ) ,
0 commit comments