-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathlog-decode.ts
More file actions
138 lines (119 loc) · 4.34 KB
/
log-decode.ts
File metadata and controls
138 lines (119 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { type GetTransactionReturnType, type Log, decodeEventLog, getAbiItem, getAddress } from 'viem'
import { Effect } from 'effect'
import type { DecodedLogEvent, Interaction, RawDecodedLog } from '../types.js'
import { getProxyImplementation } from './proxies.js'
import { getAndCacheAbi } from '../abi-loader.js'
import { getAndCacheContractMeta } from '../contract-meta-loader.js'
import * as AbiDecoder from './abi-decode.js'
import { stringify } from '../helpers/stringify.js'
import { formatAbiItem } from 'viem/utils'
function formatValue(arg: any): any {
if (arg == null) return null
if (Array.isArray(arg)) return arg.map(formatValue)
if (typeof arg === 'object') {
return Object.fromEntries(Object.entries(arg).map(([key, value]) => [key.toString(), value?.toString()]))
}
return arg.toString()
}
const decodedLog = (transaction: GetTransactionReturnType, logItem: Log) =>
Effect.gen(function* () {
const chainID = Number(transaction.chainId)
const address = getAddress(logItem.address)
const implementation = yield* getProxyImplementation({ address, chainID })
const abiAddress = implementation?.address ?? address
const [abiItem, contractData] = yield* Effect.all(
[
getAndCacheAbi({
address: abiAddress,
event: logItem.topics[0],
chainID,
}),
getAndCacheContractMeta({
address,
chainID: Number(transaction.chainId),
}),
],
{
concurrency: 'unbounded',
batching: true,
},
)
const { eventName, args: args_ } = yield* Effect.try({
try: () =>
decodeEventLog({
abi: abiItem,
topics: logItem.topics,
data: logItem.data,
strict: false,
}),
catch: (err) => new AbiDecoder.DecodeError(`Could not decode log ${abiAddress}`, err),
})
if (eventName == null) {
return yield* new AbiDecoder.DecodeError(`Could not decode log ${abiAddress}`)
}
const args = args_ as any
const fragment = yield* Effect.try({
try: () => getAbiItem({ abi: abiItem, name: eventName }),
catch: () => {
Effect.logError(`Could not find fragment in ABI ${abiAddress} ${eventName}`)
},
})
if (fragment == null) {
return yield* new AbiDecoder.DecodeError(`Could not find fragment in ABI ${abiAddress} ${eventName}`)
}
const decodedParams = yield* Effect.try({
try: () => {
if ('inputs' in fragment && fragment.inputs != null) {
return fragment.inputs.map((input, i) => {
if (input.name == null) return null
const arg = Array.isArray(args) ? args[i] : args[input.name]
const value = formatValue(arg)
return {
type: input.type,
name: input.name,
value,
} as DecodedLogEvent
})
}
return []
},
catch: () => {
Effect.logError(`Could not decode log params ${stringify(logItem)}`)
},
})
const textSignature = formatAbiItem(fragment)
const rawLog: RawDecodedLog = {
events: decodedParams.filter((x) => x != null) as DecodedLogEvent[],
name: eventName,
address,
logIndex: logItem.logIndex ?? -1,
signature: textSignature,
decoded: true,
}
const events = Object.fromEntries(rawLog.events.map((param) => [param.name, param.value]))
return {
contractName: contractData?.contractName || null,
contractSymbol: contractData?.tokenSymbol || null,
contractAddress: address,
decimals: contractData?.decimals || null,
chainID: Number(transaction.chainId),
contractType: contractData?.type ?? 'OTHER',
signature: rawLog.signature,
event: {
eventName: rawLog.name,
logIndex: rawLog.logIndex,
params: events,
...(!rawLog.decoded && { decoded: rawLog.decoded }),
},
} as Interaction
})
export const decodeLogs = ({ logs, transaction }: { logs: readonly Log[]; transaction: GetTransactionReturnType }) =>
Effect.gen(function* () {
const effects = logs.filter((log) => log.topics.length > 0).map((logItem) => decodedLog(transaction, logItem))
const eithers = effects.map((e) => Effect.either(e))
const resp = yield* Effect.all(eithers, {
concurrency: 'inherit',
batching: 'inherit',
})
return resp
})