@subsquid/evm-typegen 1.3.0 → 2.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.
- package/README.md +41 -3
- package/bin/run.js +1 -1
- package/lib/abi.support.d.ts +49 -0
- package/lib/abi.support.d.ts.map +1 -0
- package/lib/abi.support.js +100 -0
- package/lib/abi.support.js.map +1 -0
- package/lib/main.d.ts +1 -1
- package/lib/main.d.ts.map +1 -1
- package/lib/main.js +175 -21
- package/lib/main.js.map +1 -1
- package/lib/multicall.d.ts +31 -0
- package/lib/multicall.d.ts.map +1 -0
- package/lib/multicall.js +175 -0
- package/lib/multicall.js.map +1 -0
- package/lib/typegen.d.ts +13 -5
- package/lib/typegen.d.ts.map +1 -1
- package/lib/typegen.js +86 -254
- package/lib/typegen.js.map +1 -1
- package/lib/util/fetch.d.ts +7 -0
- package/lib/util/fetch.d.ts.map +1 -0
- package/lib/util/fetch.js +106 -0
- package/lib/util/fetch.js.map +1 -0
- package/lib/util/types.d.ts +7 -0
- package/lib/util/types.d.ts.map +1 -0
- package/lib/util/types.js +62 -0
- package/lib/util/types.js.map +1 -0
- package/package.json +11 -5
- package/src/abi.support.ts +115 -0
- package/src/main.ts +184 -24
- package/src/multicall.ts +212 -0
- package/src/typegen.ts +82 -290
- package/src/util/fetch.ts +82 -0
- package/src/util/types.ts +69 -0
package/src/typegen.ts
CHANGED
|
@@ -1,342 +1,134 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {def} from
|
|
4
|
-
import
|
|
1
|
+
import {EventFragment, FunctionFragment, Interface} from '@ethersproject/abi'
|
|
2
|
+
import {Logger} from '@subsquid/logger'
|
|
3
|
+
import {def} from '@subsquid/util-internal'
|
|
4
|
+
import {FileOutput, OutDir} from '@subsquid/util-internal-code-printer'
|
|
5
|
+
import {getFullTupleType, getReturnType, getStructType, getTupleType, getType} from './util/types'
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
export class Typegen {
|
|
7
|
-
private rawAbi: any
|
|
8
|
-
private abi: Interface
|
|
9
9
|
private out: FileOutput
|
|
10
10
|
|
|
11
|
-
constructor(
|
|
12
|
-
this.
|
|
13
|
-
this.abi = new Interface(this.rawAbi)
|
|
14
|
-
this.out = new FileOutput(outPath)
|
|
11
|
+
constructor(private dest: OutDir, private abi: Interface, private basename: string, private log: Logger) {
|
|
12
|
+
this.out = dest.file(basename + '.ts')
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
generate(): void {
|
|
18
|
-
this.out.line("import * as ethers from
|
|
19
|
-
this.out.line("import
|
|
20
|
-
this.out.line()
|
|
21
|
-
this.out.line("export const abi = new ethers.utils.Interface(getJsonAbi());")
|
|
16
|
+
this.out.line("import * as ethers from 'ethers'")
|
|
17
|
+
this.out.line("import {LogEvent, Func, ContractBase} from './abi.support'")
|
|
18
|
+
this.out.line(`import {ABI_JSON} from './${this.basename}.abi'`)
|
|
22
19
|
this.out.line()
|
|
20
|
+
this.out.line("export const abi = new ethers.utils.Interface(ABI_JSON);")
|
|
21
|
+
|
|
23
22
|
this.generateEvents()
|
|
24
|
-
this.out.line()
|
|
25
23
|
this.generateFunctions()
|
|
26
|
-
this.out.line()
|
|
27
24
|
this.generateContract()
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
`return ${JSON.stringify(this.rawAbi, null, 2)}`.split('\n').forEach(line => {
|
|
31
|
-
this.out.line(line)
|
|
32
|
-
})
|
|
33
|
-
})
|
|
25
|
+
|
|
26
|
+
this.writeAbi()
|
|
34
27
|
this.out.write()
|
|
28
|
+
this.log.info(`saved ${this.out.file}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private writeAbi() {
|
|
32
|
+
let out = this.dest.file(this.basename + '.abi.ts')
|
|
33
|
+
let json = this.abi.format('json') as string
|
|
34
|
+
json = JSON.stringify(JSON.parse(json), null, 4)
|
|
35
|
+
out.line(`export const ABI_JSON = ${json}`)
|
|
36
|
+
out.write()
|
|
37
|
+
this.log.info(`saved ${out.file}`)
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
private generateEvents() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.out.line(`export type ${decl.name}${i}Event = ${getTupleType(decl.overloads[i].inputs)}`)
|
|
42
|
-
this.out.line()
|
|
43
|
-
}
|
|
41
|
+
let events = Object.values(this.abi.events)
|
|
42
|
+
if (events.length == 0) {
|
|
43
|
+
return
|
|
44
44
|
}
|
|
45
|
-
this.out.block("export interface EvmLog", () => {
|
|
46
|
-
this.out.line("data: string;")
|
|
47
|
-
this.out.line("topics: string[];")
|
|
48
|
-
})
|
|
49
|
-
this.out.line()
|
|
50
|
-
this.out.block(`function decodeEvent(signature: string, data: EvmLog): any`, () => {
|
|
51
|
-
this.out.line(`return abi.decodeEventLog(`)
|
|
52
|
-
this.out.indentation(() => {
|
|
53
|
-
this.out.line(`abi.getEvent(signature),`)
|
|
54
|
-
this.out.line(`data.data || "",`)
|
|
55
|
-
this.out.line("data.topics")
|
|
56
|
-
})
|
|
57
|
-
this.out.line(");")
|
|
58
|
-
})
|
|
59
45
|
this.out.line()
|
|
60
46
|
this.out.block(`export const events =`, () => {
|
|
61
|
-
for (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.out.line(`topic: abi.getEventTopic("${signature}"),`)
|
|
67
|
-
if (event.overloads[i].inputs.length > 0) {
|
|
68
|
-
this.out.block(`decode(data: EvmLog): ${event.name}${i}Event`, () => {
|
|
69
|
-
this.out.line(`return decodeEvent("${signature}", data)`)
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
this.out.line(",")
|
|
74
|
-
}
|
|
47
|
+
for (let e of events) {
|
|
48
|
+
let topic = this.abi.getEventTopic(e)
|
|
49
|
+
this.out.line(`${this.getPropName(e)}: new LogEvent<${getFullTupleType(e.inputs)}>(`)
|
|
50
|
+
this.out.indentation(() => this.out.line(`abi, '${topic}'`))
|
|
51
|
+
this.out.line('),')
|
|
75
52
|
}
|
|
76
53
|
})
|
|
77
54
|
}
|
|
78
55
|
|
|
79
56
|
private generateFunctions() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.out.line(`export type ${upperCaseFirst(func.name)}${i}Function = ${getTupleType(func.overloads[i].inputs)}`)
|
|
84
|
-
this.out.line()
|
|
85
|
-
}
|
|
57
|
+
let functions = Object.values(this.abi.functions)
|
|
58
|
+
if (functions.length == 0) {
|
|
59
|
+
return
|
|
86
60
|
}
|
|
87
61
|
this.out.line()
|
|
88
|
-
this.out.block(`function decodeFunction(data: string): any`, () => {
|
|
89
|
-
this.out.line(`return abi.decodeFunctionData(data.slice(0, 10), data)`)
|
|
90
|
-
})
|
|
91
|
-
this.out.line()
|
|
92
62
|
this.out.block(`export const functions =`, () => {
|
|
93
|
-
for (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.out.line(`return decodeFunction(input)`)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
this.out.line(",")
|
|
105
|
-
}
|
|
63
|
+
for (let f of functions) {
|
|
64
|
+
let sighash = this.abi.getSighash(f)
|
|
65
|
+
let pArgs = getTupleType(f.inputs)
|
|
66
|
+
let pArgStruct = getStructType(f.inputs)
|
|
67
|
+
let pResult = getReturnType(f.outputs || [])
|
|
68
|
+
this.out.line(`${this.getPropName(f)}: new Func<${pArgs}, ${pArgStruct}, ${pResult}>(`)
|
|
69
|
+
this.out.indentation(() => this.out.line(`abi, '${sighash}'`))
|
|
70
|
+
this.out.line('),')
|
|
106
71
|
}
|
|
107
72
|
})
|
|
108
73
|
}
|
|
109
74
|
|
|
110
75
|
private generateContract() {
|
|
111
|
-
let abiCalls = this.getCalls()
|
|
112
|
-
|
|
113
|
-
this.out.block("interface ChainContext ", () => {
|
|
114
|
-
this.out.line(`_chain: Chain`)
|
|
115
|
-
})
|
|
116
|
-
this.out.line()
|
|
117
|
-
this.out.block("interface BlockContext ", () => {
|
|
118
|
-
this.out.line(`_chain: Chain`)
|
|
119
|
-
this.out.line(`block: Block`)
|
|
120
|
-
})
|
|
121
76
|
this.out.line()
|
|
122
|
-
this.out.block(
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.out.line()
|
|
132
|
-
this.out.block("export class Contract ", () => {
|
|
133
|
-
this.out.line(`private readonly _chain: Chain`)
|
|
134
|
-
this.out.line(`private readonly blockHeight: number`)
|
|
135
|
-
this.out.line(`readonly address: string`)
|
|
136
|
-
this.out.line()
|
|
137
|
-
this.out.line(`constructor(ctx: BlockContext, address: string)`)
|
|
138
|
-
this.out.line(`constructor(ctx: ChainContext, block: Block, address: string)`)
|
|
139
|
-
this.out.block(`constructor(ctx: BlockContext, blockOrAddress: Block | string, address?: string)`, () => {
|
|
140
|
-
this.out.line(`this._chain = ctx._chain`)
|
|
141
|
-
this.out.block(`if (typeof blockOrAddress === 'string') `, () => {
|
|
142
|
-
this.out.line(`this.blockHeight = ctx.block.height`)
|
|
143
|
-
this.out.line(`this.address = ethers.utils.getAddress(blockOrAddress)`)
|
|
144
|
-
})
|
|
145
|
-
this.out.block(`else `, () => {
|
|
146
|
-
this.out.line(`assert(address != null)`)
|
|
147
|
-
this.out.line(`this.blockHeight = blockOrAddress.height`)
|
|
148
|
-
this.out.line(`this.address = ethers.utils.getAddress(address)`)
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
this.out.line()
|
|
152
|
-
for (const decl of abiCalls) {
|
|
153
|
-
if (decl.overloads.length > 1) {
|
|
154
|
-
for (let overload of decl.overloads) {
|
|
155
|
-
const args = overload.inputs.map((i, n) => `${i.name || `arg${n}`}: ${getType(i)}`)
|
|
156
|
-
const returnType = overload.outputs.length == 1 ? getType(overload.outputs[0]) : getTupleType(overload.outputs)
|
|
157
|
-
this.out.line(`async ${decl.name}(${args}): Promise<${returnType}>`)
|
|
158
|
-
}
|
|
159
|
-
this.out.block(`async ${decl.name}(...args: any[])`, () => {
|
|
160
|
-
this.out.line(`return this.call("${decl.name}", args)`)
|
|
161
|
-
})
|
|
162
|
-
} else {
|
|
163
|
-
const overload = decl.overloads[0]
|
|
164
|
-
const params = overload.inputs.map((i, n) => `${i.name || `arg${n}`}: ${getType(i)}`)
|
|
165
|
-
const returnType = overload.outputs.length == 1 ? getType(overload.outputs[0]) : getTupleType(overload.outputs)
|
|
166
|
-
this.out.block(`async ${decl.name}(${params.join(`, `)}): Promise<${returnType}>`, () => {
|
|
167
|
-
this.out.line(`return this.call("${decl.name}", [${overload.inputs.map((i, n) => `${i.name || `arg${n}`}`).join(`, `)}])`)
|
|
77
|
+
this.out.block(`export class Contract extends ContractBase`, () => {
|
|
78
|
+
let functions = Object.values(this.abi.functions)
|
|
79
|
+
for (let f of functions) {
|
|
80
|
+
if (f.constant && f.outputs?.length) {
|
|
81
|
+
this.out.line()
|
|
82
|
+
let argNames = f.inputs.map((a, idx) => a.name || `arg${idx}`)
|
|
83
|
+
let args = f.inputs.map((a, idx) => `${argNames[idx]}: ${getType(a)}`).join(', ')
|
|
84
|
+
this.out.block(`${this.getPropName(f)}(${args}): Promise<${getReturnType(f.outputs)}>`, () => {
|
|
85
|
+
this.out.line(`return this.eth_call(functions${this.getRef(f)}, [${argNames.join(', ')}])`)
|
|
168
86
|
})
|
|
169
87
|
}
|
|
170
|
-
this.out.line()
|
|
171
88
|
}
|
|
172
|
-
this.out.block(`private async call(name: string, args: any[]) : Promise<any>`, () => {
|
|
173
|
-
this.out.line(`const fragment = abi.getFunction(name)`)
|
|
174
|
-
this.out.line(`const data = abi.encodeFunctionData(fragment, args)`)
|
|
175
|
-
this.out.line(`const result = await this._chain.client.call('eth_call', [{to: this.address, data}, this.blockHeight])`)
|
|
176
|
-
this.out.line(`const decoded = abi.decodeFunctionResult(fragment, result)`)
|
|
177
|
-
this.out.line(`return decoded.length > 1 ? decoded : decoded[0]`)
|
|
178
|
-
})
|
|
179
89
|
})
|
|
180
90
|
}
|
|
181
91
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
abiEvent = {
|
|
189
|
-
name: event.name,
|
|
190
|
-
overloads: []
|
|
191
|
-
}
|
|
192
|
-
res.set(event.name, abiEvent)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
abiEvent.overloads.push({
|
|
196
|
-
inputs: event.inputs || [],
|
|
197
|
-
})
|
|
92
|
+
private getRef(item: EventFragment | FunctionFragment): string {
|
|
93
|
+
let key = this.getPropName(item)
|
|
94
|
+
if (key[0] == "'") {
|
|
95
|
+
return `[${key}]`
|
|
96
|
+
} else {
|
|
97
|
+
return '.' + key
|
|
198
98
|
}
|
|
199
|
-
|
|
200
|
-
return [...res.values()]
|
|
201
99
|
}
|
|
202
100
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let abiFunc = res.get(func.name)
|
|
210
|
-
if (abiFunc == null) {
|
|
211
|
-
abiFunc = {
|
|
212
|
-
name: func.name,
|
|
213
|
-
overloads: []
|
|
214
|
-
}
|
|
215
|
-
res.set(func.name, abiFunc)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
abiFunc.overloads.push({
|
|
219
|
-
inputs: func.inputs || [],
|
|
220
|
-
})
|
|
101
|
+
private getPropName(item: EventFragment | FunctionFragment): string {
|
|
102
|
+
if (this.getOverloads(item) == 1) {
|
|
103
|
+
return item.name
|
|
104
|
+
} else {
|
|
105
|
+
return `'${item.format('sighash')}'`
|
|
221
106
|
}
|
|
222
|
-
|
|
223
|
-
return [...res.values()]
|
|
224
107
|
}
|
|
225
108
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
let abiCall = res.get(func.name)
|
|
233
|
-
if (abiCall == null) {
|
|
234
|
-
abiCall = {
|
|
235
|
-
name: func.name,
|
|
236
|
-
overloads: []
|
|
237
|
-
}
|
|
238
|
-
res.set(func.name, abiCall)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
abiCall.overloads.push({
|
|
242
|
-
inputs: func.inputs,
|
|
243
|
-
outputs: func.outputs || [],
|
|
244
|
-
})
|
|
109
|
+
private getOverloads(item: EventFragment | FunctionFragment): number {
|
|
110
|
+
if (item instanceof EventFragment) {
|
|
111
|
+
return this.eventOverloads()[item.name]
|
|
112
|
+
} else {
|
|
113
|
+
return this.functionOverloads()[item.name]
|
|
245
114
|
}
|
|
246
|
-
|
|
247
|
-
return [...res.values()]
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// taken from: https://github.com/ethers-io/ethers.js/blob/948f77050dae884fe88932fd88af75560aac9d78/packages/cli/src.ts/typescript.ts#L10
|
|
253
|
-
function getType(param: ParamType): string {
|
|
254
|
-
if (param.type === "address" || param.type === "string") {
|
|
255
|
-
return "string"
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (param.type === "bool") {
|
|
259
|
-
return "boolean"
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (param.type.substring(0, 5) === "bytes") {
|
|
263
|
-
return "string"
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
let match = param.type.match(/^(u?int)([0-9]+)$/)
|
|
267
|
-
if (match) {
|
|
268
|
-
return parseInt(match[2]) < 53 ? 'number' : 'ethers.BigNumber'
|
|
269
115
|
}
|
|
270
116
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
117
|
+
@def
|
|
118
|
+
private functionOverloads(): Record<string, number> {
|
|
119
|
+
let overloads: Record<string, number> = {}
|
|
120
|
+
for (let item of Object.values(this.abi.functions)) {
|
|
121
|
+
overloads[item.name] = (overloads[item.name] || 0) + 1
|
|
122
|
+
}
|
|
123
|
+
return overloads
|
|
277
124
|
}
|
|
278
125
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
let tuple = '[' + params.map(p => {
|
|
285
|
-
return p.name ? `${p.name}: ${getType(p)}` : getType(p)
|
|
286
|
-
}).join(', ') + ']'
|
|
287
|
-
|
|
288
|
-
let fields = getStructFields(params)
|
|
289
|
-
if (fields.length == 0) return tuple
|
|
290
|
-
|
|
291
|
-
let struct = '{' + fields.map(f => `${f.name}: ${getType(f)}`).join(', ') + '}'
|
|
292
|
-
|
|
293
|
-
return `(${tuple} & ${struct})`
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// https://github.com/ethers-io/ethers.js/blob/948f77050dae884fe88932fd88af75560aac9d78/packages/abi/src.ts/coders/tuple.ts#L29
|
|
298
|
-
function getStructFields(params: ParamType[]): ParamType[] {
|
|
299
|
-
let array: any = []
|
|
300
|
-
let counts: Record<string, number> = {}
|
|
301
|
-
for (let p of params) {
|
|
302
|
-
if (p.name && array[p.name] == null) {
|
|
303
|
-
counts[p.name] = (counts[p.name] || 0) + 1
|
|
126
|
+
@def
|
|
127
|
+
private eventOverloads(): Record<string, number> {
|
|
128
|
+
let overloads: Record<string, number> = {}
|
|
129
|
+
for (let item of Object.values(this.abi.events)) {
|
|
130
|
+
overloads[item.name] = (overloads[item.name] || 0) + 1
|
|
304
131
|
}
|
|
132
|
+
return overloads
|
|
305
133
|
}
|
|
306
|
-
return params.filter(p => counts[p.name] == 1)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
function createSignature(name: string, inputs: ParamType[]) {
|
|
311
|
-
return `${name}(${inputs.map((i) => i.type).join(`,`)})`
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
interface AbiEvent {
|
|
316
|
-
name: string
|
|
317
|
-
overloads: {
|
|
318
|
-
inputs: ParamType[]
|
|
319
|
-
}[]
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
interface AbiFunction {
|
|
324
|
-
name: string
|
|
325
|
-
overloads: {
|
|
326
|
-
inputs: ParamType[]
|
|
327
|
-
}[]
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
interface AbiCall {
|
|
332
|
-
name: string
|
|
333
|
-
overloads: {
|
|
334
|
-
inputs: ParamType[]
|
|
335
|
-
outputs: ParamType[]
|
|
336
|
-
}[]
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
function upperCaseFirst(s: string): string {
|
|
341
|
-
return s[0].toUpperCase() + s.slice(1)
|
|
342
134
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {createLogger} from '@subsquid/logger'
|
|
2
|
+
import {wait} from '@subsquid/util-internal'
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import fetch, {FetchError, RequestInit} from 'node-fetch'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const LOG = createLogger('sqd:evm-typegen:fetch')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export async function GET<T=any>(url: string): Promise<T> {
|
|
11
|
+
let init: RequestInit = {
|
|
12
|
+
method: 'GET',
|
|
13
|
+
headers: {
|
|
14
|
+
'accept': 'application/json',
|
|
15
|
+
'accept-encoding': 'gzip, br'
|
|
16
|
+
},
|
|
17
|
+
timeout: 10_000
|
|
18
|
+
}
|
|
19
|
+
let backoff = [1000, 2000]
|
|
20
|
+
let errors = 0
|
|
21
|
+
while (true) {
|
|
22
|
+
let result = await performFetch(url, init).catch(err => {
|
|
23
|
+
assert(err instanceof Error)
|
|
24
|
+
return err
|
|
25
|
+
})
|
|
26
|
+
if (errors < backoff.length && isRetryableError(result)) {
|
|
27
|
+
let timeout = backoff[errors]
|
|
28
|
+
LOG.warn(`${result.toString()}. Trying again in ${timeout/1000} seconds`)
|
|
29
|
+
errors += 1
|
|
30
|
+
await wait(timeout)
|
|
31
|
+
} else if (result instanceof Error) {
|
|
32
|
+
throw result
|
|
33
|
+
} else {
|
|
34
|
+
return result
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async function performFetch(url: string, init: RequestInit): Promise<any> {
|
|
41
|
+
let response = await fetch(url, init)
|
|
42
|
+
if (response.ok) return response.json()
|
|
43
|
+
let body = await response.text()
|
|
44
|
+
throw new HttpError(response.status, body)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
function isRetryableError(err: unknown): err is Error {
|
|
49
|
+
if (err instanceof HttpError) {
|
|
50
|
+
switch(err.status) {
|
|
51
|
+
case 429:
|
|
52
|
+
case 502:
|
|
53
|
+
case 503:
|
|
54
|
+
case 504:
|
|
55
|
+
return true
|
|
56
|
+
default:
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (err instanceof FetchError) {
|
|
61
|
+
switch(err.type) {
|
|
62
|
+
case 'body-timeout':
|
|
63
|
+
case 'request-timeout':
|
|
64
|
+
return true
|
|
65
|
+
case 'system':
|
|
66
|
+
return err.message.startsWith('request to')
|
|
67
|
+
default:
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
export class HttpError extends Error {
|
|
76
|
+
constructor(
|
|
77
|
+
public readonly status: number,
|
|
78
|
+
public readonly body?: string
|
|
79
|
+
) {
|
|
80
|
+
super(`Got http ${status}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type {ParamType} from '@ethersproject/abi'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// taken from: https://github.com/ethers-io/ethers.js/blob/948f77050dae884fe88932fd88af75560aac9d78/packages/cli/src.ts/typescript.ts#L10
|
|
5
|
+
export function getType(param: ParamType): string {
|
|
6
|
+
if (param.baseType === 'array') {
|
|
7
|
+
return 'Array<' + getType(param.arrayChildren) + '>'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (param.baseType === 'tuple') {
|
|
11
|
+
return getFullTupleType(param.components)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (param.type === 'address' || param.type === 'string') {
|
|
15
|
+
return 'string'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (param.type === 'bool') {
|
|
19
|
+
return 'boolean'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let match = param.type.match(/^(u?int)([0-9]+)$/)
|
|
23
|
+
if (match) {
|
|
24
|
+
return parseInt(match[2]) < 53 ? 'number' : 'ethers.BigNumber'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (param.type.substring(0, 5) === 'bytes') {
|
|
28
|
+
return 'string'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
throw new Error('unknown type')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export function getFullTupleType(params: ParamType[]): string {
|
|
36
|
+
let tuple = getTupleType(params)
|
|
37
|
+
let struct = getStructType(params)
|
|
38
|
+
if (struct == '{}') {
|
|
39
|
+
return tuple
|
|
40
|
+
} else {
|
|
41
|
+
return `(${tuple} & ${struct})`
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export function getTupleType(params: ParamType[]): string {
|
|
47
|
+
return '[' + params.map(p => {
|
|
48
|
+
return p.name ? `${p.name}: ${getType(p)}` : getType(p)
|
|
49
|
+
}).join(', ') + ']'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
// https://github.com/ethers-io/ethers.js/blob/948f77050dae884fe88932fd88af75560aac9d78/packages/abi/src.ts/coders/tuple.ts#L29
|
|
54
|
+
export function getStructType(params: ParamType[]): string {
|
|
55
|
+
let array: any = []
|
|
56
|
+
let counts: Record<string, number> = {}
|
|
57
|
+
for (let p of params) {
|
|
58
|
+
if (p.name && array[p.name] == null) {
|
|
59
|
+
counts[p.name] = (counts[p.name] || 0) + 1
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let fields = params.filter(p => counts[p.name] == 1)
|
|
63
|
+
return '{' + fields.map(f => `${f.name}: ${getType(f)}`).join(', ') + '}'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
export function getReturnType(outputs: ParamType[]) {
|
|
68
|
+
return outputs.length == 1 ? getType(outputs[0]) : getFullTupleType(outputs)
|
|
69
|
+
}
|