Skip to content

Commit d35881b

Browse files
committed
Add experimental strategy to resolve erc20 abis
1 parent d8e71f7 commit d35881b

File tree

7 files changed

+96
-44
lines changed

7 files changed

+96
-44
lines changed

.changeset/lucky-crabs-float.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 expermiental erc20 abi strategy and change how sql strategy is defined

apps/web/src/lib/decode.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import { getProvider, RPCProviderLive } from './rpc-provider'
2-
import { Effect, Layer, ManagedRuntime } from 'effect'
2+
import { Config, Effect, Layer, ManagedRuntime } from 'effect'
33
import {
44
DecodedTransaction,
55
DecodeResult,
66
decodeCalldata as calldataDecoder,
77
decodeTransactionByHash,
88
EtherscanV2StrategyResolver,
9-
FetchTransactionError,
9+
ExperimentalErc20AbiStrategyResolver,
1010
FourByteStrategyResolver,
1111
OpenchainStrategyResolver,
12-
RPCFetchError,
1312
SourcifyStrategyResolver,
14-
UnknownNetwork,
15-
UnsupportedEvent,
1613
AbiStore,
1714
AbiParams,
1815
ContractAbiResult,
1916
ContractMetaStore,
2017
ContractMetaParams,
2118
ContractMetaResult,
2219
PublicClient,
20+
ERC20RPCStrategyResolver,
21+
NFTRPCStrategyResolver,
22+
ProxyRPCStrategyResolver,
2323
} from '@3loop/transaction-decoder'
2424
import { SqlAbiStore, SqlContractMetaStore } from '@3loop/transaction-decoder/sql'
2525
import { Hex } from 'viem'
@@ -29,19 +29,33 @@ import { SqlClient } from '@effect/sql/SqlClient'
2929
import { ConfigError } from 'effect/ConfigError'
3030
import { SqlError } from '@effect/sql/SqlError'
3131

32-
const AbiStoreLive = SqlAbiStore.make({
33-
default: [
34-
EtherscanV2StrategyResolver({
35-
apikey: process.env.ETHERSCAN_API_KEY,
36-
}),
37-
SourcifyStrategyResolver(),
38-
OpenchainStrategyResolver(),
39-
FourByteStrategyResolver(),
40-
],
41-
})
32+
const AbiStoreLive = Layer.unwrapEffect(
33+
Effect.gen(function* () {
34+
const service = yield* PublicClient
35+
const apikey = yield* Config.withDefault(Config.string('ETHERSCAN_API_KEY'), undefined)
36+
return SqlAbiStore.make({
37+
default: [
38+
EtherscanV2StrategyResolver({
39+
apikey: apikey,
40+
}),
41+
ExperimentalErc20AbiStrategyResolver(service),
42+
OpenchainStrategyResolver(),
43+
SourcifyStrategyResolver(),
44+
FourByteStrategyResolver(),
45+
],
46+
})
47+
}),
48+
)
4249

43-
const MetaStoreLive = SqlContractMetaStore.make()
50+
const MetaStoreLive = Layer.unwrapEffect(
51+
Effect.gen(function* () {
52+
const service = yield* PublicClient
4453

54+
return SqlContractMetaStore.make({
55+
default: [ERC20RPCStrategyResolver(service), NFTRPCStrategyResolver(service), ProxyRPCStrategyResolver(service)],
56+
})
57+
}),
58+
)
4559
const DataLayer = Layer.mergeAll(RPCProviderLive, DatabaseLive)
4660
const LoadersLayer = Layer.mergeAll(AbiStoreLive, MetaStoreLive)
4761
const MainLayer = Layer.provideMerge(LoadersLayer, DataLayer) as Layer.Layer<

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ const AbiLoaderRequestResolver: Effect.Effect<
224224
)
225225

226226
return Effect.validateFirst(allAvailableStrategies, (strategy) => {
227-
return Effect.request(strategyRequest, strategy.resolver)
227+
return pipe(Effect.request(strategyRequest, strategy.resolver), Effect.withRequestCaching(true))
228228
}).pipe(
229229
Effect.map(Either.left),
230230
Effect.orElseSucceed(() => Either.right(req)),
@@ -253,11 +253,7 @@ const AbiLoaderRequestResolver: Effect.Effect<
253253

254254
// TODO: Distinct the errors and missing data, so we can retry on errors
255255
return Effect.validateFirst(allAvailableStrategies, (strategy) =>
256-
pipe(
257-
Effect.request(strategyRequest, strategy.resolver),
258-
Effect.withRequestCaching(true),
259-
Effect.timeout(STRATEGY_TIMEOUT),
260-
),
256+
pipe(Effect.request(strategyRequest, strategy.resolver), Effect.withRequestCaching(true)),
261257
).pipe(Effect.orElseSucceed(() => null))
262258
})
263259

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as RequestModel from './request-model.js'
2+
import { Effect, RequestResolver } from 'effect'
3+
import { PublicClient } from '../public-client.js'
4+
import { erc20Abi, getAddress, getContract } from 'viem'
5+
6+
const getLocalFragments = (service: PublicClient, { address, chainID }: RequestModel.GetContractABIStrategy) =>
7+
Effect.gen(function* () {
8+
const client = yield* service
9+
.getPublicClient(chainID)
10+
.pipe(
11+
Effect.catchAll(() =>
12+
Effect.fail(new RequestModel.ResolveStrategyABIError('local-strategy', address, chainID)),
13+
),
14+
)
15+
16+
const inst = getContract({
17+
abi: erc20Abi,
18+
address: getAddress(address),
19+
client: client.client,
20+
})
21+
22+
const decimals = yield* Effect.tryPromise({
23+
try: () => inst.read.decimals(),
24+
catch: () => new RequestModel.ResolveStrategyABIError('local-strategy', address, chainID),
25+
})
26+
27+
if (decimals != null) {
28+
return [
29+
{
30+
type: 'address',
31+
address,
32+
chainID,
33+
abi: JSON.stringify(erc20Abi),
34+
},
35+
] as RequestModel.ContractABI[]
36+
}
37+
38+
return yield* Effect.fail(new RequestModel.ResolveStrategyABIError('local-strategy', address, chainID))
39+
})
40+
41+
export const ExperimentalErc20AbiStrategyResolver = (
42+
service: PublicClient,
43+
): RequestModel.ContractAbiResolverStrategy => {
44+
return {
45+
type: 'address',
46+
resolver: RequestResolver.fromEffect((req: RequestModel.GetContractABIStrategy) =>
47+
Effect.withSpan(getLocalFragments(service, req), 'AbiStrategy.ExperimentalErc20AbiStrategyResolver', {
48+
attributes: { chainID: req.chainID, address: req.address },
49+
}),
50+
),
51+
}
52+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './blockscout-abi.js'
22
export * from './etherscan-abi.js'
33
export * from './etherscanv2-abi.js'
4+
export * from './experimental-erc20.js'
45
export * from './fourbyte-abi.js'
56
export * from './openchain-abi.js'
67
export * from './request-model.js'

packages/transaction-decoder/src/contract-meta-loader.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ const ContractMetaLoaderRequestResolver = RequestResolver.makeBatched((requests:
164164
const allAvailableStrategies = Array.prependAll(strategies.default, strategies[chainID] ?? [])
165165

166166
// TODO: Distinct the errors and missing data, so we can retry on errors
167-
return Effect.validateFirst(allAvailableStrategies, (strategy) => Effect.request(strategyRequest, strategy)).pipe(
168-
Effect.orElseSucceed(() => null),
169-
)
167+
return Effect.validateFirst(allAvailableStrategies, (strategy) =>
168+
pipe(Effect.request(strategyRequest, strategy), Effect.withRequestCaching(true)),
169+
).pipe(Effect.orElseSucceed(() => null))
170170
})
171171

172172
// Store results and resolve pending requests

packages/transaction-decoder/src/sql/contract-meta-store.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
import { SqlClient } from '@effect/sql'
22
import { Effect, Layer } from 'effect'
3-
import {
4-
ContractData,
5-
ContractMetaStore,
6-
ERC20RPCStrategyResolver,
7-
NFTRPCStrategyResolver,
8-
ProxyRPCStrategyResolver,
9-
PublicClient,
10-
} from '../effect.js'
3+
import { ContractData, ContractMetaStore } from '../effect.js'
114

12-
export const make = () =>
5+
export const make = (strategies: ContractMetaStore['strategies']) =>
136
Layer.effect(
147
ContractMetaStore,
158
Effect.gen(function* () {
169
const sql = yield* SqlClient.SqlClient
17-
const publicClient = yield* PublicClient
18-
1910
const table = sql('_loop_decoder_contract_meta_')
2011

21-
// TODO; add timestamp to the table
2212
yield* sql`
2313
CREATE TABLE IF NOT EXISTS ${table} (
2414
address TEXT NOT NULL,
@@ -37,13 +27,7 @@ export const make = () =>
3727
)
3828

3929
return ContractMetaStore.of({
40-
strategies: {
41-
default: [
42-
ERC20RPCStrategyResolver(publicClient),
43-
NFTRPCStrategyResolver(publicClient),
44-
ProxyRPCStrategyResolver(publicClient),
45-
],
46-
},
30+
strategies,
4731
set: (key, value) =>
4832
Effect.gen(function* () {
4933
if (value.status === 'success') {

0 commit comments

Comments
 (0)