@subsquid/evm-typegen 4.5.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,315 @@
1
+ import {closest} from 'fastest-levenshtein'
2
+ import * as validator from '@subsquid/util-internal-commander'
3
+ import {InvalidOptionArgumentError} from 'commander'
4
+
5
+ // ref: https://docs.etherscan.io/supported-chains
6
+ export const CHAIN_IDS: Record<string, number> = {
7
+ // Ethereum Mainnet
8
+ 'ethereum-mainnet': 1,
9
+ 'mainnet': 1,
10
+ 'ethereum': 1,
11
+ 'eth': 1,
12
+
13
+ // Sepolia Testnet
14
+ 'sepolia-testnet': 11155111,
15
+ 'sepolia': 11155111,
16
+
17
+ // Holesky Testnet
18
+ 'holesky-testnet': 17000,
19
+ 'holesky': 17000,
20
+
21
+ // Hoodi Testnet
22
+ 'hoodi-testnet': 560048,
23
+ 'hoodi': 560048,
24
+
25
+ // BNB Smart Chain Mainnet
26
+ 'bnb-smart-chain-mainnet': 56,
27
+ 'bsc': 56,
28
+ 'bnb': 56,
29
+
30
+ // BNB Smart Chain Testnet
31
+ 'bnb-smart-chain-testnet': 97,
32
+ 'bsc-testnet': 97,
33
+
34
+ // Polygon Mainnet
35
+ 'polygon-mainnet': 137,
36
+ 'polygon': 137,
37
+ 'matic': 137,
38
+
39
+ // Polygon Amoy Testnet
40
+ 'polygon-amoy-testnet': 80002,
41
+ 'amoy': 80002,
42
+ 'polygon-amoy': 80002,
43
+
44
+ // Base Mainnet
45
+ 'base-mainnet': 8453,
46
+ 'base': 8453,
47
+
48
+ // Base Sepolia Testnet
49
+ 'base-sepolia-testnet': 84532,
50
+ 'base-sepolia': 84532,
51
+
52
+ // Arbitrum One Mainnet
53
+ 'arbitrum-one-mainnet': 42161,
54
+ 'arbitrum': 42161,
55
+ 'arb': 42161,
56
+ 'arbitrum-one': 42161,
57
+
58
+ // Arbitrum Nova Mainnet
59
+ 'arbitrum-nova-mainnet': 42170,
60
+ 'arbitrum-nova': 42170,
61
+ 'nova': 42170,
62
+
63
+ // Arbitrum Sepolia Testnet
64
+ 'arbitrum-sepolia-testnet': 421614,
65
+ 'arbitrum-sepolia': 421614,
66
+ 'arb-sepolia': 421614,
67
+
68
+ // Linea Mainnet
69
+ 'linea-mainnet': 59144,
70
+ 'linea': 59144,
71
+
72
+ // Linea Sepolia Testnet
73
+ 'linea-sepolia-testnet': 59141,
74
+ 'linea-sepolia': 59141,
75
+
76
+ // Blast Mainnet
77
+ 'blast-mainnet': 81457,
78
+ 'blast': 81457,
79
+
80
+ // Blast Sepolia Testnet
81
+ 'blast-sepolia-testnet': 168587773,
82
+ 'blast-sepolia': 168587773,
83
+
84
+ // OP Mainnet
85
+ 'op-mainnet': 10,
86
+ 'optimism': 10,
87
+ 'op': 10,
88
+
89
+ // OP Sepolia Testnet
90
+ 'op-sepolia-testnet': 11155420,
91
+ 'op-sepolia': 11155420,
92
+ 'optimism-sepolia': 11155420,
93
+
94
+ // Avalanche C-Chain
95
+ 'avalanche-c-chain': 43114,
96
+ 'avalanche': 43114,
97
+ 'avax': 43114,
98
+
99
+ // Avalanche Fuji Testnet
100
+ 'avalanche-fuji-testnet': 43113,
101
+ 'fuji': 43113,
102
+ 'avalanche-fuji': 43113,
103
+
104
+ // BitTorrent Chain Mainnet
105
+ 'bittorrent-chain-mainnet': 199,
106
+ 'bittorrent': 199,
107
+ 'btt': 199,
108
+
109
+ // BitTorrent Chain Testnet
110
+ 'bittorrent-chain-testnet': 1029,
111
+ 'bittorrent-testnet': 1029,
112
+ 'btt-testnet': 1029,
113
+
114
+ // Celo Mainnet
115
+ 'celo-mainnet': 42220,
116
+ 'celo': 42220,
117
+
118
+ // Celo Sepolia Testnet
119
+ 'celo-sepolia-testnet': 11142220,
120
+ 'celo-sepolia': 11142220,
121
+
122
+ // Fraxtal Mainnet
123
+ 'fraxtal-mainnet': 252,
124
+ 'fraxtal': 252,
125
+
126
+ // Fraxtal Hoodi Testnet
127
+ 'fraxtal-hoodi-testnet': 2523,
128
+ 'fraxtal-hoodi': 2523,
129
+
130
+ // Gnosis
131
+ 'gnosis': 100,
132
+ 'gno': 100,
133
+ 'xdai': 100,
134
+
135
+ // Mantle Mainnet
136
+ 'mantle-mainnet': 5000,
137
+ 'mantle': 5000,
138
+
139
+ // Mantle Sepolia Testnet
140
+ 'mantle-sepolia-testnet': 5003,
141
+ 'mantle-sepolia': 5003,
142
+
143
+ // Memecore Mainnet
144
+ 'memecore-mainnet': 4352,
145
+ 'memecore': 4352,
146
+
147
+ // Memecore Testnet
148
+ 'memecore-testnet': 43521,
149
+
150
+ // Moonbeam Mainnet
151
+ 'moonbeam-mainnet': 1284,
152
+ 'moonbeam': 1284,
153
+
154
+ // Moonriver Mainnet
155
+ 'moonriver-mainnet': 1285,
156
+ 'moonriver': 1285,
157
+
158
+ // Moonbase Alpha Testnet
159
+ 'moonbase-alpha-testnet': 1287,
160
+ 'moonbase': 1287,
161
+ 'moonbase-alpha': 1287,
162
+
163
+ // opBNB Mainnet
164
+ 'opbnb-mainnet': 204,
165
+ 'opbnb': 204,
166
+
167
+ // opBNB Testnet
168
+ 'opbnb-testnet': 5611,
169
+
170
+ // Scroll Mainnet
171
+ 'scroll-mainnet': 534352,
172
+ 'scroll': 534352,
173
+
174
+ // Scroll Sepolia Testnet
175
+ 'scroll-sepolia-testnet': 534351,
176
+ 'scroll-sepolia': 534351,
177
+
178
+ // Taiko Mainnet
179
+ 'taiko-mainnet': 167000,
180
+ 'taiko': 167000,
181
+
182
+ // Taiko Hoodi
183
+ 'taiko-hoodi': 167013,
184
+
185
+ // XDC Mainnet
186
+ 'xdc-mainnet': 50,
187
+ 'xdc': 50,
188
+
189
+ // XDC Apothem Testnet
190
+ 'xdc-apothem-testnet': 51,
191
+ 'xdc-apothem': 51,
192
+ 'apothem': 51,
193
+
194
+ // ApeChain Mainnet
195
+ 'apechain-mainnet': 33139,
196
+ 'apechain': 33139,
197
+ 'ape': 33139,
198
+
199
+ // ApeChain Curtis Testnet
200
+ 'apechain-curtis-testnet': 33111,
201
+ 'apechain-curtis': 33111,
202
+ 'curtis': 33111,
203
+
204
+ // World Mainnet
205
+ 'world-mainnet': 480,
206
+ 'world': 480,
207
+
208
+ // World Sepolia Testnet
209
+ 'world-sepolia-testnet': 4801,
210
+ 'world-sepolia': 4801,
211
+
212
+ // Sonic Mainnet
213
+ 'sonic-mainnet': 146,
214
+ 'sonic': 146,
215
+
216
+ // Sonic Testnet
217
+ 'sonic-testnet': 14601,
218
+
219
+ // Unichain Mainnet
220
+ 'unichain-mainnet': 130,
221
+ 'unichain': 130,
222
+
223
+ // Unichain Sepolia Testnet
224
+ 'unichain-sepolia-testnet': 1301,
225
+ 'unichain-sepolia': 1301,
226
+
227
+ // Abstract Mainnet
228
+ 'abstract-mainnet': 2741,
229
+ 'abstract': 2741,
230
+
231
+ // Abstract Sepolia Testnet
232
+ 'abstract-sepolia-testnet': 11124,
233
+ 'abstract-sepolia': 11124,
234
+
235
+ // Berachain Mainnet
236
+ 'berachain-mainnet': 80094,
237
+ 'berachain': 80094,
238
+ 'bera': 80094,
239
+
240
+ // Berachain Bepolia Testnet
241
+ 'berachain-bepolia-testnet': 80069,
242
+ 'berachain-bepolia': 80069,
243
+ 'bepolia': 80069,
244
+
245
+ // Swellchain Mainnet
246
+ 'swellchain-mainnet': 1923,
247
+ 'swellchain': 1923,
248
+ 'swell': 1923,
249
+
250
+ // Swellchain Testnet
251
+ 'swellchain-testnet': 1924,
252
+ 'swell-testnet': 1924,
253
+
254
+ // Monad Mainnet
255
+ 'monad-mainnet': 143,
256
+ 'monad': 143,
257
+
258
+ // Monad Testnet
259
+ 'monad-testnet': 10143,
260
+
261
+ // HyperEVM Mainnet
262
+ 'hyperevm-mainnet': 999,
263
+ 'hyperevm': 999,
264
+
265
+ // Katana Mainnet
266
+ 'katana-mainnet': 747474,
267
+ 'katana': 747474,
268
+
269
+ // Katana Bokuto
270
+ 'katana-bokuto': 737373,
271
+ 'bokuto': 737373,
272
+
273
+ // Sei Mainnet
274
+ 'sei-mainnet': 1329,
275
+ 'sei': 1329,
276
+
277
+ // Sei Testnet
278
+ 'sei-testnet': 1328,
279
+
280
+ // Stable Mainnet
281
+ 'stable-mainnet': 988,
282
+ 'stable': 988,
283
+
284
+ // Stable Testnet
285
+ 'stable-testnet': 2201,
286
+
287
+ // Plasma Mainnet
288
+ 'plasma-mainnet': 9745,
289
+ 'plasma': 9745,
290
+
291
+ // Plasma Testnet
292
+ 'plasma-testnet': 9746,
293
+ }
294
+
295
+ export function chainIdOption(chainId?: string): number | undefined {
296
+ if (chainId == null) {
297
+ return undefined
298
+ }
299
+
300
+ // @ts-expect-error - chainId is a string
301
+ if (!isNaN(chainId)) {
302
+ return validator.positiveInt(chainId)
303
+ }
304
+
305
+ const normalizedChainId = chainId.toLowerCase()
306
+
307
+ if (normalizedChainId in CHAIN_IDS) {
308
+ return CHAIN_IDS[normalizedChainId]
309
+ }
310
+
311
+ const suggestion = closest(normalizedChainId, Object.keys(CHAIN_IDS))
312
+ throw new InvalidOptionArgumentError(
313
+ `Unknown chain: "${chainId}". Did you mean "${suggestion}"? Alternatively, provide a numeric chain ID.`
314
+ )
315
+ }
@@ -0,0 +1,221 @@
1
+ import {keccak256} from '@subsquid/evm-abi'
2
+ import type {Abi, AbiEvent, AbiFunction, AbiParameter} from 'abitype'
3
+
4
+ export interface ContractDef {
5
+ events: EventDef[]
6
+ functions: FunctionDef[]
7
+ }
8
+
9
+ export interface DocDef {
10
+ notice?: string
11
+ dev?: string
12
+ params?: Record<string, string>
13
+ returns?: Record<string, string>
14
+ }
15
+
16
+ /** NatSpec documentation extracted from a compilation artifact's userdoc/devdoc fields. */
17
+ export interface NatSpec {
18
+ userdoc?: {
19
+ methods?: Record<string, {notice?: string}>
20
+ events?: Record<string, {notice?: string}>
21
+ }
22
+ devdoc?: {
23
+ methods?: Record<string, {details?: string; params?: Record<string, string>; returns?: Record<string, string>}>
24
+ events?: Record<string, {details?: string; params?: Record<string, string>}>
25
+ }
26
+ }
27
+
28
+ export interface EventDef {
29
+ name: string
30
+ signature: string
31
+ topic: string
32
+ inputs: FieldDef[]
33
+ key: string
34
+ typeName: string
35
+ docs?: DocDef
36
+ }
37
+
38
+ export interface FunctionDef {
39
+ name: string
40
+ signature: string
41
+ selector: string
42
+ inputs: FieldDef[]
43
+ outputs: FieldDef[]
44
+ key: string
45
+ paramsTypeName: string
46
+ returnTypeName: string
47
+ docs?: DocDef
48
+ }
49
+
50
+ export interface FieldDef {
51
+ name: string
52
+ type: TypeDef
53
+ indexed?: boolean
54
+ doc?: string
55
+ }
56
+
57
+ export type TypeDef =
58
+ | {kind: 'primitive'; name: string}
59
+ | {kind: 'array'; item: TypeDef}
60
+ | {kind: 'fixedArray'; item: TypeDef; size: number}
61
+ | {kind: 'tuple'; fields: FieldDef[]}
62
+
63
+ export function describe(abi: Abi, natspec?: NatSpec): ContractDef {
64
+ const rawEvents = abi.filter((x) => x.type === 'event') as AbiEvent[]
65
+ const rawFunctions = abi.filter((x) => x.type === 'function') as AbiFunction[]
66
+
67
+ const eventSuffix = overloadSuffixer(rawEvents)
68
+ const functionSuffix = overloadSuffixer(rawFunctions)
69
+
70
+ const events: EventDef[] = rawEvents.map((e) => {
71
+ const signature = eventSignature(e)
72
+ const docs = buildEventDocs(signature, natspec)
73
+ const inputs = e.inputs.map((p, i) => toFieldDef(p, i, true))
74
+ annotateParamDocs(inputs, docs?.params)
75
+ return {
76
+ name: e.name,
77
+ signature,
78
+ topic: `0x${keccak256(signature).toString('hex')}`,
79
+ inputs,
80
+ key: eventSuffix(e, e.name),
81
+ typeName: eventSuffix(e, `${capitalize(e.name)}EventArgs`),
82
+ docs,
83
+ }
84
+ })
85
+
86
+ const functions: FunctionDef[] = rawFunctions.map((f) => {
87
+ const signature = fnSignature(f)
88
+ const docs = buildFunctionDocs(signature, natspec)
89
+ const inputs = f.inputs.map((p, i) => toFieldDef(p, i, false))
90
+ const outputs = (f.outputs ?? []).map((p, i) => toFieldDef(p, i, false))
91
+ annotateParamDocs(inputs, docs?.params)
92
+ annotateReturnDocs(outputs, docs?.returns)
93
+ return {
94
+ name: f.name,
95
+ signature,
96
+ selector: `0x${keccak256(signature).slice(0, 4).toString('hex')}`,
97
+ inputs,
98
+ outputs,
99
+ key: functionSuffix(f, f.name),
100
+ paramsTypeName: functionSuffix(f, `${capitalize(f.name)}Params`),
101
+ returnTypeName: functionSuffix(f, `${capitalize(f.name)}Return`),
102
+ docs,
103
+ }
104
+ })
105
+
106
+ return {events, functions}
107
+ }
108
+
109
+ function buildEventDocs(signature: string, natspec: NatSpec | undefined): DocDef | undefined {
110
+ const notice = natspec?.userdoc?.events?.[signature]?.notice
111
+ const devEntry = natspec?.devdoc?.events?.[signature]
112
+ if (!notice && !devEntry) return undefined
113
+ return filterEmptyDoc({
114
+ notice,
115
+ dev: devEntry?.details,
116
+ params: devEntry?.params,
117
+ })
118
+ }
119
+
120
+ function buildFunctionDocs(signature: string, natspec: NatSpec | undefined): DocDef | undefined {
121
+ const notice = natspec?.userdoc?.methods?.[signature]?.notice
122
+ const devEntry = natspec?.devdoc?.methods?.[signature]
123
+ if (!notice && !devEntry) return undefined
124
+ return filterEmptyDoc({
125
+ notice,
126
+ dev: devEntry?.details,
127
+ params: devEntry?.params,
128
+ returns: devEntry?.returns,
129
+ })
130
+ }
131
+
132
+ function filterEmptyDoc(doc: DocDef): DocDef | undefined {
133
+ const hasContent =
134
+ doc.notice != null ||
135
+ doc.dev != null ||
136
+ (doc.params != null && Object.keys(doc.params).length > 0) ||
137
+ (doc.returns != null && Object.keys(doc.returns).length > 0)
138
+ return hasContent ? doc : undefined
139
+ }
140
+
141
+ function annotateParamDocs(fields: FieldDef[], params: Record<string, string> | undefined): void {
142
+ if (!params) return
143
+ for (const field of fields) {
144
+ const doc = params[field.name]
145
+ if (doc) field.doc = doc
146
+ }
147
+ }
148
+
149
+ function annotateReturnDocs(fields: FieldDef[], returns: Record<string, string> | undefined): void {
150
+ if (!returns) return
151
+ for (let i = 0; i < fields.length; i++) {
152
+ const field = fields[i]
153
+ const doc = returns[field.name] ?? returns[`_${i}`]
154
+ if (doc) field.doc = doc
155
+ }
156
+ }
157
+
158
+ function overloadSuffixer(items: readonly (AbiEvent | AbiFunction)[]) {
159
+ const counts = new Map<string, number>()
160
+ const indices = new Map<AbiEvent | AbiFunction, number>()
161
+ for (const item of items) {
162
+ const seen = counts.get(item.name) ?? 0
163
+ indices.set(item, seen)
164
+ counts.set(item.name, seen + 1)
165
+ }
166
+ return (item: AbiEvent | AbiFunction, base: string): string => {
167
+ if ((counts.get(item.name) ?? 1) <= 1) return base
168
+ const idx = indices.get(item)!
169
+ return idx === 0 ? base : `${base}_${idx}`
170
+ }
171
+ }
172
+
173
+ function toFieldDef(p: AbiParameter, index: number, isEventInput: boolean): FieldDef {
174
+ const field: FieldDef = {
175
+ name: p.name || `_${index}`,
176
+ type: toTypeDef(p),
177
+ }
178
+ if (isEventInput && (p as any).indexed) field.indexed = true
179
+ return field
180
+ }
181
+
182
+ function toTypeDef(p: AbiParameter): TypeDef {
183
+ const fixed = p.type.match(/\[(\d+)\]$/)
184
+ if (fixed) {
185
+ return {kind: 'fixedArray', size: Number(fixed[1]), item: toTypeDef(stripOuterArray(p))}
186
+ }
187
+ if (p.type.endsWith('[]')) {
188
+ return {kind: 'array', item: toTypeDef(stripOuterArray(p))}
189
+ }
190
+ if (p.type.startsWith('tuple')) {
191
+ const components = ((p as any).components || []) as AbiParameter[]
192
+ return {
193
+ kind: 'tuple',
194
+ fields: components.map((c, i) => toFieldDef(c, i, false)),
195
+ }
196
+ }
197
+ return {kind: 'primitive', name: p.type}
198
+ }
199
+
200
+ function stripOuterArray(p: AbiParameter): AbiParameter {
201
+ return {...(p as any), type: p.type.replace(/\[\d*\]$/, '')} as AbiParameter
202
+ }
203
+
204
+ export function canonicalType(p: AbiParameter): string {
205
+ if (!p.type.startsWith('tuple')) return p.type
206
+ const arrayBrackets = p.type.slice(5)
207
+ const components = (p as any).components as AbiParameter[]
208
+ return `(${components.map(canonicalType).join(',')})${arrayBrackets}`
209
+ }
210
+
211
+ function eventSignature(e: AbiEvent): string {
212
+ return `${e.name}(${e.inputs.map(canonicalType).join(',')})`
213
+ }
214
+
215
+ function fnSignature(f: AbiFunction): string {
216
+ return `${f.name}(${f.inputs.map(canonicalType).join(',')})`
217
+ }
218
+
219
+ function capitalize(s: string): string {
220
+ return s.charAt(0).toUpperCase() + s.slice(1)
221
+ }