@chainlink/ccip-cli 0.0.0 → 0.90.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +238 -0
  3. package/dist/commands/index.d.ts +2 -0
  4. package/dist/commands/index.d.ts.map +1 -0
  5. package/dist/commands/index.js +2 -0
  6. package/dist/commands/index.js.map +1 -0
  7. package/dist/commands/manual-exec.d.ts +56 -0
  8. package/dist/commands/manual-exec.d.ts.map +1 -0
  9. package/dist/commands/manual-exec.js +405 -0
  10. package/dist/commands/manual-exec.js.map +1 -0
  11. package/dist/commands/parse.d.ts +9 -0
  12. package/dist/commands/parse.d.ts.map +1 -0
  13. package/dist/commands/parse.js +47 -0
  14. package/dist/commands/parse.js.map +1 -0
  15. package/dist/commands/send.d.ts +80 -0
  16. package/dist/commands/send.d.ts.map +1 -0
  17. package/dist/commands/send.js +258 -0
  18. package/dist/commands/send.js.map +1 -0
  19. package/dist/commands/show.d.ts +18 -0
  20. package/dist/commands/show.d.ts.map +1 -0
  21. package/dist/commands/show.js +112 -0
  22. package/dist/commands/show.js.map +1 -0
  23. package/dist/commands/supported-tokens.d.ts +37 -0
  24. package/dist/commands/supported-tokens.d.ts.map +1 -0
  25. package/dist/commands/supported-tokens.js +214 -0
  26. package/dist/commands/supported-tokens.js.map +1 -0
  27. package/dist/commands/types.d.ts +7 -0
  28. package/dist/commands/types.d.ts.map +1 -0
  29. package/dist/commands/types.js +6 -0
  30. package/dist/commands/types.js.map +1 -0
  31. package/dist/commands/utils.d.ts +40 -0
  32. package/dist/commands/utils.d.ts.map +1 -0
  33. package/dist/commands/utils.js +330 -0
  34. package/dist/commands/utils.js.map +1 -0
  35. package/dist/index.d.ts +34 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +63 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/providers/aptos.d.ts +15 -0
  40. package/dist/providers/aptos.d.ts.map +1 -0
  41. package/dist/providers/aptos.js +74 -0
  42. package/dist/providers/aptos.js.map +1 -0
  43. package/dist/providers/evm.d.ts +2 -0
  44. package/dist/providers/evm.d.ts.map +1 -0
  45. package/dist/providers/evm.js +42 -0
  46. package/dist/providers/evm.js.map +1 -0
  47. package/dist/providers/index.d.ts +13 -0
  48. package/dist/providers/index.d.ts.map +1 -0
  49. package/dist/providers/index.js +104 -0
  50. package/dist/providers/index.js.map +1 -0
  51. package/dist/providers/solana.d.ts +13 -0
  52. package/dist/providers/solana.d.ts.map +1 -0
  53. package/dist/providers/solana.js +79 -0
  54. package/dist/providers/solana.js.map +1 -0
  55. package/package.json +57 -8
  56. package/src/commands/index.ts +1 -0
  57. package/src/commands/manual-exec.ts +468 -0
  58. package/src/commands/parse.ts +52 -0
  59. package/src/commands/send.ts +316 -0
  60. package/src/commands/show.ts +151 -0
  61. package/src/commands/supported-tokens.ts +245 -0
  62. package/src/commands/types.ts +6 -0
  63. package/src/commands/utils.ts +404 -0
  64. package/src/index.ts +70 -0
  65. package/src/providers/aptos.ts +100 -0
  66. package/src/providers/evm.ts +48 -0
  67. package/src/providers/index.ts +141 -0
  68. package/src/providers/solana.ts +93 -0
  69. package/tsconfig.json +18 -0
@@ -0,0 +1,468 @@
1
+ import {
2
+ type CCIPRequest,
3
+ type CCIPVersion,
4
+ type ChainStatic,
5
+ type EVMChain,
6
+ type ExecutionReport,
7
+ ChainFamily,
8
+ bigIntReplacer,
9
+ calculateManualExecProof,
10
+ discoverOffRamp,
11
+ estimateExecGasForRequest,
12
+ fetchAllMessagesInBatch,
13
+ fetchCCIPRequestsInTx,
14
+ } from '@chainlink/ccip-sdk/src/index.ts'
15
+ import type { Argv } from 'yargs'
16
+
17
+ import type { GlobalOpts } from '../index.ts'
18
+ import { Format } from './types.ts'
19
+ import {
20
+ logParsedError,
21
+ prettyCommit,
22
+ prettyReceipt,
23
+ prettyRequest,
24
+ selectRequest,
25
+ withDateTimestamp,
26
+ } from './utils.ts'
27
+ import { fetchChainsFromRpcs } from '../providers/index.ts'
28
+
29
+ // const MAX_QUEUE = 1000
30
+ // const MAX_EXECS_IN_BATCH = 1
31
+ // const MAX_PENDING_TXS = 25
32
+
33
+ export const command = 'manualExec <tx-hash>'
34
+ export const describe = 'Execute manually pending or failed messages'
35
+
36
+ export const builder = (yargs: Argv) =>
37
+ yargs
38
+ .positional('tx-hash', {
39
+ type: 'string',
40
+ demandOption: true,
41
+ describe: 'transaction hash of the request (source) message',
42
+ })
43
+ .options({
44
+ 'log-index': {
45
+ type: 'number',
46
+ describe: 'Log index of message to execute (if more than one in request tx)',
47
+ },
48
+ 'gas-limit': {
49
+ alias: ['L', 'compute-units'],
50
+ type: 'number',
51
+ describe: 'Override gas limit or compute units for receivers callback (0 keeps original)',
52
+ },
53
+ 'tokens-gas-limit': {
54
+ type: 'number',
55
+ describe: 'Override gas limit for tokens releaseOrMint calls (0 keeps original)',
56
+ },
57
+ 'estimate-gas-limit': {
58
+ type: 'number',
59
+ describe:
60
+ 'Estimate gas limit for receivers callback; argument is a % margin to add to the estimate',
61
+ example: '10',
62
+ conflicts: 'gas-limit',
63
+ },
64
+ wallet: {
65
+ alias: 'w',
66
+ type: 'string',
67
+ describe:
68
+ 'Wallet to send transactions with; pass `ledger[:index_or_derivation]` to use Ledger USB hardware wallet, or private key in `USER_KEY` environment variable',
69
+ },
70
+ 'force-buffer': {
71
+ type: 'boolean',
72
+ describe: 'Forces the usage of buffering for Solana execution.',
73
+ },
74
+ 'force-lookup-table': {
75
+ type: 'boolean',
76
+ describe: 'Forces the creation & usage of an ad-hoc lookup table for Solana execution.',
77
+ },
78
+ 'clear-leftover-accounts': {
79
+ type: 'boolean',
80
+ describe:
81
+ 'Clears buffers (if a previous attempt was aborted) or any ALT owned by this sender.',
82
+ },
83
+ 'sender-queue': {
84
+ type: 'boolean',
85
+ describe: 'Execute all messages in sender queue, starting with the provided tx',
86
+ default: false,
87
+ },
88
+ 'exec-failed': {
89
+ type: 'boolean',
90
+ describe:
91
+ 'Whether to re-execute failed messages (instead of just non-executed) in sender queue',
92
+ implies: 'sender-queue',
93
+ },
94
+ })
95
+
96
+ export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
97
+ if (!argv.wallet) argv.wallet = process.env['USER_KEY'] || process.env['OWNER_KEY']
98
+ let destroy
99
+ const destroy$ = new Promise((resolve) => {
100
+ destroy = resolve
101
+ })
102
+ // argv.senderQueue
103
+ // ? manualExecSenderQueue(providers, argv.tx_hash, argv)
104
+ // : manualExec(argv, destroy$)
105
+ return manualExec(argv, destroy$)
106
+ .catch((err) => {
107
+ process.exitCode = 1
108
+ if (!logParsedError(err)) console.error(err)
109
+ })
110
+ .finally(destroy)
111
+ }
112
+
113
+ async function manualExec(
114
+ argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts,
115
+ destroy: Promise<unknown>,
116
+ ) {
117
+ // messageId not yet implemented for Solana
118
+ const [getChain, tx$] = fetchChainsFromRpcs(argv, argv.txHash, destroy)
119
+ const tx = await tx$
120
+ const source = tx.chain
121
+ const request = await selectRequest(await fetchCCIPRequestsInTx(tx), 'to know more', argv)
122
+
123
+ switch (argv.format) {
124
+ case Format.log: {
125
+ const logPrefix = 'log' in request ? `message ${request.log.index} = ` : 'message = '
126
+ console.log(logPrefix, withDateTimestamp(request))
127
+ break
128
+ }
129
+ case Format.pretty:
130
+ await prettyRequest(source, request)
131
+ break
132
+ case Format.json:
133
+ console.info(JSON.stringify(request, bigIntReplacer, 2))
134
+ break
135
+ }
136
+
137
+ const dest = await getChain(request.lane.destChainSelector)
138
+ const offRamp = await discoverOffRamp(source, dest, request.lane.onRamp)
139
+ const commitStore = await dest.getCommitStoreForOffRamp(offRamp)
140
+ const commit = await dest.fetchCommitReport(commitStore, request, argv)
141
+
142
+ switch (argv.format) {
143
+ case Format.log:
144
+ console.log('commit =', commit)
145
+ break
146
+ case Format.pretty:
147
+ await prettyCommit(dest, commit, request)
148
+ break
149
+ case Format.json:
150
+ console.info(JSON.stringify(commit, bigIntReplacer, 2))
151
+ break
152
+ }
153
+
154
+ const messagesInBatch = await fetchAllMessagesInBatch(source, request, commit.report, argv)
155
+ const execReportProof = calculateManualExecProof(
156
+ messagesInBatch,
157
+ request.lane,
158
+ request.message.header.messageId,
159
+ commit.report.merkleRoot,
160
+ )
161
+
162
+ const offchainTokenData = await source.fetchOffchainTokenData(request)
163
+ const execReport: ExecutionReport = {
164
+ ...execReportProof,
165
+ message: request.message,
166
+ offchainTokenData: offchainTokenData,
167
+ }
168
+
169
+ if (
170
+ argv.estimateGasLimit != null &&
171
+ 'gasLimit' in request.message &&
172
+ 'extraArgs' in request.message
173
+ ) {
174
+ if (dest.network.family !== ChainFamily.EVM)
175
+ throw new Error('Gas estimation is only supported for EVM networks for now')
176
+
177
+ let estimated = await estimateExecGasForRequest(
178
+ source,
179
+ dest as unknown as EVMChain,
180
+ request as CCIPRequest<typeof CCIPVersion.V1_5 | typeof CCIPVersion.V1_6>,
181
+ )
182
+ console.info('Estimated gasLimit override:', estimated)
183
+ estimated += Math.ceil((estimated * argv.estimateGasLimit) / 100)
184
+ if (request.message.gasLimit >= estimated) {
185
+ console.warn(
186
+ 'Estimated +',
187
+ argv.estimateGasLimit,
188
+ '% margin =',
189
+ estimated,
190
+ '< original gasLimit =',
191
+ request.message.gasLimit,
192
+ '. Leaving unchanged.',
193
+ )
194
+ } else {
195
+ argv.gasLimit = estimated
196
+ }
197
+ }
198
+
199
+ const manualExecTx = await dest.executeReport(offRamp, execReport, argv)
200
+
201
+ console.log('🚀 manualExec tx =', manualExecTx.hash, 'to offRamp =', offRamp)
202
+
203
+ let found = false
204
+ for (const log of manualExecTx.logs) {
205
+ const execReceipt = (dest.constructor as ChainStatic).decodeReceipt(log)
206
+ if (!execReceipt) continue
207
+ const timestamp = await dest.getBlockTimestamp(log.blockNumber)
208
+ const receipt = { receipt: execReceipt, log, timestamp }
209
+ switch (argv.format) {
210
+ case Format.log:
211
+ console.log('receipt =', withDateTimestamp(receipt))
212
+ break
213
+ case Format.pretty:
214
+ if (!found) console.info('Receipts (dest):')
215
+ prettyReceipt(
216
+ receipt,
217
+ request,
218
+ receipt.log.tx?.from ??
219
+ (await dest.getTransaction(receipt.log.transactionHash).catch(() => null))?.from,
220
+ )
221
+ break
222
+ case Format.json:
223
+ console.info(JSON.stringify(execReceipt, bigIntReplacer, 2))
224
+ break
225
+ }
226
+ found = true
227
+ }
228
+ if (!found) throw new Error(`Could not find receipt in tx logs`)
229
+ }
230
+
231
+ /*
232
+ export async function manualExecSenderQueue(
233
+ providers: Providers,
234
+ txHash: string,
235
+ argv: {
236
+ gasLimit?: number
237
+ tokensGasLimit?: number
238
+ logIndex?: number
239
+ execFailed?: boolean
240
+ format: Format
241
+ page: number
242
+ wallet?: string
243
+ },
244
+ ) {
245
+ const tx = await providers.getTxReceipt(txHash)
246
+ const source = tx.provider
247
+
248
+ let firstRequest
249
+ if (argv.logIndex != null) {
250
+ firstRequest = await fetchCCIPMessageInLog(tx, argv.logIndex)
251
+ } else {
252
+ firstRequest = await selectRequest(await fetchCCIPMessagesInTx(tx), 'to execute')
253
+ }
254
+ switch (argv.format) {
255
+ case Format.log:
256
+ console.log(`message ${firstRequest.log.index} =`, withDateTimestamp(firstRequest))
257
+ break
258
+ case Format.pretty:
259
+ await prettyRequest(source, firstRequest)
260
+ break
261
+ case Format.json:
262
+ console.info(JSON.stringify(firstRequest, bigIntReplacer, 2))
263
+ break
264
+ }
265
+
266
+ const dest = await providers.forChainId(chainIdFromSelector(firstRequest.lane.destChainSelector))
267
+
268
+ const requests: Omit<CCIPRequest, 'timestamp' | 'tx'>[] = []
269
+ for await (const request of fetchRequestsForSender(source, firstRequest)) {
270
+ requests.push(request)
271
+ if (requests.length >= MAX_QUEUE) break
272
+ }
273
+ console.info('Found', requests.length, `requests for "${firstRequest.message.sender}"`)
274
+ if (!requests.length) return
275
+
276
+ let startBlock = await getSomeBlockNumberBefore(dest, firstRequest.timestamp)
277
+ const wallet = (await getWallet(argv)).connect(dest)
278
+ const offRampContract = await discoverOffRamp(wallet, firstRequest.lane, {
279
+ fromBlock: startBlock,
280
+ page: argv.page,
281
+ })
282
+ const senderNonce = await offRampContract.getSenderNonce(firstRequest.message.sender)
283
+ const origRequestsCnt = requests.length,
284
+ last = requests[requests.length - 1]
285
+ while (requests.length && requests[0].message.header.sequenceNumber <= senderNonce) {
286
+ requests.shift()
287
+ }
288
+ console.info(
289
+ 'Found',
290
+ requests.length,
291
+ `requests for "${firstRequest.message.sender}", removed `,
292
+ origRequestsCnt - requests.length,
293
+ 'already executed before senderNonce =',
294
+ senderNonce,
295
+ '. Last source txHash =',
296
+ last.log.transactionHash,
297
+ )
298
+ if (!requests.length) return
299
+ let nonce = await wallet.getNonce()
300
+
301
+ let lastBatch:
302
+ | readonly [CCIPCommit, Omit<CCIPRequest<CCIPVersion>, 'tx' | 'timestamp'>[]]
303
+ | undefined
304
+ const txsPending = []
305
+ for (let i = 0; i < requests.length; ) {
306
+ let commit, batch
307
+ if (!lastBatch || requests[i].message.header.sequenceNumber > lastBatch[0].report.maxSeqNr) {
308
+ commit = await fetchCommitReport(dest, requests[i], {
309
+ startBlock,
310
+ page: argv.page,
311
+ })
312
+ startBlock = commit.log.blockNumber + 1
313
+
314
+ batch = await fetchAllMessagesInBatch(
315
+ source,
316
+ requests[i].lane.destChainSelector,
317
+ requests[i].log,
318
+ commit.report,
319
+ { page: argv.page },
320
+ )
321
+ lastBatch = [commit, batch]
322
+ } else {
323
+ ;[commit, batch] = lastBatch
324
+ }
325
+
326
+ const msgIdsToExec = [] as string[]
327
+ while (
328
+ i < requests.length &&
329
+ requests[i].message.header.sequenceNumber <= commit.report.maxSeqNr &&
330
+ msgIdsToExec.length < MAX_EXECS_IN_BATCH
331
+ ) {
332
+ msgIdsToExec.push(requests[i++].message.header.messageId)
333
+ }
334
+
335
+ const manualExecReport = calculateManualExecProof(
336
+ batch.map(({ message }) => message),
337
+ firstRequest.lane,
338
+ msgIdsToExec,
339
+ commit.report.merkleRoot,
340
+ )
341
+ const requestsToExec = manualExecReport.messages.map(
342
+ ({ header }) =>
343
+ requests.find(({ message }) => message.header.messageId === header.messageId)!,
344
+ )
345
+ const offchainTokenData = await Promise.all(
346
+ requestsToExec.map(async (request) => {
347
+ const tx = await lazyCached(`tx ${request.log.transactionHash}`, () =>
348
+ source.getTransactionReceipt(request.log.transactionHash).then((res) => {
349
+ if (!res) throw new Error(`Tx not found: ${request.log.transactionHash}`)
350
+ return res
351
+ }),
352
+ )
353
+ return fetchOffchainTokenData({ ...request, tx })
354
+ }),
355
+ )
356
+ const execReport = { ...manualExecReport, offchainTokenData }
357
+ const getGasLimitOverride = (message: { gasLimit: bigint } | { extraArgs: string }): bigint => {
358
+ if (argv.gasLimit != null) {
359
+ const argvGasLimit = BigInt(argv.gasLimit)
360
+ let msgGasLimit
361
+ if ('gasLimit' in message) {
362
+ msgGasLimit = message.gasLimit
363
+ } else {
364
+ const parsedArgs = parseExtraArgs(message.extraArgs, source.network.family)
365
+ if (!parsedArgs || !('gasLimit' in parsedArgs) || !parsedArgs.gasLimit) {
366
+ throw new Error(`Missing gasLimit argument`)
367
+ }
368
+ msgGasLimit = BigInt(parsedArgs.gasLimit)
369
+ }
370
+ if (argvGasLimit > msgGasLimit) {
371
+ return argvGasLimit
372
+ }
373
+ }
374
+ return 0n
375
+ }
376
+
377
+ let manualExecTx
378
+ if (firstRequest.lane.version === CCIPVersion.V1_2) {
379
+ const gasOverrides = manualExecReport.messages.map((message) =>
380
+ getGasLimitOverride(message as CCIPMessage<typeof CCIPVersion.V1_2>),
381
+ )
382
+ manualExecTx = await (
383
+ offRampContract as CCIPContract<typeof CCIPContractType.OffRamp, typeof CCIPVersion.V1_2>
384
+ ).manuallyExecute(
385
+ execReport as {
386
+ offchainTokenData: string[][]
387
+ messages: CCIPMessage<typeof CCIPVersion.V1_2>[]
388
+ proofs: string[]
389
+ proofFlagBits: bigint
390
+ },
391
+ gasOverrides,
392
+ { nonce: nonce++, gasLimit: argv.gasLimit ? argv.gasLimit : undefined },
393
+ )
394
+ } else if (firstRequest.lane.version === CCIPVersion.V1_5) {
395
+ const gasOverrides = manualExecReport.messages.map((message) => ({
396
+ receiverExecutionGasLimit: getGasLimitOverride(
397
+ message as CCIPMessage<typeof CCIPVersion.V1_5>,
398
+ ),
399
+ tokenGasOverrides: message.tokenAmounts.map(() => BigInt(argv.tokensGasLimit ?? 0)),
400
+ }))
401
+ manualExecTx = await (
402
+ offRampContract as CCIPContract<typeof CCIPContractType.OffRamp, typeof CCIPVersion.V1_5>
403
+ ).manuallyExecute(
404
+ execReport as {
405
+ offchainTokenData: string[][]
406
+ messages: CCIPMessage<typeof CCIPVersion.V1_5>[]
407
+ proofs: string[]
408
+ proofFlagBits: bigint
409
+ },
410
+ gasOverrides,
411
+ { nonce: nonce++, gasLimit: argv.gasLimit ? argv.gasLimit : undefined },
412
+ )
413
+ } else {
414
+ const gasOverrides = manualExecReport.messages.map((message) => ({
415
+ receiverExecutionGasLimit: getGasLimitOverride(
416
+ message as CCIPMessage<typeof CCIPVersion.V1_6>,
417
+ ),
418
+ tokenGasOverrides: message.tokenAmounts.map(() => BigInt(argv.tokensGasLimit ?? 0)),
419
+ }))
420
+ manualExecTx = await (
421
+ offRampContract as CCIPContract<typeof CCIPContractType.OffRamp, typeof CCIPVersion.V1_6>
422
+ ).manuallyExecute(
423
+ [
424
+ {
425
+ sourceChainSelector: firstRequest.lane.sourceChainSelector,
426
+ messages: execReport.messages as (CCIPMessage<typeof CCIPVersion.V1_6> & {
427
+ gasLimit: bigint
428
+ })[],
429
+ proofs: execReport.proofs,
430
+ proofFlagBits: execReport.proofFlagBits,
431
+ offchainTokenData: execReport.offchainTokenData,
432
+ },
433
+ ],
434
+ [gasOverrides],
435
+ { nonce: nonce++, gasLimit: argv.gasLimit ? argv.gasLimit : undefined },
436
+ )
437
+ }
438
+
439
+ const toExec = requests[i - 1] // log only request data for last msg in msgIdsToExec
440
+ console.log(
441
+ `🚀 [${i}/${requests.length}, ${batch.length} batch, ${msgIdsToExec.length} to exec]`,
442
+ 'source tx =',
443
+ toExec.log.transactionHash,
444
+ 'msgId =',
445
+ toExec.message.header.messageId,
446
+ 'nonce =',
447
+ toExec.message.header.nonce,
448
+ 'manualExec tx =',
449
+ manualExecTx.hash,
450
+ 'to =',
451
+ manualExecTx.to,
452
+ 'gasLimit =',
453
+ manualExecTx.gasLimit,
454
+ )
455
+ txsPending.push(manualExecTx)
456
+ if (txsPending.length >= MAX_PENDING_TXS) {
457
+ console.debug(
458
+ 'awaiting',
459
+ txsPending.length,
460
+ 'txs:',
461
+ txsPending.map((tx) => tx.hash),
462
+ )
463
+ await txsPending[txsPending.length - 1].wait()
464
+ txsPending.length = 0
465
+ }
466
+ }
467
+ }
468
+ */
@@ -0,0 +1,52 @@
1
+ import { bigIntReplacer, supportedChains } from '@chainlink/ccip-sdk/src/index.ts'
2
+ import type { Argv } from 'yargs'
3
+
4
+ import type { GlobalOpts } from '../index.ts'
5
+ import { Format } from './types.ts'
6
+ import { prettyTable } from './utils.ts'
7
+
8
+ export const command = ['parse <data>', 'parseBytes <data>', 'parseData <data>']
9
+ export const describe =
10
+ 'Try to parse and print errors, revert reasons or function call or event data'
11
+
12
+ export const builder = (yargs: Argv) =>
13
+ yargs.positional('data', {
14
+ type: 'string',
15
+ demandOption: true,
16
+ describe: 'router contract address on source',
17
+ })
18
+
19
+ export function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
20
+ try {
21
+ parseBytes(argv)
22
+ } catch (err) {
23
+ process.exitCode = 1
24
+ console.error(err)
25
+ }
26
+ }
27
+
28
+ function parseBytes(argv: Parameters<typeof handler>[0]) {
29
+ let parsed
30
+ for (const chain of Object.values(supportedChains)) {
31
+ try {
32
+ parsed = chain.parse?.(argv.data)
33
+ if (parsed) break
34
+ } catch (_) {
35
+ // pass
36
+ }
37
+ }
38
+ if (!parsed) throw new Error('Unknown data')
39
+
40
+ switch (argv.format) {
41
+ case Format.log: {
42
+ console.log(`parsed =`, parsed)
43
+ break
44
+ }
45
+ case Format.pretty:
46
+ prettyTable(parsed)
47
+ break
48
+ case Format.json:
49
+ console.info(JSON.stringify(parsed, bigIntReplacer, 2))
50
+ break
51
+ }
52
+ }