Skip to content

Commit e20cc27

Browse files
committed
Add an unique id for each strategy and use it in request param
1 parent 5fdb9b6 commit e20cc27

16 files changed

+156
-105
lines changed

.changeset/swift-taxis-impress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@3loop/transaction-decoder': minor
3+
---
4+
5+
Add an id to each strategy to make each request to a strategy unique. Effect will cache the request in a single global cache, thus to avoid the same request of being cached across different strategies we added an unique id that will identify each request.

packages/transaction-decoder/src/abi-loader.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,22 @@ const AbiLoaderRequestResolver: Effect.Effect<
214214
const response = yield* Effect.forEach(
215215
remaining,
216216
(req) => {
217-
const strategyRequest = new GetContractABIStrategy({
218-
address: req.address,
219-
chainID: req.chainID,
220-
})
221-
222217
const allAvailableStrategies = Array.prependAll(strategies.default, strategies[req.chainID] ?? []).filter(
223218
(strategy) => strategy.type === 'address',
224219
)
225220

226221
return Effect.validateFirst(allAvailableStrategies, (strategy) => {
227-
return pipe(Effect.request(strategyRequest, strategy.resolver), Effect.withRequestCaching(false))
222+
return pipe(
223+
Effect.request(
224+
new GetContractABIStrategy({
225+
address: req.address,
226+
chainId: req.chainID,
227+
strategyId: strategy.id,
228+
}),
229+
strategy.resolver,
230+
),
231+
Effect.withRequestCaching(true),
232+
)
228233
}).pipe(
229234
Effect.map(Either.left),
230235
Effect.orElseSucceed(() => Either.right(req)),
@@ -239,23 +244,35 @@ const AbiLoaderRequestResolver: Effect.Effect<
239244
const [addressStrategyResults, notFound] = Array.partitionMap(response, (res) => res)
240245

241246
// NOTE: Secondly we request strategies to fetch fragments
242-
const fragmentStrategyResults = yield* Effect.forEach(notFound, ({ chainID, address, event, signature }) => {
243-
const strategyRequest = new GetContractABIStrategy({
244-
address,
245-
chainID,
246-
event,
247-
signature,
248-
})
249-
250-
const allAvailableStrategies = Array.prependAll(strategies.default, strategies[chainID] ?? []).filter(
251-
(strategy) => strategy.type === 'fragment',
252-
)
247+
const fragmentStrategyResults = yield* Effect.forEach(
248+
notFound,
249+
({ chainID, address, event, signature }) => {
250+
const allAvailableStrategies = Array.prependAll(strategies.default, strategies[chainID] ?? []).filter(
251+
(strategy) => strategy.type === 'fragment',
252+
)
253253

254-
// TODO: Distinct the errors and missing data, so we can retry on errors
255-
return Effect.validateFirst(allAvailableStrategies, (strategy) =>
256-
pipe(Effect.request(strategyRequest, strategy.resolver), Effect.withRequestCaching(false)),
257-
).pipe(Effect.orElseSucceed(() => null))
258-
})
254+
// TODO: Distinct the errors and missing data, so we can retry on errors
255+
return Effect.validateFirst(allAvailableStrategies, (strategy) =>
256+
pipe(
257+
Effect.request(
258+
new GetContractABIStrategy({
259+
address,
260+
chainId: chainID,
261+
event,
262+
signature,
263+
strategyId: strategy.id,
264+
}),
265+
strategy.resolver,
266+
),
267+
Effect.withRequestCaching(true),
268+
),
269+
).pipe(Effect.orElseSucceed(() => null))
270+
},
271+
{
272+
concurrency: 'unbounded',
273+
batching: true,
274+
},
275+
)
259276

260277
const strategyResults = Array.appendAll(addressStrategyResults, fragmentStrategyResults)
261278

@@ -299,7 +316,7 @@ export const getAndCacheAbi = (params: AbiParams) =>
299316
}).pipe(
300317
Effect.withSpan('AbiLoader.GetAndCacheAbi', {
301318
attributes: {
302-
chainID: params.chainID,
319+
chainId: params.chainID,
303320
address: params.address,
304321
event: params.event,
305322
signature: params.signature,

packages/transaction-decoder/src/abi-strategy/blockscout-abi.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Effect, RequestResolver } from 'effect'
22
import * as RequestModel from './request-model.js'
33

44
async function fetchContractABI(
5-
{ address, chainID }: RequestModel.GetContractABIStrategy,
5+
{ address, chainId }: RequestModel.GetContractABIStrategy,
66
config: { apikey?: string; endpoint: string },
77
): Promise<RequestModel.ContractABI[]> {
88
const endpoint = config.endpoint
@@ -25,31 +25,32 @@ async function fetchContractABI(
2525
if (json.status === '1') {
2626
return [
2727
{
28-
chainID,
28+
chainID: chainId,
2929
address,
3030
abi: json.result,
3131
type: 'address',
3232
},
3333
]
3434
}
3535

36-
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainID}`)
36+
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainId}`)
3737
}
3838

3939
export const BlockscoutStrategyResolver = (config: {
4040
apikey?: string
4141
endpoint: string
4242
}): RequestModel.ContractAbiResolverStrategy => {
4343
return {
44+
id: 'blockscout-strategy',
4445
type: 'address',
4546
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
4647
Effect.withSpan(
4748
Effect.tryPromise({
4849
try: () => fetchContractABI(req, config),
49-
catch: () => new RequestModel.ResolveStrategyABIError('Blockscout', req.address, req.chainID),
50+
catch: () => new RequestModel.ResolveStrategyABIError('Blockscout', req.address, req.chainId),
5051
}),
5152
'AbiStrategy.BlockscoutStrategyResolver',
52-
{ attributes: { chainID: req.chainID, address: req.address } },
53+
{ attributes: { chainId: req.chainId, address: req.address } },
5354
),
5455
),
5556
}

packages/transaction-decoder/src/abi-strategy/etherscan-abi.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ const endpoints: { [k: number]: string } = {
4848
}
4949

5050
async function fetchContractABI(
51-
{ address, chainID }: RequestModel.GetContractABIStrategy,
51+
{ address, chainId }: RequestModel.GetContractABIStrategy,
5252
config?: { apikey?: string; endpoint?: string },
5353
): Promise<RequestModel.ContractABI[]> {
54-
const endpoint = config?.endpoint ?? endpoints[chainID]
54+
const endpoint = config?.endpoint ?? endpoints[chainId]
5555
const params: Record<string, string> = {
5656
module: 'contract',
5757
action: 'getabi',
@@ -72,29 +72,30 @@ async function fetchContractABI(
7272
{
7373
type: 'address',
7474
address,
75-
chainID,
75+
chainID: chainId,
7676
abi: json.result,
7777
},
7878
]
7979
}
8080

81-
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainID}`)
81+
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainId}`)
8282
}
8383

8484
export const EtherscanStrategyResolver = (config?: {
8585
apikey?: string
8686
endpoint?: string
8787
}): RequestModel.ContractAbiResolverStrategy => {
8888
return {
89+
id: 'etherscan-strategy',
8990
type: 'address',
9091
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
9192
Effect.withSpan(
9293
Effect.tryPromise({
9394
try: () => fetchContractABI(req, config),
94-
catch: () => new RequestModel.ResolveStrategyABIError('etherscan', req.address, req.chainID),
95+
catch: () => new RequestModel.ResolveStrategyABIError('etherscan', req.address, req.chainId),
9596
}),
9697
'AbiStrategy.EtherscanStrategyResolver',
97-
{ attributes: { chainID: req.chainID, address: req.address } },
98+
{ attributes: { chainId: req.chainId, address: req.address } },
9899
),
99100
),
100101
}

packages/transaction-decoder/src/abi-strategy/etherscanv2-abi.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import * as RequestModel from './request-model.js'
44
const endpoint = 'https://api.etherscan.io/v2/api'
55

66
async function fetchContractABI(
7-
{ address, chainID }: RequestModel.GetContractABIStrategy,
7+
{ address, chainId }: RequestModel.GetContractABIStrategy,
88
config?: { apikey?: string },
99
): Promise<RequestModel.ContractABI[]> {
1010
const params: Record<string, string> = {
1111
module: 'contract',
1212
action: 'getabi',
13-
chainId: chainID.toString(),
13+
chainId: chainId.toString(),
1414
address,
1515
}
1616

@@ -28,26 +28,27 @@ async function fetchContractABI(
2828
{
2929
type: 'address',
3030
address,
31-
chainID,
31+
chainID: chainId,
3232
abi: json.result,
3333
},
3434
]
3535
}
3636

37-
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainID}`)
37+
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainId}`)
3838
}
3939

4040
export const EtherscanV2StrategyResolver = (config?: { apikey?: string }): RequestModel.ContractAbiResolverStrategy => {
4141
return {
42+
id: 'etherscanV2-strategy',
4243
type: 'address',
4344
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
4445
Effect.withSpan(
4546
Effect.tryPromise({
4647
try: () => fetchContractABI(req, config),
47-
catch: () => new RequestModel.ResolveStrategyABIError('etherscanV2', req.address, req.chainID),
48+
catch: () => new RequestModel.ResolveStrategyABIError('etherscanV2', req.address, req.chainId),
4849
}),
4950
'AbiStrategy.EtherscanV2StrategyResolver',
50-
{ attributes: { chainID: req.chainID, address: req.address } },
51+
{ attributes: { chainId: req.chainId, address: req.address } },
5152
),
5253
),
5354
}

packages/transaction-decoder/src/abi-strategy/experimental-erc20.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { Effect, RequestResolver } from 'effect'
33
import { PublicClient } from '../public-client.js'
44
import { erc20Abi, getAddress, getContract } from 'viem'
55

6-
const getLocalFragments = (service: PublicClient, { address, chainID }: RequestModel.GetContractABIStrategy) =>
6+
const getLocalFragments = (service: PublicClient, { address, chainId }: RequestModel.GetContractABIStrategy) =>
77
Effect.gen(function* () {
88
const client = yield* service
9-
.getPublicClient(chainID)
9+
.getPublicClient(chainId)
1010
.pipe(
1111
Effect.catchAll(() =>
12-
Effect.fail(new RequestModel.ResolveStrategyABIError('local-strategy', address, chainID)),
12+
Effect.fail(new RequestModel.ResolveStrategyABIError('local-strategy', address, chainId)),
1313
),
1414
)
1515

@@ -21,31 +21,32 @@ const getLocalFragments = (service: PublicClient, { address, chainID }: RequestM
2121

2222
const decimals = yield* Effect.tryPromise({
2323
try: () => inst.read.decimals(),
24-
catch: () => new RequestModel.ResolveStrategyABIError('local-strategy', address, chainID),
24+
catch: () => new RequestModel.ResolveStrategyABIError('local-strategy', address, chainId),
2525
})
2626

2727
if (decimals != null) {
2828
return [
2929
{
3030
type: 'address',
3131
address,
32-
chainID,
32+
chainID: chainId,
3333
abi: JSON.stringify(erc20Abi),
3434
},
3535
] as RequestModel.ContractABI[]
3636
}
3737

38-
return yield* Effect.fail(new RequestModel.ResolveStrategyABIError('local-strategy', address, chainID))
38+
return yield* Effect.fail(new RequestModel.ResolveStrategyABIError('local-strategy', address, chainId))
3939
})
4040

4141
export const ExperimentalErc20AbiStrategyResolver = (
4242
service: PublicClient,
4343
): RequestModel.ContractAbiResolverStrategy => {
4444
return {
45+
id: 'experimental-erc20-strategy',
4546
type: 'address',
4647
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
4748
Effect.withSpan(getLocalFragments(service, req), 'AbiStrategy.ExperimentalErc20AbiStrategyResolver', {
48-
attributes: { chainID: req.chainID, address: req.address },
49+
attributes: { chainId: req.chainId, address: req.address },
4950
}),
5051
),
5152
}

packages/transaction-decoder/src/abi-strategy/fourbyte-abi.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async function fetchABI({
2424
address,
2525
event,
2626
signature,
27-
chainID,
27+
chainId,
2828
}: RequestModel.GetContractABIStrategy): Promise<RequestModel.ContractABI[]> {
2929
if (signature != null) {
3030
const full_match = await fetch(`${endpoint}/signatures/?hex_signature=${signature}`)
@@ -34,7 +34,7 @@ async function fetchABI({
3434
return json.results.map((result) => ({
3535
type: 'func',
3636
address,
37-
chainID,
37+
chainID: chainId,
3838
abi: parseFunctionSignature(result.text_signature),
3939
signature,
4040
}))
@@ -48,27 +48,28 @@ async function fetchABI({
4848
return json.results.map((result) => ({
4949
type: 'event',
5050
address,
51-
chainID,
51+
chainID: chainId,
5252
abi: parseEventSignature(result.text_signature),
5353
event,
5454
}))
5555
}
5656
}
5757

58-
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainID}`)
58+
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainId}`)
5959
}
6060

6161
export const FourByteStrategyResolver = (): RequestModel.ContractAbiResolverStrategy => {
6262
return {
63+
id: 'fourbyte-strategy',
6364
type: 'fragment',
6465
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
6566
Effect.withSpan(
6667
Effect.tryPromise({
6768
try: () => fetchABI(req),
68-
catch: () => new RequestModel.ResolveStrategyABIError('4byte.directory', req.address, req.chainID),
69+
catch: () => new RequestModel.ResolveStrategyABIError('4byte.directory', req.address, req.chainId),
6970
}),
7071
'AbiStrategy.FourByteStrategyResolver',
71-
{ attributes: { chainID: req.chainID, address: req.address } },
72+
{ attributes: { chainId: req.chainId, address: req.address } },
7273
),
7374
),
7475
}

packages/transaction-decoder/src/abi-strategy/openchain-abi.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function parseEventSignature(signature: string): string {
4040

4141
async function fetchABI({
4242
address,
43-
chainID,
43+
chainId,
4444
signature,
4545
event,
4646
}: RequestModel.GetContractABIStrategy): Promise<RequestModel.ContractABI[]> {
@@ -52,7 +52,7 @@ async function fetchABI({
5252
return json.result.function[signature].map((f) => ({
5353
type: 'func',
5454
address,
55-
chainID,
55+
chainID: chainId,
5656
abi: parseFunctionSignature(f.name),
5757
signature,
5858
}))
@@ -66,27 +66,28 @@ async function fetchABI({
6666
return json.result.event[event].map((e) => ({
6767
type: 'event',
6868
address,
69-
chainID,
69+
chainID: chainId,
7070
abi: parseEventSignature(e.name),
7171
event,
7272
}))
7373
}
7474
}
7575

76-
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainID}`)
76+
throw new Error(`Failed to fetch ABI for ${address} on chain ${chainId}`)
7777
}
7878

7979
export const OpenchainStrategyResolver = (): RequestModel.ContractAbiResolverStrategy => {
8080
return {
81+
id: 'openchain-strategy',
8182
type: 'fragment',
8283
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
8384
Effect.withSpan(
8485
Effect.tryPromise({
8586
try: () => fetchABI(req),
86-
catch: () => new RequestModel.ResolveStrategyABIError('openchain', req.address, req.chainID),
87+
catch: () => new RequestModel.ResolveStrategyABIError('openchain', req.address, req.chainId),
8788
}),
8889
'AbiStrategy.OpenchainStrategyResolver',
89-
{ attributes: { chainID: req.chainID, address: req.address } },
90+
{ attributes: { chainId: req.chainId, address: req.address } },
9091
),
9192
),
9293
}

0 commit comments

Comments
 (0)