dequanto 0.2.8 → 0.2.12

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 (57) hide show
  1. package/lib/cjs/clients/debug/ClientDebugMethods.js +3 -13
  2. package/lib/cjs/clients/debug/ClientDebugMethods.js.map +1 -1
  3. package/lib/cjs/hardhat/HardhatProvider.js +7 -2
  4. package/lib/cjs/hardhat/HardhatProvider.js.map +1 -1
  5. package/lib/cjs/hardhat/HardhatWeb3Client.js +4 -0
  6. package/lib/cjs/hardhat/HardhatWeb3Client.js.map +1 -1
  7. package/lib/cjs/txs/TxDataBuilder.js +20 -0
  8. package/lib/cjs/txs/TxDataBuilder.js.map +1 -1
  9. package/lib/cjs/utils/$color.js +2 -2
  10. package/lib/cjs/utils/$color.js.map +1 -1
  11. package/lib/cjs/utils/$contract.js +10 -0
  12. package/lib/cjs/utils/$contract.js.map +1 -1
  13. package/lib/cjs/utils/$require.js +7 -0
  14. package/lib/cjs/utils/$require.js.map +1 -1
  15. package/lib/cjs/utils/$traces.js +492 -0
  16. package/lib/cjs/utils/$traces.js.map +1 -0
  17. package/lib/esm/clients/debug/ClientDebugMethods.js.map +1 -1
  18. package/lib/esm/clients/debug/ClientDebugMethods.mjs +3 -13
  19. package/lib/esm/hardhat/HardhatProvider.js.map +1 -1
  20. package/lib/esm/hardhat/HardhatProvider.mjs +7 -2
  21. package/lib/esm/hardhat/HardhatWeb3Client.js.map +1 -1
  22. package/lib/esm/hardhat/HardhatWeb3Client.mjs +4 -0
  23. package/lib/esm/txs/TxDataBuilder.js.map +1 -1
  24. package/lib/esm/txs/TxDataBuilder.mjs +20 -0
  25. package/lib/esm/utils/$color.js.map +1 -1
  26. package/lib/esm/utils/$color.mjs +1 -1
  27. package/lib/esm/utils/$contract.js.map +1 -1
  28. package/lib/esm/utils/$contract.mjs +10 -0
  29. package/lib/esm/utils/$require.js.map +1 -1
  30. package/lib/esm/utils/$require.mjs +7 -0
  31. package/lib/esm/utils/$traces.js.map +1 -0
  32. package/lib/esm/utils/$traces.mjs +486 -0
  33. package/lib/types/clients/debug/ClientDebugMethods.d.ts +18 -0
  34. package/lib/types/clients/interfaces/IWeb3Client.d.ts +4 -0
  35. package/lib/types/models/TEth.d.ts +2 -2
  36. package/lib/types/utils/$color.d.ts +62 -0
  37. package/lib/types/utils/$contract.d.ts +8 -0
  38. package/lib/types/utils/$require.d.ts +2 -0
  39. package/lib/types/utils/$traces.d.ts +18 -0
  40. package/package.json +1 -1
  41. package/src/clients/debug/ClientDebugMethods.ts +23 -14
  42. package/src/clients/interfaces/IWeb3Client.ts +4 -0
  43. package/src/hardhat/HardhatProvider.ts +8 -3
  44. package/src/hardhat/HardhatWeb3Client.ts +4 -0
  45. package/src/models/TEth.ts +2 -2
  46. package/src/txs/TxDataBuilder.ts +20 -0
  47. package/src/types/TAbi.ts +1 -1
  48. package/src/utils/$color.ts +1 -1
  49. package/src/utils/$contract.ts +17 -1
  50. package/src/utils/$require.ts +9 -0
  51. package/src/utils/$traces.ts +600 -0
  52. package/lib/cjs/utils/$sign.js +0 -118
  53. package/lib/cjs/utils/$sign.js.map +0 -1
  54. package/lib/esm/utils/$sign.js.map +0 -1
  55. package/lib/esm/utils/$sign.mjs +0 -117
  56. package/lib/types/utils/$sign.d.ts +0 -1
  57. package/src/utils/$sign.ts +0 -136
@@ -0,0 +1,600 @@
1
+ import { $abiCoder } from '@dequanto/abi/$abiCoder';
2
+ import { TClientDebugTraces } from '@dequanto/clients/debug/ClientDebugMethods';
3
+ import { TEth } from '@dequanto/models/TEth';
4
+ import { $abiUtils } from './$abiUtils';
5
+ import { $bigint } from './$bigint';
6
+ import { $hex } from '@dequanto/utils/$hex';
7
+ import { $color, ColorData } from './$color';
8
+ import alot from 'alot';
9
+
10
+ export namespace $traces {
11
+
12
+ type TStructLog = TClientDebugTraces['structLogs'][0];
13
+ type TAbiMeta = {
14
+ name?: string
15
+ contractName?: string
16
+ abi: TEth.Abi.Item[]
17
+ };
18
+ type TTraceEvent = {
19
+ index: number
20
+ op: string
21
+ topics: TEth.Hex[]
22
+ data: TEth.Hex
23
+ label?: string
24
+ };
25
+
26
+ type TTraceFrame = {
27
+ id: number
28
+ parentId: number | null
29
+ depth: number
30
+ start: number
31
+ end: number
32
+ callSite: number | null
33
+ callOp: string
34
+ to: TEth.Address | null
35
+ value: string | number | bigint | null
36
+ input: TEth.Hex | null
37
+ selector: TEth.Hex | null
38
+ output: TEth.Hex | null
39
+ children: TTraceFrame[]
40
+ events: TTraceEvent[]
41
+ failed?: boolean
42
+ exitOp?: string
43
+ label?: string
44
+ };
45
+
46
+ const CALL_OPS = new Set([ 'CALL', 'STATICCALL', 'DELEGATECALL', 'CALLCODE' ]);
47
+ const FAILURE_OPS = new Set([ 'REVERT', 'INVALID' ]);
48
+
49
+ export async function format (params: {
50
+ tx: TEth.TxLike
51
+ traces: TClientDebugTraces
52
+ getABI (contract: TEth.Address): Promise<TAbiMeta>
53
+ color?: boolean
54
+ shortAddress?: boolean
55
+ addresses?: Record<TEth.Address, string>,
56
+ }): Promise<string> {
57
+ const logs = params.traces.structLogs ?? [];
58
+ const frames = logs.length > 0
59
+ ? buildFrames(logs)
60
+ : [];
61
+ const abiCache = new Map<string, Promise<TAbiMeta | null>>();
62
+
63
+ const entryAbiMeta = params.tx.to != null
64
+ ? await getAbiMeta(params.tx.to)
65
+ : null;
66
+ const entryAbiItem = getFunctionAbiItem(entryAbiMeta, getSelector(params.tx));
67
+
68
+ if (frames[0] != null) {
69
+ frames[0].to = params.tx.to ?? null;
70
+ frames[0].value = params.tx.value ?? null;
71
+ frames[0].input = getInput(params.tx);
72
+ frames[0].selector = getSelector(params.tx);
73
+ frames[0].output = params.traces.returnValue ?? frames[0].output;
74
+ }
75
+
76
+ await Promise.all(frames.map(async frame => {
77
+ let abiMeta = frame.to != null
78
+ ? await getAbiMeta(frame.to)
79
+ : null;
80
+ let abiItem = getFunctionAbiItem(abiMeta, frame.selector);
81
+
82
+ if (frame.id !== 0) {
83
+ frame.label = formatFrameLabel(frame, abiMeta?.contractName ?? abiMeta?.name, abiItem, params);
84
+ }
85
+ for (let event of frame.events) {
86
+ event.label = formatEventLabel(event, abiMeta, params);
87
+ }
88
+ }));
89
+
90
+ for (let frame of frames) {
91
+ if (frame.id !== 0) {
92
+ frame.label ??= formatFrameLabel(frame, null, null, params);
93
+ }
94
+ for (let event of frame.events) {
95
+ event.label ??= formatEventLabel(event, null, params);
96
+ }
97
+ }
98
+
99
+ let lines: string[] = [];
100
+ let entryLine = formatEntrypointLabel(params.tx, params.traces, entryAbiMeta?.contractName ?? entryAbiMeta?.name, entryAbiItem, params);
101
+ lines.push(`${entryLine}${params.traces.failed ? ' [FAILED]' : ''}`);
102
+
103
+ let root = frames[0];
104
+ if (root != null) {
105
+ appendEntries(root, 1, lines);
106
+ }
107
+
108
+ let addresses = alot.fromObject(params.addresses ?? {}).map(x => {
109
+ return `${x.key} <=> ${x.value}`;
110
+ }).toArray();
111
+
112
+ lines.push('\n', ...addresses);
113
+
114
+ return lines.join('\n');
115
+
116
+ async function getAbiMeta(contract: TEth.Address): Promise<TAbiMeta | null> {
117
+ let key = contract.toLowerCase();
118
+ if (abiCache.has(key) === false) {
119
+ abiCache.set(key, params.getABI(contract).catch(() => null));
120
+ }
121
+ return await abiCache.get(key);
122
+ }
123
+
124
+ function appendEntries(frame: TTraceFrame, level: number, out: string[]) {
125
+ let indent = ' '.repeat(level);
126
+ let entries = [
127
+ ...frame.children.map(child => ({ kind: 'frame' as const, index: child.start, frame: child })),
128
+ ...frame.events.map(event => ({ kind: 'event' as const, index: event.index, event })),
129
+ ].sort((a, b) => a.index - b.index);
130
+
131
+ for (let entry of entries) {
132
+ if (entry.kind === 'frame') {
133
+ out.push(`${indent}${entry.frame.label}${entry.frame.failed ? ' [FAILED]' : ''}`);
134
+ appendEntries(entry.frame, level + 1, out);
135
+ continue;
136
+ }
137
+ out.push(`${indent}${entry.event.label}`);
138
+ }
139
+ }
140
+ }
141
+
142
+ function buildFrames(logs: TStructLog[]): TTraceFrame[] {
143
+ let root: TTraceFrame = {
144
+ id: 0,
145
+ parentId: null,
146
+ depth: logs[0].depth,
147
+ start: 0,
148
+ end: logs.length - 1,
149
+ callSite: null,
150
+ callOp: 'ROOT',
151
+ to: null,
152
+ value: null,
153
+ input: null,
154
+ selector: null,
155
+ output: null,
156
+ children: [],
157
+ events: []
158
+ };
159
+
160
+ let frames = [ root ];
161
+ let open = [ root ];
162
+
163
+ for (let i = 1; i < logs.length; i++) {
164
+ let prev = logs[i - 1];
165
+ let current = logs[i];
166
+
167
+ while (open.length > 0 && open[open.length - 1].depth > current.depth) {
168
+ let closing = open.pop();
169
+ closing.end = i - 1;
170
+ }
171
+
172
+ let active = open[open.length - 1];
173
+ if (current.depth > active.depth) {
174
+ let meta = CALL_OPS.has(prev.op)
175
+ ? getCallMeta(prev)
176
+ : { op: prev.op, to: null, value: null, input: null, selector: null };
177
+
178
+ let frame: TTraceFrame = {
179
+ id: frames.length,
180
+ parentId: active.id,
181
+ depth: current.depth,
182
+ start: i,
183
+ end: i,
184
+ callSite: i - 1,
185
+ callOp: meta.op,
186
+ to: meta.to,
187
+ value: meta.value,
188
+ input: meta.input,
189
+ selector: meta.selector,
190
+ output: null,
191
+ children: [],
192
+ events: []
193
+ };
194
+ active.children.push(frame);
195
+ frames.push(frame);
196
+ open.push(frame);
197
+ active = frame;
198
+ }
199
+
200
+ let event = getEventMeta(current, i);
201
+ if (event != null) {
202
+ active.events.push(event);
203
+ }
204
+
205
+ active.end = i;
206
+ }
207
+
208
+ while (open.length > 0) {
209
+ let frame = open.pop();
210
+ frame.end = logs.length - 1;
211
+ }
212
+
213
+ for (let frame of frames) {
214
+ let endLog = logs[frame.end];
215
+ frame.exitOp = endLog?.op;
216
+ frame.failed = FAILURE_OPS.has(frame.exitOp);
217
+ frame.output = getOutputMeta(endLog);
218
+ }
219
+
220
+ return frames;
221
+ }
222
+
223
+ function getCallMeta(log: TStructLog): {
224
+ op: string
225
+ to: TEth.Address | null
226
+ value: bigint | null
227
+ input: TEth.Hex | null
228
+ selector: TEth.Hex | null
229
+ } {
230
+ let stack = log.stack ?? [];
231
+ let to: TEth.Address | null = null;
232
+ let value: bigint | null = null;
233
+ let inputOffset = 0;
234
+ let inputSize = 0;
235
+
236
+ if (log.op === 'CALL' || log.op === 'CALLCODE') {
237
+ if (stack.length < 7) {
238
+ return { op: log.op, to: null, value: null, input: null, selector: null };
239
+ }
240
+ to = toAddress(stack[stack.length - 2]);
241
+ value = toBigInt(stack[stack.length - 3]);
242
+ inputOffset = toNumber(stack[stack.length - 4]);
243
+ inputSize = toNumber(stack[stack.length - 5]);
244
+ } else if (log.op === 'STATICCALL' || log.op === 'DELEGATECALL') {
245
+ if (stack.length < 6) {
246
+ return { op: log.op, to: null, value: null, input: null, selector: null };
247
+ }
248
+ to = toAddress(stack[stack.length - 2]);
249
+ inputOffset = toNumber(stack[stack.length - 3]);
250
+ inputSize = toNumber(stack[stack.length - 4]);
251
+ } else {
252
+ return { op: log.op, to: null, value: null, input: null, selector: null };
253
+ }
254
+
255
+ let input = readMemory(log.memory ?? [], inputOffset, inputSize);
256
+ let selector = input != null && input.length >= 10
257
+ ? input.slice(0, 10) as TEth.Hex
258
+ : null;
259
+
260
+ return {
261
+ op: log.op,
262
+ to,
263
+ value,
264
+ input,
265
+ selector,
266
+ };
267
+ }
268
+
269
+ function getEventMeta(log: TStructLog, index: number): TTraceEvent | null {
270
+ let match = /^LOG([0-4])$/.exec(log.op);
271
+ if (match == null) {
272
+ return null;
273
+ }
274
+ let topicCount = Number(match[1]);
275
+ let stack = log.stack ?? [];
276
+ if (stack.length < topicCount + 2) {
277
+ return null;
278
+ }
279
+ let offset = toNumber(stack[stack.length - 1]);
280
+ let size = toNumber(stack[stack.length - 2]);
281
+ let topics = Array.from({ length: topicCount }, (_, i) => {
282
+ return $hex.ensure(stack[stack.length - 3 - i]) as TEth.Hex;
283
+ });
284
+ let data = readMemory(log.memory ?? [], offset, size) ?? '0x';
285
+ return {
286
+ index,
287
+ op: log.op,
288
+ topics,
289
+ data,
290
+ };
291
+ }
292
+
293
+ function readMemory(memory: string[], offset: number, size: number): TEth.Hex | null {
294
+ if (size <= 0 || memory.length === 0) {
295
+ return null;
296
+ }
297
+ let hex = memory.join('');
298
+ let start = offset * 2;
299
+ let end = start + size * 2;
300
+ return `0x${hex.slice(start, end)}` as TEth.Hex;
301
+ }
302
+
303
+ function formatEntrypointLabel(
304
+ tx: TEth.TxLike,
305
+ traces: TClientDebugTraces,
306
+ contractName: string | null,
307
+ abi: TEth.Abi.Item | null,
308
+ params: {
309
+ color?: boolean
310
+ }
311
+ ): string {
312
+ let frame: TTraceFrame = {
313
+ id: -1,
314
+ parentId: null,
315
+ depth: 0,
316
+ start: 0,
317
+ end: 0,
318
+ callSite: null,
319
+ callOp: '',
320
+ to: tx.to ?? null,
321
+ value: tx.value ?? null,
322
+ input: getInput(tx),
323
+ selector: getSelector(tx),
324
+ output: traces.returnValue ?? null,
325
+ children: [],
326
+ events: [],
327
+ failed: traces.failed,
328
+ exitOp: null,
329
+ };
330
+ return formatFrameLabel(frame, contractName, abi, params);
331
+ }
332
+
333
+ function formatFrameLabel(frame: TTraceFrame, contractName: string | null, abi: TEth.Abi.Item | null, params: {
334
+ shortAddress?: boolean
335
+ color?: boolean
336
+ addresses?: Record<string, string>
337
+ }): string {
338
+ let head = color('gray', frame.callOp.toLowerCase(), params);
339
+ let suffix = '';
340
+
341
+ if (frame.to != null) {
342
+ let address = shortAddress(frame.to, params);
343
+ if (contractName != null && abi != null && 'name' in abi && abi.name != null) {
344
+ let args = frame.input != null
345
+ ? decodeArgs(abi, frame.input, params)
346
+ : '';
347
+ if (params != null) {
348
+ params.addresses ??= {};
349
+ params.addresses[address] = contractName;
350
+ }
351
+ suffix = `${color('bold', contractName, params)}.${color('magenta', abi.name, params)}(${args})`;
352
+ } else {
353
+ suffix = `${address}${frame.selector ? ` ${frame.selector}` : ''}`;
354
+ if (frame.input != null) {
355
+ suffix += ` calldata=${shortHex(frame.input, 18, 10)}`;
356
+ }
357
+ }
358
+ }
359
+
360
+ if (frame.value != null && frame.value !== 0n) {
361
+ suffix += `${suffix ? ' ' : ''}`;
362
+ if (frame.value && BigInt(frame.value) > 0n) {
363
+ suffix += `value=${frame.value.toString()}`
364
+ }
365
+ }
366
+ if (frame.exitOp != null && frame.exitOp !== 'STOP' && frame.exitOp !== 'RETURN') {
367
+ suffix += `${suffix ? ' ' : ''}exit=${frame.exitOp}`;
368
+ }
369
+ if (frame.output != null) {
370
+ let returnValue = formatReturnValue(frame, abi, params);
371
+ suffix += `: ${returnValue}`;
372
+ }
373
+
374
+ return suffix ? `${head} ${suffix}` : head;
375
+ }
376
+
377
+ function formatEventLabel(event: TTraceEvent, abiMeta: TAbiMeta | null | undefined, params: {
378
+ color?: boolean
379
+ }): string {
380
+ let abi = getEventAbiItem(abiMeta, event.topics);
381
+ if (abi == null) {
382
+ let head = color('cyan', 'event', params);
383
+ let name = event.topics[0] != null
384
+ ? shortHex(event.topics[0], 18, 10)
385
+ : event.op;
386
+ return `${head} ${name}`;
387
+ }
388
+
389
+ let args = decodeEventArgs(abi, event, params);
390
+ return `${color('cyan', 'event', params)} ${color('magenta', abi.name, params)}(${args})`;
391
+ }
392
+
393
+ function getFunctionAbiItem(abiMeta: TAbiMeta | null | undefined, selector: TEth.Hex | null): TEth.Abi.Item | null {
394
+ if (abiMeta == null || selector == null) {
395
+ return null;
396
+ }
397
+ return abiMeta.abi.find(item => {
398
+ if (item.type !== 'function' || !('name' in item)) {
399
+ return false;
400
+ }
401
+ return $abiUtils.getMethodSignature(item) === selector;
402
+ }) ?? null;
403
+ }
404
+
405
+ function getEventAbiItem(abiMeta: TAbiMeta | null | undefined, topics: TEth.Hex[]): TEth.Abi.Item | null {
406
+ if (abiMeta == null) {
407
+ return null;
408
+ }
409
+ return abiMeta.abi.find(item => {
410
+ if (item.type !== 'event' || !('name' in item) || item.name == null) {
411
+ return false;
412
+ }
413
+ if ('anonymous' in item && item.anonymous === true) {
414
+ return topics.length === (item.inputs?.filter(x => x.indexed).length ?? 0);
415
+ }
416
+ return topics[0] === $abiUtils.getTopicSignature(item);
417
+ }) ?? null;
418
+ }
419
+
420
+ function decodeArgs(abi: TEth.Abi.Item, input: TEth.Hex, params: {
421
+ color?: boolean
422
+ }): string {
423
+ try {
424
+ let inputs = 'inputs' in abi && Array.isArray(abi.inputs)
425
+ ? abi.inputs
426
+ : [];
427
+ if (inputs.length === 0) {
428
+ return '';
429
+ }
430
+ let data = `0x${input.slice(10)}` as TEth.Hex;
431
+ let values = $abiCoder.decode(inputs as any, data);
432
+ return inputs.map((param, index) => {
433
+ let name = param.name?.trim() || `arg${index}`;
434
+ return `${name}: ${formatValue(values[index], params)}`;
435
+ }).join(', ');
436
+ } catch {
437
+ return shortHex(input, 18, 10);
438
+ }
439
+ }
440
+
441
+ function decodeEventArgs(abi: TEth.Abi.Item, event: TTraceEvent, params: {
442
+ color?: boolean
443
+ }): string {
444
+ try {
445
+ let inputs = 'inputs' in abi && Array.isArray(abi.inputs)
446
+ ? abi.inputs
447
+ : [];
448
+ if (inputs.length === 0) {
449
+ return '';
450
+ }
451
+ let dataInputs = inputs.filter(x => x.indexed !== true);
452
+ let dataValues = dataInputs.length > 0
453
+ ? $abiCoder.decode(dataInputs as any, event.data)
454
+ : [];
455
+ let topicIndex = 'anonymous' in abi && abi.anonymous === true ? 0 : 1;
456
+ let dataIndex = 0;
457
+
458
+ return inputs.map((param, index) => {
459
+ let name = param.name?.trim() || `arg${index}`;
460
+ let value;
461
+ if (param.indexed === true) {
462
+ let topic = event.topics[topicIndex++];
463
+ if (topic == null) {
464
+ value = 'undefined';
465
+ } else if ($abiUtils.isDynamicType(param.type)) {
466
+ value = topic;
467
+ } else {
468
+ value = $abiCoder.decode([ param ] as any, topic)[0];
469
+ }
470
+ } else {
471
+ value = dataValues[dataIndex++];
472
+ }
473
+ return `${name}: ${formatValue(value, params)}`;
474
+ }).join(', ');
475
+ } catch {
476
+ let rendered = [
477
+ ...event.topics.map(topic => shortHex(topic, 18, 10)),
478
+ event.data !== '0x' ? shortHex(event.data, 18, 10) : null,
479
+ ].filter(Boolean);
480
+ return rendered.join(', ');
481
+ }
482
+ }
483
+
484
+ function formatReturnValue(frame: TTraceFrame, abi: TEth.Abi.Item | null, params: {
485
+ color?: boolean
486
+ }): string {
487
+ if (frame.output == null) {
488
+ return '0x';
489
+ }
490
+ if (frame.failed !== true && abi != null) {
491
+ try {
492
+ let outputs = 'outputs' in abi && Array.isArray(abi.outputs)
493
+ ? abi.outputs
494
+ : [];
495
+ if (outputs.length > 0) {
496
+ let values = $abiCoder.decode(outputs as any, frame.output);
497
+ return outputs.map((param, index) => {
498
+ let name = param.name?.trim();
499
+ if (!name && outputs.length > 1) {
500
+ name = `out${index}`;
501
+ }
502
+ if (name) {
503
+ name += ': ';
504
+ }
505
+ return `${name ?? ''}${formatValue(values[index], params)}`;
506
+ }).join(', ');
507
+ }
508
+ } catch {
509
+ // Fall back to raw bytes if decoding fails.
510
+ }
511
+ }
512
+ return shortHex(frame.output, 18, 10);
513
+ }
514
+
515
+ function getOutputMeta(log: TStructLog | null | undefined): TEth.Hex | null {
516
+ if (log == null || (log.op !== 'RETURN' && log.op !== 'REVERT')) {
517
+ return null;
518
+ }
519
+ let stack = log.stack ?? [];
520
+ if (stack.length < 2) {
521
+ return null;
522
+ }
523
+ let offset = toNumber(stack[stack.length - 1]);
524
+ let size = toNumber(stack[stack.length - 2]);
525
+ return readMemory(log.memory ?? [], offset, size);
526
+ }
527
+
528
+ function getInput(tx: TEth.TxLike): TEth.Hex | null {
529
+ return tx.data ?? tx.input ?? null;
530
+ }
531
+
532
+ function getSelector(tx: TEth.TxLike): TEth.Hex | null {
533
+ let input = getInput(tx);
534
+ return input != null && input.length >= 10
535
+ ? input.slice(0, 10) as TEth.Hex
536
+ : null;
537
+ }
538
+
539
+ function formatValue(value: any, params: { color?: boolean }): string {
540
+ if (typeof value === 'bigint') {
541
+ return color('cyan', value.toString(), params);
542
+ }
543
+ if (Array.isArray(value)) {
544
+ return `[${value.map(x => formatValue(x, params)).join(', ')}]`;
545
+ }
546
+ if (value != null && typeof value === 'object') {
547
+ let entries = Object.entries(value);
548
+ return `{ ${entries.map(([ key, val ]) => `${key}: ${formatValue(val, params)}`).join(', ')} }`;
549
+ }
550
+ if (typeof value === 'string') {
551
+ if (/^0x[a-fA-F0-9]{40}$/.test(value)) {
552
+ return value as TEth.Address;
553
+ }
554
+ if (/^0x[a-fA-F0-9]{18,}$/.test(value)) {
555
+ return shortHex(value as TEth.Hex, 18, 10);
556
+ }
557
+ return color('yellow', value, params);
558
+ }
559
+ return String(value);
560
+ }
561
+
562
+ function shortAddress(address: TEth.Address, params: {
563
+ shortAddress?: boolean
564
+ }): string {
565
+ if (params.shortAddress === false) {
566
+ return address;
567
+ }
568
+ return `${address.slice(0, 8)}...${address.slice(-4)}`;
569
+ }
570
+
571
+ function shortHex(hex: TEth.Hex, start: number, end: number): string {
572
+ if (hex.length <= start + end) {
573
+ return hex;
574
+ }
575
+ return `${hex.slice(0, start)}...${hex.slice(-end)}`;
576
+ }
577
+
578
+ function toAddress(word: string): TEth.Address {
579
+ let hex = word.replace(/^0x/, '').slice(-40).toLowerCase();
580
+ return `0x${hex}` as TEth.Address;
581
+ }
582
+
583
+ function toBigInt(word: string): bigint {
584
+ return $bigint.from($hex.ensure(word));
585
+ }
586
+
587
+ function toNumber(word: string): number {
588
+ let value = toBigInt(word);
589
+ return value > BigInt(Number.MAX_SAFE_INTEGER)
590
+ ? Number.MAX_SAFE_INTEGER
591
+ : Number(value);
592
+ }
593
+
594
+ function color (name: keyof (typeof ColorData['ColorAscii'])['value'], str: string, params: { color?: boolean }): string {
595
+ if (!params.color) {
596
+ return str;
597
+ }
598
+ return $color(`${name}<${str}>`);
599
+ }
600
+ }