@hypurrquant/defi-cli 0.3.5 → 0.4.1

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/dist/index.js CHANGED
@@ -15,693 +15,7 @@ import { encodeFunctionData as encodeFunctionData2, decodeFunctionResult, parseA
15
15
  import { readFileSync, readdirSync } from "fs";
16
16
  import { resolve } from "path";
17
17
  import { fileURLToPath } from "url";
18
-
19
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
20
- function getLineColFromPtr(string, ptr) {
21
- let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
22
- return [lines.length, lines.pop().length + 1];
23
- }
24
- function makeCodeBlock(string, line, column) {
25
- let lines = string.split(/\r\n|\n|\r/g);
26
- let codeblock = "";
27
- let numberLen = (Math.log10(line + 1) | 0) + 1;
28
- for (let i = line - 1; i <= line + 1; i++) {
29
- let l = lines[i - 1];
30
- if (!l)
31
- continue;
32
- codeblock += i.toString().padEnd(numberLen, " ");
33
- codeblock += ": ";
34
- codeblock += l;
35
- codeblock += "\n";
36
- if (i === line) {
37
- codeblock += " ".repeat(numberLen + column + 2);
38
- codeblock += "^\n";
39
- }
40
- }
41
- return codeblock;
42
- }
43
- var TomlError = class extends Error {
44
- line;
45
- column;
46
- codeblock;
47
- constructor(message, options) {
48
- const [line, column] = getLineColFromPtr(options.toml, options.ptr);
49
- const codeblock = makeCodeBlock(options.toml, line, column);
50
- super(`Invalid TOML document: ${message}
51
-
52
- ${codeblock}`, options);
53
- this.line = line;
54
- this.column = column;
55
- this.codeblock = codeblock;
56
- }
57
- };
58
-
59
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/util.js
60
- function isEscaped(str, ptr) {
61
- let i = 0;
62
- while (str[ptr - ++i] === "\\")
63
- ;
64
- return --i && i % 2;
65
- }
66
- function indexOfNewline(str, start = 0, end = str.length) {
67
- let idx = str.indexOf("\n", start);
68
- if (str[idx - 1] === "\r")
69
- idx--;
70
- return idx <= end ? idx : -1;
71
- }
72
- function skipComment(str, ptr) {
73
- for (let i = ptr; i < str.length; i++) {
74
- let c = str[i];
75
- if (c === "\n")
76
- return i;
77
- if (c === "\r" && str[i + 1] === "\n")
78
- return i + 1;
79
- if (c < " " && c !== " " || c === "\x7F") {
80
- throw new TomlError("control characters are not allowed in comments", {
81
- toml: str,
82
- ptr
83
- });
84
- }
85
- }
86
- return str.length;
87
- }
88
- function skipVoid(str, ptr, banNewLines, banComments) {
89
- let c;
90
- while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
91
- ptr++;
92
- return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
93
- }
94
- function skipUntil(str, ptr, sep, end, banNewLines = false) {
95
- if (!end) {
96
- ptr = indexOfNewline(str, ptr);
97
- return ptr < 0 ? str.length : ptr;
98
- }
99
- for (let i = ptr; i < str.length; i++) {
100
- let c = str[i];
101
- if (c === "#") {
102
- i = indexOfNewline(str, i);
103
- } else if (c === sep) {
104
- return i + 1;
105
- } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
106
- return i;
107
- }
108
- }
109
- throw new TomlError("cannot find end of structure", {
110
- toml: str,
111
- ptr
112
- });
113
- }
114
- function getStringEnd(str, seek) {
115
- let first = str[seek];
116
- let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
117
- seek += target.length - 1;
118
- do
119
- seek = str.indexOf(target, ++seek);
120
- while (seek > -1 && first !== "'" && isEscaped(str, seek));
121
- if (seek > -1) {
122
- seek += target.length;
123
- if (target.length > 1) {
124
- if (str[seek] === first)
125
- seek++;
126
- if (str[seek] === first)
127
- seek++;
128
- }
129
- }
130
- return seek;
131
- }
132
-
133
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/date.js
134
- var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
135
- var TomlDate = class _TomlDate extends Date {
136
- #hasDate = false;
137
- #hasTime = false;
138
- #offset = null;
139
- constructor(date) {
140
- let hasDate = true;
141
- let hasTime = true;
142
- let offset = "Z";
143
- if (typeof date === "string") {
144
- let match = date.match(DATE_TIME_RE);
145
- if (match) {
146
- if (!match[1]) {
147
- hasDate = false;
148
- date = `0000-01-01T${date}`;
149
- }
150
- hasTime = !!match[2];
151
- hasTime && date[10] === " " && (date = date.replace(" ", "T"));
152
- if (match[2] && +match[2] > 23) {
153
- date = "";
154
- } else {
155
- offset = match[3] || null;
156
- date = date.toUpperCase();
157
- if (!offset && hasTime)
158
- date += "Z";
159
- }
160
- } else {
161
- date = "";
162
- }
163
- }
164
- super(date);
165
- if (!isNaN(this.getTime())) {
166
- this.#hasDate = hasDate;
167
- this.#hasTime = hasTime;
168
- this.#offset = offset;
169
- }
170
- }
171
- isDateTime() {
172
- return this.#hasDate && this.#hasTime;
173
- }
174
- isLocal() {
175
- return !this.#hasDate || !this.#hasTime || !this.#offset;
176
- }
177
- isDate() {
178
- return this.#hasDate && !this.#hasTime;
179
- }
180
- isTime() {
181
- return this.#hasTime && !this.#hasDate;
182
- }
183
- isValid() {
184
- return this.#hasDate || this.#hasTime;
185
- }
186
- toISOString() {
187
- let iso = super.toISOString();
188
- if (this.isDate())
189
- return iso.slice(0, 10);
190
- if (this.isTime())
191
- return iso.slice(11, 23);
192
- if (this.#offset === null)
193
- return iso.slice(0, -1);
194
- if (this.#offset === "Z")
195
- return iso;
196
- let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
197
- offset = this.#offset[0] === "-" ? offset : -offset;
198
- let offsetDate = new Date(this.getTime() - offset * 6e4);
199
- return offsetDate.toISOString().slice(0, -1) + this.#offset;
200
- }
201
- static wrapAsOffsetDateTime(jsDate, offset = "Z") {
202
- let date = new _TomlDate(jsDate);
203
- date.#offset = offset;
204
- return date;
205
- }
206
- static wrapAsLocalDateTime(jsDate) {
207
- let date = new _TomlDate(jsDate);
208
- date.#offset = null;
209
- return date;
210
- }
211
- static wrapAsLocalDate(jsDate) {
212
- let date = new _TomlDate(jsDate);
213
- date.#hasTime = false;
214
- date.#offset = null;
215
- return date;
216
- }
217
- static wrapAsLocalTime(jsDate) {
218
- let date = new _TomlDate(jsDate);
219
- date.#hasDate = false;
220
- date.#offset = null;
221
- return date;
222
- }
223
- };
224
-
225
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/primitive.js
226
- var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
227
- var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
228
- var LEADING_ZERO = /^[+-]?0[0-9_]/;
229
- var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
230
- var ESC_MAP = {
231
- b: "\b",
232
- t: " ",
233
- n: "\n",
234
- f: "\f",
235
- r: "\r",
236
- e: "\x1B",
237
- '"': '"',
238
- "\\": "\\"
239
- };
240
- function parseString(str, ptr = 0, endPtr = str.length) {
241
- let isLiteral = str[ptr] === "'";
242
- let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
243
- if (isMultiline) {
244
- endPtr -= 2;
245
- if (str[ptr += 2] === "\r")
246
- ptr++;
247
- if (str[ptr] === "\n")
248
- ptr++;
249
- }
250
- let tmp = 0;
251
- let isEscape;
252
- let parsed = "";
253
- let sliceStart = ptr;
254
- while (ptr < endPtr - 1) {
255
- let c = str[ptr++];
256
- if (c === "\n" || c === "\r" && str[ptr] === "\n") {
257
- if (!isMultiline) {
258
- throw new TomlError("newlines are not allowed in strings", {
259
- toml: str,
260
- ptr: ptr - 1
261
- });
262
- }
263
- } else if (c < " " && c !== " " || c === "\x7F") {
264
- throw new TomlError("control characters are not allowed in strings", {
265
- toml: str,
266
- ptr: ptr - 1
267
- });
268
- }
269
- if (isEscape) {
270
- isEscape = false;
271
- if (c === "x" || c === "u" || c === "U") {
272
- let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
273
- if (!ESCAPE_REGEX.test(code)) {
274
- throw new TomlError("invalid unicode escape", {
275
- toml: str,
276
- ptr: tmp
277
- });
278
- }
279
- try {
280
- parsed += String.fromCodePoint(parseInt(code, 16));
281
- } catch {
282
- throw new TomlError("invalid unicode escape", {
283
- toml: str,
284
- ptr: tmp
285
- });
286
- }
287
- } else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
288
- ptr = skipVoid(str, ptr - 1, true);
289
- if (str[ptr] !== "\n" && str[ptr] !== "\r") {
290
- throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
291
- toml: str,
292
- ptr: tmp
293
- });
294
- }
295
- ptr = skipVoid(str, ptr);
296
- } else if (c in ESC_MAP) {
297
- parsed += ESC_MAP[c];
298
- } else {
299
- throw new TomlError("unrecognized escape sequence", {
300
- toml: str,
301
- ptr: tmp
302
- });
303
- }
304
- sliceStart = ptr;
305
- } else if (!isLiteral && c === "\\") {
306
- tmp = ptr - 1;
307
- isEscape = true;
308
- parsed += str.slice(sliceStart, tmp);
309
- }
310
- }
311
- return parsed + str.slice(sliceStart, endPtr - 1);
312
- }
313
- function parseValue(value, toml, ptr, integersAsBigInt) {
314
- if (value === "true")
315
- return true;
316
- if (value === "false")
317
- return false;
318
- if (value === "-inf")
319
- return -Infinity;
320
- if (value === "inf" || value === "+inf")
321
- return Infinity;
322
- if (value === "nan" || value === "+nan" || value === "-nan")
323
- return NaN;
324
- if (value === "-0")
325
- return integersAsBigInt ? 0n : 0;
326
- let isInt = INT_REGEX.test(value);
327
- if (isInt || FLOAT_REGEX.test(value)) {
328
- if (LEADING_ZERO.test(value)) {
329
- throw new TomlError("leading zeroes are not allowed", {
330
- toml,
331
- ptr
332
- });
333
- }
334
- value = value.replace(/_/g, "");
335
- let numeric = +value;
336
- if (isNaN(numeric)) {
337
- throw new TomlError("invalid number", {
338
- toml,
339
- ptr
340
- });
341
- }
342
- if (isInt) {
343
- if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
344
- throw new TomlError("integer value cannot be represented losslessly", {
345
- toml,
346
- ptr
347
- });
348
- }
349
- if (isInt || integersAsBigInt === true)
350
- numeric = BigInt(value);
351
- }
352
- return numeric;
353
- }
354
- const date = new TomlDate(value);
355
- if (!date.isValid()) {
356
- throw new TomlError("invalid value", {
357
- toml,
358
- ptr
359
- });
360
- }
361
- return date;
362
- }
363
-
364
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/extract.js
365
- function sliceAndTrimEndOf(str, startPtr, endPtr) {
366
- let value = str.slice(startPtr, endPtr);
367
- let commentIdx = value.indexOf("#");
368
- if (commentIdx > -1) {
369
- skipComment(str, commentIdx);
370
- value = value.slice(0, commentIdx);
371
- }
372
- return [value.trimEnd(), commentIdx];
373
- }
374
- function extractValue(str, ptr, end, depth, integersAsBigInt) {
375
- if (depth === 0) {
376
- throw new TomlError("document contains excessively nested structures. aborting.", {
377
- toml: str,
378
- ptr
379
- });
380
- }
381
- let c = str[ptr];
382
- if (c === "[" || c === "{") {
383
- let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
384
- if (end) {
385
- endPtr2 = skipVoid(str, endPtr2);
386
- if (str[endPtr2] === ",")
387
- endPtr2++;
388
- else if (str[endPtr2] !== end) {
389
- throw new TomlError("expected comma or end of structure", {
390
- toml: str,
391
- ptr: endPtr2
392
- });
393
- }
394
- }
395
- return [value, endPtr2];
396
- }
397
- let endPtr;
398
- if (c === '"' || c === "'") {
399
- endPtr = getStringEnd(str, ptr);
400
- let parsed = parseString(str, ptr, endPtr);
401
- if (end) {
402
- endPtr = skipVoid(str, endPtr);
403
- if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
404
- throw new TomlError("unexpected character encountered", {
405
- toml: str,
406
- ptr: endPtr
407
- });
408
- }
409
- endPtr += +(str[endPtr] === ",");
410
- }
411
- return [parsed, endPtr];
412
- }
413
- endPtr = skipUntil(str, ptr, ",", end);
414
- let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
415
- if (!slice[0]) {
416
- throw new TomlError("incomplete key-value declaration: no value specified", {
417
- toml: str,
418
- ptr
419
- });
420
- }
421
- if (end && slice[1] > -1) {
422
- endPtr = skipVoid(str, ptr + slice[1]);
423
- endPtr += +(str[endPtr] === ",");
424
- }
425
- return [
426
- parseValue(slice[0], str, ptr, integersAsBigInt),
427
- endPtr
428
- ];
429
- }
430
-
431
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/struct.js
432
- var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
433
- function parseKey(str, ptr, end = "=") {
434
- let dot = ptr - 1;
435
- let parsed = [];
436
- let endPtr = str.indexOf(end, ptr);
437
- if (endPtr < 0) {
438
- throw new TomlError("incomplete key-value: cannot find end of key", {
439
- toml: str,
440
- ptr
441
- });
442
- }
443
- do {
444
- let c = str[ptr = ++dot];
445
- if (c !== " " && c !== " ") {
446
- if (c === '"' || c === "'") {
447
- if (c === str[ptr + 1] && c === str[ptr + 2]) {
448
- throw new TomlError("multiline strings are not allowed in keys", {
449
- toml: str,
450
- ptr
451
- });
452
- }
453
- let eos = getStringEnd(str, ptr);
454
- if (eos < 0) {
455
- throw new TomlError("unfinished string encountered", {
456
- toml: str,
457
- ptr
458
- });
459
- }
460
- dot = str.indexOf(".", eos);
461
- let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
462
- let newLine = indexOfNewline(strEnd);
463
- if (newLine > -1) {
464
- throw new TomlError("newlines are not allowed in keys", {
465
- toml: str,
466
- ptr: ptr + dot + newLine
467
- });
468
- }
469
- if (strEnd.trimStart()) {
470
- throw new TomlError("found extra tokens after the string part", {
471
- toml: str,
472
- ptr: eos
473
- });
474
- }
475
- if (endPtr < eos) {
476
- endPtr = str.indexOf(end, eos);
477
- if (endPtr < 0) {
478
- throw new TomlError("incomplete key-value: cannot find end of key", {
479
- toml: str,
480
- ptr
481
- });
482
- }
483
- }
484
- parsed.push(parseString(str, ptr, eos));
485
- } else {
486
- dot = str.indexOf(".", ptr);
487
- let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
488
- if (!KEY_PART_RE.test(part)) {
489
- throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
490
- toml: str,
491
- ptr
492
- });
493
- }
494
- parsed.push(part.trimEnd());
495
- }
496
- }
497
- } while (dot + 1 && dot < endPtr);
498
- return [parsed, skipVoid(str, endPtr + 1, true, true)];
499
- }
500
- function parseInlineTable(str, ptr, depth, integersAsBigInt) {
501
- let res = {};
502
- let seen = /* @__PURE__ */ new Set();
503
- let c;
504
- ptr++;
505
- while ((c = str[ptr++]) !== "}" && c) {
506
- if (c === ",") {
507
- throw new TomlError("expected value, found comma", {
508
- toml: str,
509
- ptr: ptr - 1
510
- });
511
- } else if (c === "#")
512
- ptr = skipComment(str, ptr);
513
- else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
514
- let k;
515
- let t = res;
516
- let hasOwn = false;
517
- let [key, keyEndPtr] = parseKey(str, ptr - 1);
518
- for (let i = 0; i < key.length; i++) {
519
- if (i)
520
- t = hasOwn ? t[k] : t[k] = {};
521
- k = key[i];
522
- if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
523
- throw new TomlError("trying to redefine an already defined value", {
524
- toml: str,
525
- ptr
526
- });
527
- }
528
- if (!hasOwn && k === "__proto__") {
529
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
530
- }
531
- }
532
- if (hasOwn) {
533
- throw new TomlError("trying to redefine an already defined value", {
534
- toml: str,
535
- ptr
536
- });
537
- }
538
- let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
539
- seen.add(value);
540
- t[k] = value;
541
- ptr = valueEndPtr;
542
- }
543
- }
544
- if (!c) {
545
- throw new TomlError("unfinished table encountered", {
546
- toml: str,
547
- ptr
548
- });
549
- }
550
- return [res, ptr];
551
- }
552
- function parseArray(str, ptr, depth, integersAsBigInt) {
553
- let res = [];
554
- let c;
555
- ptr++;
556
- while ((c = str[ptr++]) !== "]" && c) {
557
- if (c === ",") {
558
- throw new TomlError("expected value, found comma", {
559
- toml: str,
560
- ptr: ptr - 1
561
- });
562
- } else if (c === "#")
563
- ptr = skipComment(str, ptr);
564
- else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
565
- let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
566
- res.push(e[0]);
567
- ptr = e[1];
568
- }
569
- }
570
- if (!c) {
571
- throw new TomlError("unfinished array encountered", {
572
- toml: str,
573
- ptr
574
- });
575
- }
576
- return [res, ptr];
577
- }
578
-
579
- // ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/parse.js
580
- function peekTable(key, table, meta, type) {
581
- let t = table;
582
- let m = meta;
583
- let k;
584
- let hasOwn = false;
585
- let state;
586
- for (let i = 0; i < key.length; i++) {
587
- if (i) {
588
- t = hasOwn ? t[k] : t[k] = {};
589
- m = (state = m[k]).c;
590
- if (type === 0 && (state.t === 1 || state.t === 2)) {
591
- return null;
592
- }
593
- if (state.t === 2) {
594
- let l = t.length - 1;
595
- t = t[l];
596
- m = m[l].c;
597
- }
598
- }
599
- k = key[i];
600
- if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
601
- return null;
602
- }
603
- if (!hasOwn) {
604
- if (k === "__proto__") {
605
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
606
- Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
607
- }
608
- m[k] = {
609
- t: i < key.length - 1 && type === 2 ? 3 : type,
610
- d: false,
611
- i: 0,
612
- c: {}
613
- };
614
- }
615
- }
616
- state = m[k];
617
- if (state.t !== type && !(type === 1 && state.t === 3)) {
618
- return null;
619
- }
620
- if (type === 2) {
621
- if (!state.d) {
622
- state.d = true;
623
- t[k] = [];
624
- }
625
- t[k].push(t = {});
626
- state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
627
- }
628
- if (state.d) {
629
- return null;
630
- }
631
- state.d = true;
632
- if (type === 1) {
633
- t = hasOwn ? t[k] : t[k] = {};
634
- } else if (type === 0 && hasOwn) {
635
- return null;
636
- }
637
- return [k, t, state.c];
638
- }
639
- function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
640
- let res = {};
641
- let meta = {};
642
- let tbl = res;
643
- let m = meta;
644
- for (let ptr = skipVoid(toml, 0); ptr < toml.length; ) {
645
- if (toml[ptr] === "[") {
646
- let isTableArray = toml[++ptr] === "[";
647
- let k = parseKey(toml, ptr += +isTableArray, "]");
648
- if (isTableArray) {
649
- if (toml[k[1] - 1] !== "]") {
650
- throw new TomlError("expected end of table declaration", {
651
- toml,
652
- ptr: k[1] - 1
653
- });
654
- }
655
- k[1]++;
656
- }
657
- let p = peekTable(
658
- k[0],
659
- res,
660
- meta,
661
- isTableArray ? 2 : 1
662
- /* Type.EXPLICIT */
663
- );
664
- if (!p) {
665
- throw new TomlError("trying to redefine an already defined table or value", {
666
- toml,
667
- ptr
668
- });
669
- }
670
- m = p[2];
671
- tbl = p[1];
672
- ptr = k[1];
673
- } else {
674
- let k = parseKey(toml, ptr);
675
- let p = peekTable(
676
- k[0],
677
- tbl,
678
- m,
679
- 0
680
- /* Type.DOTTED */
681
- );
682
- if (!p) {
683
- throw new TomlError("trying to redefine an already defined table or value", {
684
- toml,
685
- ptr
686
- });
687
- }
688
- let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
689
- p[1][p[0]] = v[0];
690
- ptr = v[1];
691
- }
692
- ptr = skipVoid(toml, ptr, true);
693
- if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") {
694
- throw new TomlError("each key-value declaration must be followed by an end-of-line", {
695
- toml,
696
- ptr
697
- });
698
- }
699
- ptr = skipVoid(toml, ptr);
700
- }
701
- return res;
702
- }
703
-
704
- // ../defi-core/dist/index.js
18
+ import { parse } from "smol-toml";
705
19
  import { existsSync } from "fs";
706
20
  var TxStatus = /* @__PURE__ */ ((TxStatus2) => {
707
21
  TxStatus2["DryRun"] = "dry_run";
@@ -1694,8 +1008,25 @@ function isPlaceholder(addr) {
1694
1008
  function registerStatus(parent, getOpts) {
1695
1009
  parent.command("status").description("Show chain and protocol status").option("--verify", "Verify contract addresses on-chain").action(async (opts) => {
1696
1010
  const globalOpts = parent.opts();
1697
- const chainName = globalOpts.chain ?? "hyperevm";
1698
1011
  const registry = Registry.loadEmbedded();
1012
+ const chainKeys = globalOpts.chain ? [globalOpts.chain] : Array.from(registry.chains.keys());
1013
+ if (chainKeys.length > 1) {
1014
+ const summary = [];
1015
+ for (const ck of chainKeys) {
1016
+ const cc = registry.getChain(ck);
1017
+ const protos = registry.getProtocolsForChain(ck);
1018
+ summary.push({
1019
+ chain: cc.name,
1020
+ chain_id: cc.chain_id,
1021
+ rpc_url: cc.effectiveRpcUrl(),
1022
+ protocols: protos.map((p) => ({ slug: p.slug, name: p.name, category: p.category, interface: p.interface })),
1023
+ summary: { total_protocols: protos.length }
1024
+ });
1025
+ }
1026
+ printOutput(summary, getOpts());
1027
+ return;
1028
+ }
1029
+ const chainName = chainKeys[0];
1699
1030
  const chainConfig = registry.getChain(chainName);
1700
1031
  const chainProtocols = registry.getProtocolsForChain(chainName);
1701
1032
  let blockNumber;
@@ -1799,27 +1130,34 @@ function registerStatus(parent, getOpts) {
1799
1130
  function handleSchema(params) {
1800
1131
  const action = typeof params["action"] === "string" ? params["action"] : "all";
1801
1132
  switch (action) {
1802
- case "dex.swap":
1133
+ case "status":
1134
+ return { action: "status", params: {}, cli: "defi status" };
1135
+ case "list_protocols":
1803
1136
  return {
1804
- action: "dex.swap",
1137
+ action: "list_protocols",
1805
1138
  params: {
1806
- protocol: { type: "string", required: true, description: "Protocol slug (e.g. hyperswap-v3)" },
1807
- token_in: { type: "string", required: true, description: "Input token symbol or address" },
1808
- token_out: { type: "string", required: true, description: "Output token symbol or address" },
1809
- amount: { type: "string", required: true, description: "Amount (human-readable, e.g. '1.5')" },
1810
- slippage_bps: { type: "number", required: false, default: 50, description: "Slippage in basis points" },
1811
- recipient: { type: "string", required: false, description: "Recipient address" }
1812
- }
1139
+ category: { type: "string", required: false, description: "Filter by category (e.g. dex, lending)" }
1140
+ },
1141
+ cli: "defi status"
1142
+ };
1143
+ case "yield":
1144
+ return {
1145
+ action: "yield",
1146
+ params: {
1147
+ chain: { type: "string", required: false, description: "Target chain (omit for all chains)" },
1148
+ asset: { type: "string", required: false, default: "USDC", description: "Token symbol" }
1149
+ },
1150
+ cli: "defi yield --asset USDC"
1813
1151
  };
1814
- case "dex.quote":
1152
+ case "lending.rates":
1815
1153
  return {
1816
- action: "dex.quote",
1154
+ action: "lending.rates",
1817
1155
  params: {
1156
+ chain: { type: "string", required: true, description: "Target chain" },
1818
1157
  protocol: { type: "string", required: true, description: "Protocol slug" },
1819
- token_in: { type: "string", required: true, description: "Input token symbol or address" },
1820
- token_out: { type: "string", required: true, description: "Output token symbol or address" },
1821
- amount: { type: "string", required: true, description: "Amount (human-readable)" }
1822
- }
1158
+ asset: { type: "string", required: true, description: "Token symbol or address" }
1159
+ },
1160
+ cli: "defi --chain hyperevm lending rates --protocol hypurrfi --asset USDC"
1823
1161
  };
1824
1162
  case "lending.supply":
1825
1163
  case "lending.borrow":
@@ -1828,47 +1166,54 @@ function handleSchema(params) {
1828
1166
  return {
1829
1167
  action,
1830
1168
  params: {
1169
+ chain: { type: "string", required: true, description: "Target chain" },
1831
1170
  protocol: { type: "string", required: true, description: "Protocol slug" },
1832
1171
  asset: { type: "string", required: true, description: "Token symbol or address" },
1833
- amount: { type: "string", required: true, description: "Amount (human-readable)" }
1834
- }
1172
+ amount: { type: "string", required: true, description: "Amount in wei" }
1173
+ },
1174
+ cli: `defi --chain hyperevm lending ${action.split(".")[1]} --protocol hypurrfi --asset USDC --amount 1000000`
1835
1175
  };
1836
- case "staking.stake":
1837
- case "staking.unstake":
1176
+ case "lp.discover":
1838
1177
  return {
1839
- action,
1178
+ action: "lp.discover",
1840
1179
  params: {
1841
- protocol: { type: "string", required: true, description: "Protocol slug" },
1842
- amount: { type: "string", required: true, description: "Amount (human-readable)" }
1843
- }
1180
+ chain: { type: "string", required: true, description: "Target chain" },
1181
+ protocol: { type: "string", required: false, description: "Filter by protocol slug" }
1182
+ },
1183
+ cli: "defi --chain hyperevm lp discover"
1844
1184
  };
1845
- case "vault.deposit":
1846
- case "vault.withdraw":
1185
+ case "swap":
1847
1186
  return {
1848
- action,
1187
+ action: "swap",
1849
1188
  params: {
1850
- protocol: { type: "string", required: true, description: "Protocol slug" },
1851
- amount: { type: "string", required: true, description: "Amount (human-readable)" }
1852
- }
1189
+ chain: { type: "string", required: true, description: "Target chain" },
1190
+ from: { type: "string", required: true, description: "Input token symbol or address" },
1191
+ to: { type: "string", required: true, description: "Output token symbol or address" },
1192
+ amount: { type: "string", required: true, description: "Amount in wei" },
1193
+ provider: { type: "string", required: false, default: "kyber", description: "Aggregator: kyber, openocean, liquid" },
1194
+ slippage: { type: "string", required: false, default: "50", description: "Slippage in bps" }
1195
+ },
1196
+ cli: "defi --chain hyperevm swap --from USDC --to WHYPE --amount 1000000"
1853
1197
  };
1854
- case "cdp.open":
1198
+ case "price":
1855
1199
  return {
1856
- action: "cdp.open",
1200
+ action: "price",
1857
1201
  params: {
1858
- protocol: { type: "string", required: true, description: "Protocol slug" },
1859
- collateral: { type: "string", required: true, description: "Collateral token symbol or address" },
1860
- collateral_amount: { type: "string", required: true, description: "Collateral amount (human-readable)" },
1861
- debt_amount: { type: "string", required: true, description: "Debt amount (human-readable)" }
1862
- }
1202
+ chain: { type: "string", required: true, description: "Target chain" },
1203
+ asset: { type: "string", required: true, description: "Token symbol or address" }
1204
+ },
1205
+ cli: "defi --chain hyperevm price --asset WHYPE"
1863
1206
  };
1864
- case "status":
1865
- return { action: "status", params: {} };
1866
- case "list_protocols":
1207
+ case "bridge":
1867
1208
  return {
1868
- action: "list_protocols",
1209
+ action: "bridge",
1869
1210
  params: {
1870
- category: { type: "string", required: false, description: "Filter by category (e.g. dex, lending, vault)" }
1871
- }
1211
+ chain: { type: "string", required: true, description: "Source chain" },
1212
+ token: { type: "string", required: true, description: "Token symbol or address" },
1213
+ amount: { type: "string", required: true, description: "Amount in wei" },
1214
+ to_chain: { type: "string", required: true, description: "Destination chain" }
1215
+ },
1216
+ cli: "defi --chain hyperevm bridge --token USDC --amount 1000000 --to-chain mantle"
1872
1217
  };
1873
1218
  default:
1874
1219
  return {
@@ -1876,17 +1221,25 @@ function handleSchema(params) {
1876
1221
  "status",
1877
1222
  "list_protocols",
1878
1223
  "schema",
1879
- "dex.swap",
1880
- "dex.quote",
1224
+ "yield",
1225
+ "lending.rates",
1881
1226
  "lending.supply",
1882
1227
  "lending.borrow",
1883
1228
  "lending.repay",
1884
1229
  "lending.withdraw",
1885
- "staking.stake",
1886
- "staking.unstake",
1887
- "vault.deposit",
1888
- "vault.withdraw",
1889
- "cdp.open"
1230
+ "lp.discover",
1231
+ "lp.add",
1232
+ "lp.farm",
1233
+ "lp.claim",
1234
+ "lp.remove",
1235
+ "swap",
1236
+ "price",
1237
+ "token.balance",
1238
+ "token.approve",
1239
+ "token.transfer",
1240
+ "wallet.balance",
1241
+ "portfolio.show",
1242
+ "bridge"
1890
1243
  ]
1891
1244
  };
1892
1245
  }
@@ -4141,7 +3494,8 @@ var lbQuoterAbi2 = parseAbi12([
4141
3494
  "function findBestPathFromAmountIn(address[] calldata route, uint128 amountIn) external view returns ((address[] route, address[] pairs, uint256[] binSteps, uint256[] versions, uint128[] amounts, uint128[] virtualAmountsWithoutSlippage, uint128[] fees))"
4142
3495
  ]);
4143
3496
  var erc20Abi2 = parseAbi12([
4144
- "function symbol() external view returns (string)"
3497
+ "function symbol() external view returns (string)",
3498
+ "function balanceOf(address account) external view returns (uint256)"
4145
3499
  ]);
4146
3500
  var _addressAbi = parseAbi12(["function f() external view returns (address)"]);
4147
3501
  function decodeAddressResult(data) {
@@ -4649,6 +4003,74 @@ var MerchantMoeLBAdapter = class {
4649
4003
  } catch {
4650
4004
  }
4651
4005
  }
4006
+ const stableSymbols = /* @__PURE__ */ new Set(["USDT", "USDC", "USDT0", "MUSD", "AUSD", "USDY", "FDUSD", "USDe", "sUSDe"]);
4007
+ const mntSymbols = /* @__PURE__ */ new Set(["WMNT", "MNT"]);
4008
+ const moeSymbols = /* @__PURE__ */ new Set(["MOE"]);
4009
+ const sixDecimalStables = /* @__PURE__ */ new Set(["USDT", "USDC", "USDT0", "FDUSD"]);
4010
+ const tokenPriceMap = /* @__PURE__ */ new Map();
4011
+ const tokenDecimalsMap = /* @__PURE__ */ new Map();
4012
+ for (const [addr, sym] of symbolMap) {
4013
+ const key = addr.toLowerCase();
4014
+ if (stableSymbols.has(sym)) {
4015
+ tokenPriceMap.set(key, 1);
4016
+ tokenDecimalsMap.set(key, sixDecimalStables.has(sym) ? 6 : 18);
4017
+ } else if (mntSymbols.has(sym)) {
4018
+ tokenPriceMap.set(key, wmntPriceUsd);
4019
+ tokenDecimalsMap.set(key, 18);
4020
+ } else if (moeSymbols.has(sym)) {
4021
+ tokenPriceMap.set(key, moePriceUsd);
4022
+ tokenDecimalsMap.set(key, 18);
4023
+ }
4024
+ }
4025
+ const unknownTokenAddrs = [];
4026
+ for (let i = 0; i < rewardedPairs.length; i++) {
4027
+ for (const addr of [tokenXAddresses[i], tokenYAddresses[i]]) {
4028
+ if (addr && !tokenPriceMap.has(addr.toLowerCase())) {
4029
+ if (!unknownTokenAddrs.some((a) => a.toLowerCase() === addr.toLowerCase())) {
4030
+ unknownTokenAddrs.push(addr);
4031
+ }
4032
+ }
4033
+ }
4034
+ }
4035
+ if (unknownTokenAddrs.length > 0 && this.lbQuoter && this.wmnt && wmntPriceUsd > 0) {
4036
+ const erc20DecimalsAbi = parseAbi12(["function decimals() external view returns (uint8)"]);
4037
+ const decCalls = unknownTokenAddrs.map((addr) => [
4038
+ addr,
4039
+ encodeFunctionData12({ abi: erc20DecimalsAbi, functionName: "decimals" })
4040
+ ]);
4041
+ const decResults = await multicallRead(rpcUrl, decCalls).catch(() => []);
4042
+ for (let i = 0; i < unknownTokenAddrs.length; i++) {
4043
+ const dec = decResults[i] ? Number(decodeUint256Result(decResults[i]) ?? 18n) : 18;
4044
+ tokenDecimalsMap.set(unknownTokenAddrs[i].toLowerCase(), dec);
4045
+ }
4046
+ const quotePromises = unknownTokenAddrs.map(async (tokenAddr) => {
4047
+ try {
4048
+ const dec = tokenDecimalsMap.get(tokenAddr.toLowerCase()) ?? 18;
4049
+ const quoteUnit = 10n ** BigInt(Math.max(dec - 2, 0));
4050
+ const quote = await client.readContract({
4051
+ address: this.lbQuoter,
4052
+ abi: lbQuoterAbi2,
4053
+ functionName: "findBestPathFromAmountIn",
4054
+ args: [[tokenAddr, this.wmnt], quoteUnit]
4055
+ });
4056
+ const amountOut = quote.amounts?.at(-1) ?? 0n;
4057
+ const priceInWmnt = Number(amountOut) / 1e18 * (10 ** dec / Number(quoteUnit));
4058
+ return { addr: tokenAddr, price: priceInWmnt * wmntPriceUsd };
4059
+ } catch {
4060
+ return { addr: tokenAddr, price: 0 };
4061
+ }
4062
+ });
4063
+ const priceResults = await Promise.all(quotePromises);
4064
+ for (const { addr, price } of priceResults) {
4065
+ if (price > 0) tokenPriceMap.set(addr.toLowerCase(), price);
4066
+ }
4067
+ }
4068
+ const getTokenPriceUsd = (_sym, addr) => {
4069
+ return tokenPriceMap.get(addr.toLowerCase()) ?? 0;
4070
+ };
4071
+ const getTokenDecimals = (_sym, addr) => {
4072
+ return tokenDecimalsMap.get(addr.toLowerCase()) ?? 18;
4073
+ };
4652
4074
  const binRequests = [];
4653
4075
  for (let i = 0; i < rewardedPairs.length; i++) {
4654
4076
  const range = poolData[i].range;
@@ -4679,19 +4101,23 @@ var MerchantMoeLBAdapter = class {
4679
4101
  binReservesY.get(poolIdx).set(binId, decoded[1]);
4680
4102
  }
4681
4103
  }
4682
- const stableSymbols = /* @__PURE__ */ new Set(["USDT", "USDC", "MUSD", "AUSD", "USDY", "FDUSD"]);
4683
- const mntSymbols = /* @__PURE__ */ new Set(["WMNT", "MNT"]);
4684
- const moeSymbols = /* @__PURE__ */ new Set(["MOE"]);
4685
- const sixDecimalStables = /* @__PURE__ */ new Set(["USDT", "USDC", "FDUSD"]);
4686
- const getTokenPriceUsd = (sym) => {
4687
- if (stableSymbols.has(sym)) return 1;
4688
- if (mntSymbols.has(sym)) return wmntPriceUsd;
4689
- if (moeSymbols.has(sym)) return moePriceUsd;
4690
- return 0;
4691
- };
4692
- const getTokenDecimals = (sym) => {
4693
- return sixDecimalStables.has(sym) ? 6 : 18;
4694
- };
4104
+ const poolBalanceX = /* @__PURE__ */ new Map();
4105
+ const poolBalanceY = /* @__PURE__ */ new Map();
4106
+ {
4107
+ const balCalls = [];
4108
+ for (let i = 0; i < rewardedPairs.length; i++) {
4109
+ const tx = tokenXAddresses[i];
4110
+ const ty = tokenYAddresses[i];
4111
+ const pool = rewardedPairs[i].pool;
4112
+ balCalls.push([tx ?? "0x0000000000000000000000000000000000000000", encodeFunctionData12({ abi: erc20Abi2, functionName: "balanceOf", args: [pool] })]);
4113
+ balCalls.push([ty ?? "0x0000000000000000000000000000000000000000", encodeFunctionData12({ abi: erc20Abi2, functionName: "balanceOf", args: [pool] })]);
4114
+ }
4115
+ const balResults = await multicallRead(rpcUrl, balCalls).catch(() => []);
4116
+ for (let i = 0; i < rewardedPairs.length; i++) {
4117
+ poolBalanceX.set(i, decodeUint256Result(balResults[i * 2] ?? null) ?? 0n);
4118
+ poolBalanceY.set(i, decodeUint256Result(balResults[i * 2 + 1] ?? null) ?? 0n);
4119
+ }
4120
+ }
4695
4121
  const results = [];
4696
4122
  for (let i = 0; i < rewardedPairs.length; i++) {
4697
4123
  const { pool, rewarder } = rewardedPairs[i];
@@ -4716,18 +4142,25 @@ var MerchantMoeLBAdapter = class {
4716
4142
  const maxBin = Number(range[1]);
4717
4143
  rewardedBins = maxBin - minBin + 1;
4718
4144
  if (rxMap && ryMap) {
4719
- const priceX = getTokenPriceUsd(symX);
4720
- const priceY = getTokenPriceUsd(symY);
4721
- const decX = getTokenDecimals(symX);
4722
- const decY = getTokenDecimals(symY);
4145
+ const priceX2 = getTokenPriceUsd(symX, tokenX);
4146
+ const priceY2 = getTokenPriceUsd(symY, tokenY);
4147
+ const decX2 = getTokenDecimals(symX, tokenX);
4148
+ const decY2 = getTokenDecimals(symY, tokenY);
4723
4149
  for (let b = minBin; b <= maxBin; b++) {
4724
4150
  const rx = rxMap.get(b) ?? 0n;
4725
4151
  const ry = ryMap.get(b) ?? 0n;
4726
- rangeTvlUsd += Number(rx) / 10 ** decX * priceX;
4727
- rangeTvlUsd += Number(ry) / 10 ** decY * priceY;
4152
+ rangeTvlUsd += Number(rx) / 10 ** decX2 * priceX2;
4153
+ rangeTvlUsd += Number(ry) / 10 ** decY2 * priceY2;
4728
4154
  }
4729
4155
  }
4730
4156
  }
4157
+ const priceX = getTokenPriceUsd(symX, tokenX);
4158
+ const priceY = getTokenPriceUsd(symY, tokenY);
4159
+ const decX = getTokenDecimals(symX, tokenX);
4160
+ const decY = getTokenDecimals(symY, tokenY);
4161
+ const fullBalX = poolBalanceX.get(i) ?? 0n;
4162
+ const fullBalY = poolBalanceY.get(i) ?? 0n;
4163
+ const poolTvlUsd = Number(fullBalX) / 10 ** decX * priceX + Number(fullBalY) / 10 ** decY * priceY;
4731
4164
  const aprPercent = rangeTvlUsd > 0 && moePriceUsd > 0 ? poolMoePerDay * moePriceUsd * 365 / rangeTvlUsd * 100 : 0;
4732
4165
  results.push({
4733
4166
  pool,
@@ -4744,8 +4177,11 @@ var MerchantMoeLBAdapter = class {
4744
4177
  isTopPool,
4745
4178
  moePerDay: poolMoePerDay,
4746
4179
  rangeTvlUsd,
4180
+ poolTvlUsd,
4747
4181
  aprPercent,
4748
- rewardedBins
4182
+ rewardedBins,
4183
+ totalMoePerDay: moePerDay,
4184
+ moePriceUsd
4749
4185
  });
4750
4186
  }
4751
4187
  return results;
@@ -6388,207 +5824,58 @@ var HINT_HELPERS_ABI = parseAbi21([
6388
5824
  var SORTED_TROVES_ABI = parseAbi21([
6389
5825
  "function findInsertPosition(uint256 _annualInterestRate, uint256 _prevId, uint256 _nextId) external view returns (uint256 prevId, uint256 nextId)"
6390
5826
  ]);
6391
- var FelixCdpAdapter = class {
5827
+ var PRICE_FEED_ABI = parseAbi222([
5828
+ "function fetchPrice() external view returns (uint256 price, bool isNewOracleFailureDetected)",
5829
+ "function lastGoodPrice() external view returns (uint256)"
5830
+ ]);
5831
+ var FelixOracleAdapter = class {
6392
5832
  protocolName;
6393
- borrowerOperations;
6394
- troveManager;
6395
- hintHelpers;
6396
- sortedTroves;
5833
+ priceFeed;
5834
+ asset;
6397
5835
  rpcUrl;
6398
5836
  constructor(entry, rpcUrl) {
6399
5837
  this.protocolName = entry.name;
5838
+ if (!rpcUrl) throw DefiError.rpcError(`[${entry.name}] RPC URL required for oracle`);
6400
5839
  this.rpcUrl = rpcUrl;
6401
5840
  const contracts = entry.contracts ?? {};
6402
- const bo = contracts["borrower_operations"];
6403
- if (!bo) throw DefiError.contractError("Missing 'borrower_operations' contract");
6404
- this.borrowerOperations = bo;
6405
- this.troveManager = contracts["trove_manager"];
6406
- this.hintHelpers = contracts["hint_helpers"];
6407
- this.sortedTroves = contracts["sorted_troves"];
5841
+ const feed = contracts["price_feed"];
5842
+ if (!feed) throw DefiError.contractError(`[${entry.name}] Missing 'price_feed' contract address`);
5843
+ this.priceFeed = feed;
5844
+ this.asset = contracts["asset"] ?? "0x0000000000000000000000000000000000000000";
6408
5845
  }
6409
5846
  name() {
6410
5847
  return this.protocolName;
6411
5848
  }
6412
- async getHints(interestRate) {
6413
- if (!this.hintHelpers || !this.sortedTroves || !this.rpcUrl) {
6414
- return [0n, 0n];
5849
+ async getPrice(asset) {
5850
+ if (asset !== this.asset && this.asset !== "0x0000000000000000000000000000000000000000") {
5851
+ throw DefiError.unsupported(`[${this.protocolName}] Felix PriceFeed only supports asset ${this.asset}`);
6415
5852
  }
6416
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
6417
- const approxResult = await client.readContract({
6418
- address: this.hintHelpers,
6419
- abi: HINT_HELPERS_ABI,
6420
- functionName: "getApproxHint",
6421
- args: [0n, interestRate, 15n, 42n]
6422
- }).catch(() => null);
6423
- if (!approxResult) return [0n, 0n];
6424
- const [hintId] = approxResult;
6425
- const insertResult = await client.readContract({
6426
- address: this.sortedTroves,
6427
- abi: SORTED_TROVES_ABI,
6428
- functionName: "findInsertPosition",
6429
- args: [interestRate, hintId, hintId]
6430
- }).catch(() => null);
6431
- if (!insertResult) return [0n, 0n];
6432
- const [prevId, nextId] = insertResult;
6433
- return [prevId, nextId];
6434
- }
6435
- async buildOpen(params) {
6436
- const interestRate = 50000000000000000n;
6437
- const [upperHint, lowerHint] = await this.getHints(interestRate);
6438
- const hasHints = upperHint !== 0n || lowerHint !== 0n;
6439
- const data = encodeFunctionData20({
6440
- abi: BORROWER_OPS_ABI,
6441
- functionName: "openTrove",
6442
- args: [
6443
- params.recipient,
6444
- 0n,
6445
- params.collateral_amount,
6446
- params.debt_amount,
6447
- upperHint,
6448
- lowerHint,
6449
- interestRate,
6450
- BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
6451
- // U256::MAX
6452
- params.recipient,
6453
- params.recipient,
6454
- params.recipient
6455
- ]
6456
- });
5853
+ const client = createPublicClient17({ transport: http17(this.rpcUrl) });
5854
+ let priceVal;
5855
+ try {
5856
+ const result = await client.readContract({
5857
+ address: this.priceFeed,
5858
+ abi: PRICE_FEED_ABI,
5859
+ functionName: "fetchPrice"
5860
+ });
5861
+ const [price] = result;
5862
+ priceVal = price;
5863
+ } catch {
5864
+ priceVal = await client.readContract({
5865
+ address: this.priceFeed,
5866
+ abi: PRICE_FEED_ABI,
5867
+ functionName: "lastGoodPrice"
5868
+ }).catch((e) => {
5869
+ throw DefiError.rpcError(`[${this.protocolName}] lastGoodPrice failed: ${e}`);
5870
+ });
5871
+ }
5872
+ const priceF64 = Number(priceVal) / 1e18;
6457
5873
  return {
6458
- description: `[${this.protocolName}] Open trove: collateral=${params.collateral_amount}, debt=${params.debt_amount} (hints=${hasHints ? "optimized" : "none"})`,
6459
- to: this.borrowerOperations,
6460
- data,
6461
- value: 0n,
6462
- gas_estimate: hasHints ? 5e5 : 5e6
6463
- };
6464
- }
6465
- async buildAdjust(params) {
6466
- const collChange = params.collateral_delta ?? 0n;
6467
- const debtChange = params.debt_delta ?? 0n;
6468
- const data = encodeFunctionData20({
6469
- abi: BORROWER_OPS_ABI,
6470
- functionName: "adjustTrove",
6471
- args: [
6472
- params.cdp_id,
6473
- collChange,
6474
- params.add_collateral,
6475
- debtChange,
6476
- params.add_debt,
6477
- 0n,
6478
- 0n,
6479
- BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
6480
- ]
6481
- });
6482
- return {
6483
- description: `[${this.protocolName}] Adjust trove ${params.cdp_id}`,
6484
- to: this.borrowerOperations,
6485
- data,
6486
- value: 0n,
6487
- gas_estimate: 4e5
6488
- };
6489
- }
6490
- async buildClose(params) {
6491
- const data = encodeFunctionData20({
6492
- abi: BORROWER_OPS_ABI,
6493
- functionName: "closeTrove",
6494
- args: [params.cdp_id]
6495
- });
6496
- return {
6497
- description: `[${this.protocolName}] Close trove ${params.cdp_id}`,
6498
- to: this.borrowerOperations,
6499
- data,
6500
- value: 0n,
6501
- gas_estimate: 35e4
6502
- };
6503
- }
6504
- async getCdpInfo(cdpId) {
6505
- if (!this.rpcUrl) throw DefiError.rpcError(`[${this.protocolName}] getCdpInfo requires RPC \u2014 set HYPEREVM_RPC_URL`);
6506
- if (!this.troveManager) throw DefiError.contractError(`[${this.protocolName}] trove_manager contract not configured`);
6507
- const client = createPublicClient16({ transport: http16(this.rpcUrl) });
6508
- const data = await client.readContract({
6509
- address: this.troveManager,
6510
- abi: TROVE_MANAGER_ABI,
6511
- functionName: "getLatestTroveData",
6512
- args: [cdpId]
6513
- }).catch((e) => {
6514
- throw DefiError.invalidParam(`[${this.protocolName}] Trove ${cdpId} not found: ${e}`);
6515
- });
6516
- const [entireDebt, entireColl] = data;
6517
- if (entireDebt === 0n && entireColl === 0n) {
6518
- throw DefiError.invalidParam(`[${this.protocolName}] Trove ${cdpId} does not exist`);
6519
- }
6520
- const collRatio = entireDebt > 0n ? Number(entireColl) / Number(entireDebt) : 0;
6521
- return {
6522
- protocol: this.protocolName,
6523
- cdp_id: cdpId,
6524
- collateral: {
6525
- token: zeroAddress11,
6526
- symbol: "WHYPE",
6527
- amount: entireColl,
6528
- decimals: 18
6529
- },
6530
- debt: {
6531
- token: zeroAddress11,
6532
- symbol: "feUSD",
6533
- amount: entireDebt,
6534
- decimals: 18
6535
- },
6536
- collateral_ratio: collRatio
6537
- };
6538
- }
6539
- };
6540
- var PRICE_FEED_ABI = parseAbi222([
6541
- "function fetchPrice() external view returns (uint256 price, bool isNewOracleFailureDetected)",
6542
- "function lastGoodPrice() external view returns (uint256)"
6543
- ]);
6544
- var FelixOracleAdapter = class {
6545
- protocolName;
6546
- priceFeed;
6547
- asset;
6548
- rpcUrl;
6549
- constructor(entry, rpcUrl) {
6550
- this.protocolName = entry.name;
6551
- if (!rpcUrl) throw DefiError.rpcError(`[${entry.name}] RPC URL required for oracle`);
6552
- this.rpcUrl = rpcUrl;
6553
- const contracts = entry.contracts ?? {};
6554
- const feed = contracts["price_feed"];
6555
- if (!feed) throw DefiError.contractError(`[${entry.name}] Missing 'price_feed' contract address`);
6556
- this.priceFeed = feed;
6557
- this.asset = contracts["asset"] ?? "0x0000000000000000000000000000000000000000";
6558
- }
6559
- name() {
6560
- return this.protocolName;
6561
- }
6562
- async getPrice(asset) {
6563
- if (asset !== this.asset && this.asset !== "0x0000000000000000000000000000000000000000") {
6564
- throw DefiError.unsupported(`[${this.protocolName}] Felix PriceFeed only supports asset ${this.asset}`);
6565
- }
6566
- const client = createPublicClient17({ transport: http17(this.rpcUrl) });
6567
- let priceVal;
6568
- try {
6569
- const result = await client.readContract({
6570
- address: this.priceFeed,
6571
- abi: PRICE_FEED_ABI,
6572
- functionName: "fetchPrice"
6573
- });
6574
- const [price] = result;
6575
- priceVal = price;
6576
- } catch {
6577
- priceVal = await client.readContract({
6578
- address: this.priceFeed,
6579
- abi: PRICE_FEED_ABI,
6580
- functionName: "lastGoodPrice"
6581
- }).catch((e) => {
6582
- throw DefiError.rpcError(`[${this.protocolName}] lastGoodPrice failed: ${e}`);
6583
- });
6584
- }
6585
- const priceF64 = Number(priceVal) / 1e18;
6586
- return {
6587
- source: "Felix PriceFeed",
6588
- source_type: "oracle",
6589
- asset,
6590
- price_usd: priceVal,
6591
- price_f64: priceF64
5874
+ source: "Felix PriceFeed",
5875
+ source_type: "oracle",
5876
+ asset,
5877
+ price_usd: priceVal,
5878
+ price_f64: priceF64
6592
5879
  };
6593
5880
  }
6594
5881
  async getPrices(assets) {
@@ -6792,14 +6079,6 @@ function createLending(entry, rpcUrl) {
6792
6079
  throw DefiError.unsupported(`Lending interface '${entry.interface}' not yet implemented`);
6793
6080
  }
6794
6081
  }
6795
- function createCdp(entry, rpcUrl) {
6796
- switch (entry.interface) {
6797
- case "liquity_v2":
6798
- return new FelixCdpAdapter(entry, rpcUrl);
6799
- default:
6800
- throw DefiError.unsupported(`CDP interface '${entry.interface}' not yet implemented`);
6801
- }
6802
- }
6803
6082
  function createVault(entry, rpcUrl) {
6804
6083
  switch (entry.interface) {
6805
6084
  case "erc4626":
@@ -6895,6 +6174,21 @@ var DexSpotPrice = class {
6895
6174
  }
6896
6175
  };
6897
6176
 
6177
+ // src/whitelist.ts
6178
+ import { readFileSync as readFileSync2 } from "fs";
6179
+ import { resolve as resolve2 } from "path";
6180
+ import { parse as parse2 } from "smol-toml";
6181
+ function loadWhitelist() {
6182
+ const path = resolve2(process.env["HOME"] ?? "~", ".defi", "pools.toml");
6183
+ try {
6184
+ const raw = readFileSync2(path, "utf-8");
6185
+ const parsed = parse2(raw);
6186
+ return parsed.whitelist ?? [];
6187
+ } catch {
6188
+ return [];
6189
+ }
6190
+ }
6191
+
6898
6192
  // src/commands/lp.ts
6899
6193
  function resolveAccount(optOwner) {
6900
6194
  if (optOwner) return optOwner;
@@ -6911,7 +6205,11 @@ function resolvePoolAddress(registry, protocolSlug, pool) {
6911
6205
  function registerLP(parent, getOpts, makeExecutor2) {
6912
6206
  const lp = parent.command("lp").description("Unified LP operations: discover, add, farm, claim, remove, positions");
6913
6207
  lp.command("discover").description("Scan all protocols for fee + emission pools (gauges, farming, LB rewards)").option("--protocol <protocol>", "Filter to a single protocol slug").option("--emission-only", "Only show emission (gauge/farming) pools, skip fee-only").action(async (opts) => {
6914
- const chainName = parent.opts().chain ?? "hyperevm";
6208
+ const chainName = parent.opts().chain;
6209
+ if (!chainName) {
6210
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6211
+ return;
6212
+ }
6915
6213
  const registry = Registry.loadEmbedded();
6916
6214
  const chain = registry.getChain(chainName);
6917
6215
  const rpcUrl = chain.effectiveRpcUrl();
@@ -6966,7 +6264,13 @@ function registerLP(parent, getOpts, makeExecutor2) {
6966
6264
  moePerDay: p.moePerDay,
6967
6265
  aprPercent: p.aprPercent,
6968
6266
  rangeTvlUsd: p.rangeTvlUsd,
6969
- isTopPool: p.isTopPool
6267
+ poolTvlUsd: p.poolTvlUsd,
6268
+ isTopPool: p.isTopPool,
6269
+ rewardedBins: p.rewardedBins,
6270
+ minBinId: p.minBinId,
6271
+ maxBinId: p.maxBinId,
6272
+ totalMoePerDay: p.totalMoePerDay,
6273
+ moePriceUsd: p.moePriceUsd
6970
6274
  });
6971
6275
  }
6972
6276
  }
@@ -6983,7 +6287,11 @@ function registerLP(parent, getOpts, makeExecutor2) {
6983
6287
  });
6984
6288
  lp.command("add").description("Add liquidity to a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--recipient <address>", "Recipient address").option("--tick-lower <tick>", "Lower tick for concentrated LP (default: full range)").option("--tick-upper <tick>", "Upper tick for concentrated LP (default: full range)").option("--range <percent>", "\xB1N% concentrated range around current price (e.g. --range 2)").action(async (opts) => {
6985
6289
  const executor = makeExecutor2();
6986
- const chainName = parent.opts().chain ?? "hyperevm";
6290
+ const chainName = parent.opts().chain;
6291
+ if (!chainName) {
6292
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6293
+ return;
6294
+ }
6987
6295
  const registry = Registry.loadEmbedded();
6988
6296
  const chain = registry.getChain(chainName);
6989
6297
  const protocol = registry.getProtocol(opts.protocol);
@@ -7009,7 +6317,11 @@ function registerLP(parent, getOpts, makeExecutor2) {
7009
6317
  });
7010
6318
  lp.command("farm").description("Add liquidity and auto-stake into gauge/farming for emissions").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--amount-a <amount>", "Amount of token A in wei").requiredOption("--amount-b <amount>", "Amount of token B in wei").option("--pool <name_or_address>", "Pool name (e.g. WHYPE/USDC) or address").option("--gauge <address>", "Gauge address (required for solidly/hybra if not resolved automatically)").option("--recipient <address>", "Recipient / owner address").option("--tick-lower <tick>", "Lower tick for concentrated LP").option("--tick-upper <tick>", "Upper tick for concentrated LP").option("--range <percent>", "\xB1N% concentrated range around current price").action(async (opts) => {
7011
6319
  const executor = makeExecutor2();
7012
- const chainName = parent.opts().chain ?? "hyperevm";
6320
+ const chainName = parent.opts().chain;
6321
+ if (!chainName) {
6322
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6323
+ return;
6324
+ }
7013
6325
  const registry = Registry.loadEmbedded();
7014
6326
  const chain = registry.getChain(chainName);
7015
6327
  const protocol = registry.getProtocol(opts.protocol);
@@ -7087,7 +6399,11 @@ function registerLP(parent, getOpts, makeExecutor2) {
7087
6399
  });
7088
6400
  lp.command("claim").description("Claim rewards from a pool (fee or emission)").requiredOption("--protocol <protocol>", "Protocol slug").option("--pool <address>", "Pool address (required for farming/LB)").option("--gauge <address>", "Gauge contract address (required for solidly/hybra)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--bins <binIds>", "Comma-separated bin IDs (for Merchant Moe LB)").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
7089
6401
  const executor = makeExecutor2();
7090
- const chainName = parent.opts().chain ?? "hyperevm";
6402
+ const chainName = parent.opts().chain;
6403
+ if (!chainName) {
6404
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6405
+ return;
6406
+ }
7091
6407
  const registry = Registry.loadEmbedded();
7092
6408
  const chain = registry.getChain(chainName);
7093
6409
  const rpcUrl = chain.effectiveRpcUrl();
@@ -7134,7 +6450,11 @@ function registerLP(parent, getOpts, makeExecutor2) {
7134
6450
  });
7135
6451
  lp.command("remove").description("Auto-unstake (if staked) and remove liquidity from a pool").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--token-a <token>", "First token symbol or address").requiredOption("--token-b <token>", "Second token symbol or address").requiredOption("--liquidity <amount>", "Liquidity amount to remove in wei").option("--pool <address>", "Pool address (needed to resolve gauge)").option("--gauge <address>", "Gauge contract address (for solidly/hybra unstake)").option("--token-id <id>", "NFT tokenId (for CL gauge or farming positions)").option("--recipient <address>", "Recipient address").action(async (opts) => {
7136
6452
  const executor = makeExecutor2();
7137
- const chainName = parent.opts().chain ?? "hyperevm";
6453
+ const chainName = parent.opts().chain;
6454
+ if (!chainName) {
6455
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6456
+ return;
6457
+ }
7138
6458
  const registry = Registry.loadEmbedded();
7139
6459
  const chain = registry.getChain(chainName);
7140
6460
  const rpcUrl = chain.effectiveRpcUrl();
@@ -7197,7 +6517,11 @@ function registerLP(parent, getOpts, makeExecutor2) {
7197
6517
  printOutput({ step: "lp_remove", ...removeResult }, getOpts());
7198
6518
  });
7199
6519
  lp.command("positions").description("Show all LP positions across protocols").option("--protocol <protocol>", "Filter to a single protocol slug").option("--pool <address>", "Filter to a specific pool address").option("--bins <binIds>", "Comma-separated bin IDs (for Merchant Moe LB, auto-detected if omitted)").option("--address <address>", "Wallet address (defaults to DEFI_WALLET_ADDRESS)").action(async (opts) => {
7200
- const chainName = parent.opts().chain ?? "hyperevm";
6520
+ const chainName = parent.opts().chain;
6521
+ if (!chainName) {
6522
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6523
+ return;
6524
+ }
7201
6525
  const registry = Registry.loadEmbedded();
7202
6526
  const chain = registry.getChain(chainName);
7203
6527
  const rpcUrl = chain.effectiveRpcUrl();
@@ -7228,13 +6552,251 @@ function registerLP(parent, getOpts, makeExecutor2) {
7228
6552
  );
7229
6553
  printOutput(results, getOpts());
7230
6554
  });
6555
+ lp.command("autopilot").description("Auto-allocate budget across whitelisted pools (reads ~/.defi/pools.toml)").requiredOption("--budget <usd>", "Total budget in USD").option("--chain <chain>", "Filter whitelist to a specific chain").option("--dry-run", "Show plan only (default)", true).option("--broadcast", "Execute the plan (lending supply supported; LP types show a warning)").action(async (opts) => {
6556
+ const budgetUsd = parseFloat(opts.budget);
6557
+ if (isNaN(budgetUsd) || budgetUsd <= 0) {
6558
+ printOutput({ error: `Invalid budget: ${opts.budget}` }, getOpts());
6559
+ process.exit(1);
6560
+ return;
6561
+ }
6562
+ let whitelist = loadWhitelist();
6563
+ if (whitelist.length === 0) {
6564
+ printOutput(
6565
+ { error: "No pools whitelisted. Create ~/.defi/pools.toml (see config/pools.example.toml)" },
6566
+ getOpts()
6567
+ );
6568
+ process.exit(1);
6569
+ return;
6570
+ }
6571
+ const chainFilter = opts.chain?.toLowerCase();
6572
+ if (chainFilter) {
6573
+ whitelist = whitelist.filter((e) => e.chain.toLowerCase() === chainFilter);
6574
+ if (whitelist.length === 0) {
6575
+ printOutput(
6576
+ { error: `No whitelisted pools found for chain '${chainFilter}'` },
6577
+ getOpts()
6578
+ );
6579
+ process.exit(1);
6580
+ return;
6581
+ }
6582
+ }
6583
+ const registry = Registry.loadEmbedded();
6584
+ const scanned = await Promise.all(
6585
+ whitelist.map(async (entry) => {
6586
+ try {
6587
+ const chainName = entry.chain.toLowerCase();
6588
+ let chain;
6589
+ try {
6590
+ chain = registry.getChain(chainName);
6591
+ } catch {
6592
+ return { entry, scan_error: `Unknown chain '${chainName}'` };
6593
+ }
6594
+ const rpcUrl = chain.effectiveRpcUrl();
6595
+ if (entry.type === "lending" && entry.asset) {
6596
+ const protos = registry.getProtocolsForChain(chainName).filter(
6597
+ (p) => p.category === ProtocolCategory.Lending && p.slug === entry.protocol
6598
+ );
6599
+ if (protos.length === 0) {
6600
+ return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
6601
+ }
6602
+ const proto = protos[0];
6603
+ const assetAddr = registry.resolveToken(chainName, entry.asset).address;
6604
+ const adapter = createLending(proto, rpcUrl);
6605
+ const rates = await adapter.getRates(assetAddr);
6606
+ return { entry, apy: rates.supply_apy };
6607
+ }
6608
+ if (entry.type === "lb" && entry.pool) {
6609
+ const protos = registry.getProtocolsForChain(chainName).filter((p) => p.slug === entry.protocol);
6610
+ if (protos.length === 0) {
6611
+ return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
6612
+ }
6613
+ const proto = protos[0];
6614
+ if (proto.interface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
6615
+ const adapter = createMerchantMoeLB(proto, rpcUrl);
6616
+ const pools = await adapter.discoverRewardedPools();
6617
+ const match = pools.find(
6618
+ (p) => p.pool.toLowerCase() === entry.pool.toLowerCase() || `${p.symbolX}/${p.symbolY}`.toLowerCase() === entry.pool.toLowerCase() || `${p.symbolY}/${p.symbolX}`.toLowerCase() === entry.pool.toLowerCase()
6619
+ );
6620
+ if (match) {
6621
+ return { entry, apr: match.aprPercent, active: !match.stopped };
6622
+ }
6623
+ }
6624
+ return { entry, scan_error: "Pool not found in LB discovery" };
6625
+ }
6626
+ if (entry.type === "farming" && entry.pool) {
6627
+ const protos = registry.getProtocolsForChain(chainName).filter((p) => p.slug === entry.protocol);
6628
+ if (protos.length === 0) {
6629
+ return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
6630
+ }
6631
+ const proto = protos[0];
6632
+ if (proto.interface === "algebra_v3" && proto.contracts?.["farming_center"]) {
6633
+ const adapter = createKittenSwapFarming(proto, rpcUrl);
6634
+ const pools = await adapter.discoverFarmingPools();
6635
+ const match = pools.find(
6636
+ (p) => p.pool.toLowerCase() === entry.pool.toLowerCase()
6637
+ );
6638
+ if (match) {
6639
+ return { entry, active: match.active };
6640
+ }
6641
+ }
6642
+ return { entry, scan_error: "Pool not found in farming discovery" };
6643
+ }
6644
+ if (entry.type === "gauge" && entry.pool) {
6645
+ const protos = registry.getProtocolsForChain(chainName).filter((p) => p.slug === entry.protocol);
6646
+ if (protos.length === 0) {
6647
+ return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
6648
+ }
6649
+ const proto = protos[0];
6650
+ if (["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(proto.interface)) {
6651
+ const adapter = createGauge(proto, rpcUrl);
6652
+ if (adapter.discoverGaugedPools) {
6653
+ const pools = await adapter.discoverGaugedPools();
6654
+ const poolAddr = entry.pool.startsWith("0x") ? entry.pool.toLowerCase() : void 0;
6655
+ const match = pools.find(
6656
+ (p) => poolAddr && p.pool.toLowerCase() === poolAddr || `${p.token0}/${p.token1}`.toLowerCase() === entry.pool.toLowerCase() || `${p.token1}/${p.token0}`.toLowerCase() === entry.pool.toLowerCase()
6657
+ );
6658
+ return { entry, active: !!match };
6659
+ }
6660
+ }
6661
+ return { entry, scan_error: "Gauge discovery not supported for this protocol" };
6662
+ }
6663
+ return { entry, scan_error: "Unsupported entry type or missing pool/asset field" };
6664
+ } catch (err) {
6665
+ return { entry, scan_error: String(err) };
6666
+ }
6667
+ })
6668
+ );
6669
+ const RESERVE_PCT = 0.2;
6670
+ const deployableBudget = budgetUsd * (1 - RESERVE_PCT);
6671
+ const reserveUsd = budgetUsd * RESERVE_PCT;
6672
+ const ranked = [...scanned].sort((a, b) => {
6673
+ const scoreA = a.apy ?? a.apr ?? (a.active ? 1 : 0);
6674
+ const scoreB = b.apy ?? b.apr ?? (b.active ? 1 : 0);
6675
+ return scoreB - scoreA;
6676
+ });
6677
+ const allocations = [];
6678
+ let remainingBudget = deployableBudget;
6679
+ for (const s of ranked) {
6680
+ if (remainingBudget <= 0) break;
6681
+ const maxAlloc = budgetUsd * (s.entry.max_allocation_pct / 100);
6682
+ const alloc = Math.min(maxAlloc, remainingBudget);
6683
+ if (alloc <= 0) continue;
6684
+ const item = {
6685
+ protocol: s.entry.protocol,
6686
+ chain: s.entry.chain,
6687
+ type: s.entry.type,
6688
+ amount_usd: Math.round(alloc * 100) / 100
6689
+ };
6690
+ if (s.entry.pool) item["pool"] = s.entry.pool;
6691
+ if (s.entry.asset) item["asset"] = s.entry.asset;
6692
+ if (s.apy !== void 0) item["apy"] = s.apy;
6693
+ if (s.apr !== void 0) item["apr"] = s.apr;
6694
+ if (s.active !== void 0) item["active"] = s.active;
6695
+ if (s.scan_error) item["scan_error"] = s.scan_error;
6696
+ allocations.push(item);
6697
+ remainingBudget -= alloc;
6698
+ }
6699
+ const totalReserved = reserveUsd + remainingBudget;
6700
+ allocations.push({
6701
+ reserve: true,
6702
+ amount_usd: Math.round(totalReserved * 100) / 100,
6703
+ note: "20% safety margin (hardcoded) + unallocated remainder"
6704
+ });
6705
+ let estimatedAnnualYieldUsd = 0;
6706
+ for (const alloc of allocations) {
6707
+ if (alloc["reserve"]) continue;
6708
+ const amt = alloc["amount_usd"];
6709
+ const rate = alloc["apy"] ?? alloc["apr"];
6710
+ if (rate !== void 0 && rate > 0) {
6711
+ estimatedAnnualYieldUsd += amt * rate;
6712
+ }
6713
+ }
6714
+ const estimatedDailyYieldUsd = estimatedAnnualYieldUsd / 365;
6715
+ const isBroadcast = !!opts.broadcast;
6716
+ const plan = {
6717
+ budget_usd: budgetUsd,
6718
+ deployable_usd: Math.round(deployableBudget * 100) / 100,
6719
+ reserve_pct: RESERVE_PCT * 100,
6720
+ allocations,
6721
+ estimated_daily_yield_usd: Math.round(estimatedDailyYieldUsd * 100) / 100,
6722
+ estimated_annual_yield_usd: Math.round(estimatedAnnualYieldUsd * 100) / 100,
6723
+ execution: isBroadcast ? "broadcast" : "dry_run"
6724
+ };
6725
+ printOutput(plan, getOpts());
6726
+ if (!isBroadcast) return;
6727
+ process.stderr.write("\nExecuting autopilot plan...\n");
6728
+ const executor = makeExecutor2();
6729
+ const execResults = [];
6730
+ let allocIndex = 0;
6731
+ const actionAllocs = allocations.filter((a) => !a["reserve"]);
6732
+ for (const alloc of actionAllocs) {
6733
+ allocIndex++;
6734
+ const chainName = alloc["chain"].toLowerCase();
6735
+ let chain;
6736
+ try {
6737
+ chain = registry.getChain(chainName);
6738
+ } catch {
6739
+ process.stderr.write(`
6740
+ --- ${allocIndex}/${actionAllocs.length}: ${alloc["protocol"]} \u2014 unknown chain '${chainName}', skipping ---
6741
+ `);
6742
+ execResults.push({ ...alloc, exec_status: "skipped", exec_error: `Unknown chain '${chainName}'` });
6743
+ continue;
6744
+ }
6745
+ const rpc = chain.effectiveRpcUrl();
6746
+ process.stderr.write(`
6747
+ --- ${allocIndex}/${actionAllocs.length}: ${alloc["protocol"]} (${alloc["type"]}) $${alloc["amount_usd"]} ---
6748
+ `);
6749
+ if (alloc["type"] === "lending" && alloc["asset"]) {
6750
+ try {
6751
+ const protocol = registry.getProtocol(alloc["protocol"]);
6752
+ const adapter = createLending(protocol, rpc);
6753
+ const tokenInfo = registry.resolveToken(chainName, alloc["asset"]);
6754
+ const assetAddr = tokenInfo.address;
6755
+ const decimals = tokenInfo.decimals ?? 18;
6756
+ const amountWei = BigInt(Math.floor(alloc["amount_usd"] * 10 ** decimals));
6757
+ const wallet = resolveAccount();
6758
+ const tx = await adapter.buildSupply({
6759
+ protocol: alloc["protocol"],
6760
+ asset: assetAddr,
6761
+ amount: amountWei,
6762
+ on_behalf_of: wallet
6763
+ });
6764
+ process.stderr.write(` Supplying ${amountWei} wei of ${alloc["asset"]} to ${alloc["protocol"]}...
6765
+ `);
6766
+ const result = await executor.execute(tx);
6767
+ process.stderr.write(` Status: ${result.status}
6768
+ `);
6769
+ const explorerUrl = result.details?.["explorer_url"];
6770
+ if (explorerUrl) process.stderr.write(` Explorer: ${explorerUrl}
6771
+ `);
6772
+ execResults.push({ ...alloc, exec_status: result.status, tx_hash: result.tx_hash });
6773
+ } catch (err) {
6774
+ const msg = err instanceof Error ? err.message : String(err);
6775
+ process.stderr.write(` Error: ${msg}
6776
+ `);
6777
+ execResults.push({ ...alloc, exec_status: "error", exec_error: msg });
6778
+ }
6779
+ continue;
6780
+ }
6781
+ const lpMsg = `LP execution for type '${alloc["type"]}' pool '${alloc["pool"] ?? ""}' \u2014 requires manual token preparation (swap + addLiquidity not yet automated)`;
6782
+ process.stderr.write(` Warning: ${lpMsg}
6783
+ `);
6784
+ execResults.push({ ...alloc, exec_status: "skipped", exec_note: lpMsg });
6785
+ }
6786
+ process.stderr.write("\nAutopilot execution complete.\n");
6787
+ printOutput({ execution_results: execResults }, getOpts());
6788
+ });
7231
6789
  }
7232
6790
 
7233
6791
  // src/commands/lending.ts
7234
6792
  function registerLending(parent, getOpts, makeExecutor2) {
7235
6793
  const lending = parent.command("lending").description("Lending operations: supply, borrow, repay, withdraw, rates, position");
7236
6794
  lending.command("rates").description("Show current lending rates").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").action(async (opts) => {
7237
- const chainName = parent.opts().chain ?? "hyperevm";
6795
+ const chainName = parent.opts().chain;
6796
+ if (!chainName) {
6797
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6798
+ return;
6799
+ }
7238
6800
  const registry = Registry.loadEmbedded();
7239
6801
  const chain = registry.getChain(chainName);
7240
6802
  const protocol = registry.getProtocol(opts.protocol);
@@ -7244,7 +6806,11 @@ function registerLending(parent, getOpts, makeExecutor2) {
7244
6806
  printOutput(rates, getOpts());
7245
6807
  });
7246
6808
  lending.command("position").description("Show current lending position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--address <address>", "Wallet address to query").action(async (opts) => {
7247
- const chainName = parent.opts().chain ?? "hyperevm";
6809
+ const chainName = parent.opts().chain;
6810
+ if (!chainName) {
6811
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6812
+ return;
6813
+ }
7248
6814
  const registry = Registry.loadEmbedded();
7249
6815
  const chain = registry.getChain(chainName);
7250
6816
  const protocol = registry.getProtocol(opts.protocol);
@@ -7254,7 +6820,11 @@ function registerLending(parent, getOpts, makeExecutor2) {
7254
6820
  });
7255
6821
  lending.command("supply").description("Supply an asset to a lending protocol").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount to supply in wei").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
7256
6822
  const executor = makeExecutor2();
7257
- const chainName = parent.opts().chain ?? "hyperevm";
6823
+ const chainName = parent.opts().chain;
6824
+ if (!chainName) {
6825
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6826
+ return;
6827
+ }
7258
6828
  const registry = Registry.loadEmbedded();
7259
6829
  const chain = registry.getChain(chainName);
7260
6830
  const protocol = registry.getProtocol(opts.protocol);
@@ -7267,7 +6837,11 @@ function registerLending(parent, getOpts, makeExecutor2) {
7267
6837
  });
7268
6838
  lending.command("borrow").description("Borrow an asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--rate-mode <mode>", "variable or stable", "variable").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
7269
6839
  const executor = makeExecutor2();
7270
- const chainName = parent.opts().chain ?? "hyperevm";
6840
+ const chainName = parent.opts().chain;
6841
+ if (!chainName) {
6842
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6843
+ return;
6844
+ }
7271
6845
  const registry = Registry.loadEmbedded();
7272
6846
  const chain = registry.getChain(chainName);
7273
6847
  const protocol = registry.getProtocol(opts.protocol);
@@ -7286,7 +6860,11 @@ function registerLending(parent, getOpts, makeExecutor2) {
7286
6860
  });
7287
6861
  lending.command("repay").description("Repay a borrowed asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--rate-mode <mode>", "variable or stable", "variable").option("--on-behalf-of <address>", "On behalf of address").action(async (opts) => {
7288
6862
  const executor = makeExecutor2();
7289
- const chainName = parent.opts().chain ?? "hyperevm";
6863
+ const chainName = parent.opts().chain;
6864
+ if (!chainName) {
6865
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6866
+ return;
6867
+ }
7290
6868
  const registry = Registry.loadEmbedded();
7291
6869
  const chain = registry.getChain(chainName);
7292
6870
  const protocol = registry.getProtocol(opts.protocol);
@@ -7305,7 +6883,11 @@ function registerLending(parent, getOpts, makeExecutor2) {
7305
6883
  });
7306
6884
  lending.command("withdraw").description("Withdraw a supplied asset").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").option("--to <address>", "Recipient address").action(async (opts) => {
7307
6885
  const executor = makeExecutor2();
7308
- const chainName = parent.opts().chain ?? "hyperevm";
6886
+ const chainName = parent.opts().chain;
6887
+ if (!chainName) {
6888
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
6889
+ return;
6890
+ }
7309
6891
  const registry = Registry.loadEmbedded();
7310
6892
  const chain = registry.getChain(chainName);
7311
6893
  const protocol = registry.getProtocol(opts.protocol);
@@ -7318,115 +6900,6 @@ function registerLending(parent, getOpts, makeExecutor2) {
7318
6900
  });
7319
6901
  }
7320
6902
 
7321
- // src/commands/cdp.ts
7322
- function registerCdp(parent, getOpts, makeExecutor2) {
7323
- const cdp = parent.command("cdp").description("CDP operations: open, adjust, close, info");
7324
- cdp.command("open").description("Open a new CDP position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--collateral <token>", "Collateral token address").requiredOption("--amount <amount>", "Collateral amount in wei").requiredOption("--mint <amount>", "Stablecoin to mint in wei").option("--recipient <address>", "Recipient address").action(async (opts) => {
7325
- const executor = makeExecutor2();
7326
- const chainName = parent.opts().chain ?? "hyperevm";
7327
- const registry = Registry.loadEmbedded();
7328
- const chain = registry.getChain(chainName);
7329
- const protocol = registry.getProtocol(opts.protocol);
7330
- const adapter = createCdp(protocol, chain.effectiveRpcUrl());
7331
- const recipient = opts.recipient ?? process.env.DEFI_WALLET_ADDRESS ?? "0x0000000000000000000000000000000000000001";
7332
- const tx = await adapter.buildOpen({
7333
- protocol: protocol.name,
7334
- collateral: opts.collateral,
7335
- collateral_amount: BigInt(opts.amount),
7336
- debt_amount: BigInt(opts.mint),
7337
- recipient
7338
- });
7339
- const result = await executor.execute(tx);
7340
- printOutput(result, getOpts());
7341
- });
7342
- cdp.command("info").description("Show CDP position info, or protocol overview if --position is omitted").requiredOption("--protocol <protocol>", "Protocol slug").option("--position <id>", "CDP/trove ID (omit for protocol overview)").action(async (opts) => {
7343
- const chainName = parent.opts().chain ?? "hyperevm";
7344
- const registry = Registry.loadEmbedded();
7345
- const chain = registry.getChain(chainName);
7346
- const protocol = registry.getProtocol(opts.protocol);
7347
- if (opts.position === void 0) {
7348
- printOutput({
7349
- name: protocol.name,
7350
- slug: protocol.slug,
7351
- chain: chainName,
7352
- contracts: protocol.contracts ?? {}
7353
- }, getOpts());
7354
- return;
7355
- }
7356
- const adapter = createCdp(protocol, chain.effectiveRpcUrl());
7357
- const info = await adapter.getCdpInfo(BigInt(opts.position));
7358
- printOutput(info, getOpts());
7359
- });
7360
- cdp.command("adjust").description("Adjust an existing CDP position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--position <id>", "CDP/trove ID").option("--add-collateral <amount>", "Add collateral in wei").option("--withdraw-collateral <amount>", "Withdraw collateral in wei").option("--mint <amount>", "Mint additional stablecoin").option("--repay <amount>", "Repay stablecoin").action(async (opts) => {
7361
- const executor = makeExecutor2();
7362
- const chainName = parent.opts().chain ?? "hyperevm";
7363
- const registry = Registry.loadEmbedded();
7364
- const chain = registry.getChain(chainName);
7365
- const protocol = registry.getProtocol(opts.protocol);
7366
- const adapter = createCdp(protocol, chain.effectiveRpcUrl());
7367
- const tx = await adapter.buildAdjust({
7368
- protocol: protocol.name,
7369
- cdp_id: BigInt(opts.position),
7370
- collateral_delta: opts.addCollateral ? BigInt(opts.addCollateral) : opts.withdrawCollateral ? BigInt(opts.withdrawCollateral) : void 0,
7371
- debt_delta: opts.mint ? BigInt(opts.mint) : opts.repay ? BigInt(opts.repay) : void 0,
7372
- add_collateral: !!opts.addCollateral,
7373
- add_debt: !!opts.mint
7374
- });
7375
- const result = await executor.execute(tx);
7376
- printOutput(result, getOpts());
7377
- });
7378
- cdp.command("close").description("Close a CDP position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--position <id>", "CDP/trove ID").action(async (opts) => {
7379
- const executor = makeExecutor2();
7380
- const chainName = parent.opts().chain ?? "hyperevm";
7381
- const registry = Registry.loadEmbedded();
7382
- const chain = registry.getChain(chainName);
7383
- const protocol = registry.getProtocol(opts.protocol);
7384
- const adapter = createCdp(protocol, chain.effectiveRpcUrl());
7385
- const tx = await adapter.buildClose({ protocol: protocol.name, cdp_id: BigInt(opts.position) });
7386
- const result = await executor.execute(tx);
7387
- printOutput(result, getOpts());
7388
- });
7389
- }
7390
-
7391
- // src/commands/vault.ts
7392
- function registerVault(parent, getOpts, makeExecutor2) {
7393
- const vault = parent.command("vault").description("Vault operations: deposit, withdraw, info");
7394
- vault.command("deposit").description("Deposit assets into a vault").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--amount <amount>", "Amount in wei").option("--receiver <address>", "Receiver address for vault shares").action(async (opts) => {
7395
- const executor = makeExecutor2();
7396
- const chainName = parent.opts().chain ?? "hyperevm";
7397
- const registry = Registry.loadEmbedded();
7398
- const chain = registry.getChain(chainName);
7399
- const protocol = registry.getProtocol(opts.protocol);
7400
- const adapter = createVault(protocol, chain.effectiveRpcUrl());
7401
- const receiver = opts.receiver ?? process.env.DEFI_WALLET_ADDRESS ?? "0x0000000000000000000000000000000000000001";
7402
- const tx = await adapter.buildDeposit(BigInt(opts.amount), receiver);
7403
- const result = await executor.execute(tx);
7404
- printOutput(result, getOpts());
7405
- });
7406
- vault.command("withdraw").description("Withdraw assets from a vault").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--amount <amount>", "Amount in wei (shares)").option("--receiver <address>", "Receiver address").option("--owner <address>", "Owner address").action(async (opts) => {
7407
- const executor = makeExecutor2();
7408
- const chainName = parent.opts().chain ?? "hyperevm";
7409
- const registry = Registry.loadEmbedded();
7410
- const chain = registry.getChain(chainName);
7411
- const protocol = registry.getProtocol(opts.protocol);
7412
- const adapter = createVault(protocol, chain.effectiveRpcUrl());
7413
- const receiver = opts.receiver ?? process.env.DEFI_WALLET_ADDRESS ?? "0x0000000000000000000000000000000000000001";
7414
- const owner = opts.owner ?? receiver;
7415
- const tx = await adapter.buildWithdraw(BigInt(opts.amount), receiver, owner);
7416
- const result = await executor.execute(tx);
7417
- printOutput(result, getOpts());
7418
- });
7419
- vault.command("info").description("Show vault info (TVL, APY, shares)").requiredOption("--protocol <protocol>", "Protocol slug").action(async (opts) => {
7420
- const chainName = parent.opts().chain ?? "hyperevm";
7421
- const registry = Registry.loadEmbedded();
7422
- const chain = registry.getChain(chainName);
7423
- const protocol = registry.getProtocol(opts.protocol);
7424
- const adapter = createVault(protocol, chain.effectiveRpcUrl());
7425
- const info = await adapter.getVaultInfo();
7426
- printOutput(info, getOpts());
7427
- });
7428
- }
7429
-
7430
6903
  // src/commands/yield.ts
7431
6904
  function resolveAsset(registry, chain, asset) {
7432
6905
  if (/^0x[0-9a-fA-F]{40}$/.test(asset)) {
@@ -7642,8 +7115,10 @@ function registerYield(parent, getOpts, makeExecutor2) {
7642
7115
  try {
7643
7116
  const registry = Registry.loadEmbedded();
7644
7117
  const asset = opts.asset;
7118
+ const specifiedChain = parent.opts().chain;
7119
+ const chainKeys = specifiedChain ? [specifiedChain.toLowerCase()] : Array.from(registry.chains.keys());
7645
7120
  const allRates = [];
7646
- for (const [chainKey] of registry.chains) {
7121
+ for (const chainKey of chainKeys) {
7647
7122
  try {
7648
7123
  const chain = registry.getChain(chainKey);
7649
7124
  const rpc = chain.effectiveRpcUrl();
@@ -7672,7 +7147,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
7672
7147
  yieldCmd.command("compare").description("Compare lending rates across protocols for an asset").option("--asset <token>", "Token symbol or address", "USDC").action(async (opts) => {
7673
7148
  try {
7674
7149
  const registry = Registry.loadEmbedded();
7675
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
7150
+ const specChain = parent.opts().chain;
7151
+ if (!specChain) {
7152
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
7153
+ return;
7154
+ }
7155
+ const chainName = specChain.toLowerCase();
7676
7156
  const chain = registry.getChain(chainName);
7677
7157
  const rpc = chain.effectiveRpcUrl();
7678
7158
  const assetAddr = resolveAsset(registry, chainName, opts.asset);
@@ -7908,7 +7388,12 @@ function registerYield(parent, getOpts, makeExecutor2) {
7908
7388
  yieldCmd.command("optimize").description("Find the optimal yield strategy for an asset").requiredOption("--asset <token>", "Token symbol or address").option("--strategy <strategy>", "Strategy: best-supply, leverage-loop, auto", "auto").option("--amount <amount>", "Amount to deploy (for allocation breakdown)").action(async (opts) => {
7909
7389
  try {
7910
7390
  const registry = Registry.loadEmbedded();
7911
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
7391
+ const specChain = parent.opts().chain;
7392
+ if (!specChain) {
7393
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
7394
+ return;
7395
+ }
7396
+ const chainName = specChain.toLowerCase();
7912
7397
  const chain = registry.getChain(chainName);
7913
7398
  const rpc = chain.effectiveRpcUrl();
7914
7399
  const asset = opts.asset;
@@ -8040,9 +7525,9 @@ function registerYield(parent, getOpts, makeExecutor2) {
8040
7525
  import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
8041
7526
 
8042
7527
  // src/portfolio-tracker.ts
8043
- import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
7528
+ import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
8044
7529
  import { homedir } from "os";
8045
- import { resolve as resolve2 } from "path";
7530
+ import { resolve as resolve3 } from "path";
8046
7531
  import { encodeFunctionData as encodeFunctionData27, parseAbi as parseAbi30 } from "viem";
8047
7532
  var ERC20_ABI4 = parseAbi30([
8048
7533
  "function balanceOf(address owner) external view returns (uint256)"
@@ -8059,7 +7544,7 @@ function decodeU256Word(data, wordOffset = 0) {
8059
7544
  return BigInt("0x" + hex);
8060
7545
  }
8061
7546
  function snapshotDir() {
8062
- return resolve2(homedir(), ".defi-cli", "snapshots");
7547
+ return resolve3(homedir(), ".defi-cli", "snapshots");
8063
7548
  }
8064
7549
  async function takeSnapshot(chainName, wallet, registry) {
8065
7550
  const chain = registry.getChain(chainName);
@@ -8173,7 +7658,7 @@ function saveSnapshot(snapshot) {
8173
7658
  const dir = snapshotDir();
8174
7659
  mkdirSync(dir, { recursive: true });
8175
7660
  const filename = `${snapshot.chain}_${snapshot.wallet}_${snapshot.timestamp}.json`;
8176
- const filepath = resolve2(dir, filename);
7661
+ const filepath = resolve3(dir, filename);
8177
7662
  writeFileSync(filepath, JSON.stringify(snapshot, (_k, v) => typeof v === "bigint" ? v.toString() : v, 2));
8178
7663
  return filepath;
8179
7664
  }
@@ -8183,7 +7668,7 @@ function loadSnapshots(chain, wallet, limit = 10) {
8183
7668
  const prefix = `${chain}_${wallet}_`;
8184
7669
  const files = readdirSync2(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".json")).sort().reverse().slice(0, limit);
8185
7670
  return files.map((f) => {
8186
- const raw = JSON.parse(readFileSync2(resolve2(dir, f), "utf-8"));
7671
+ const raw = JSON.parse(readFileSync3(resolve3(dir, f), "utf-8"));
8187
7672
  if (Array.isArray(raw.tokens)) {
8188
7673
  for (const t of raw.tokens) {
8189
7674
  if (typeof t.balance === "string") t.balance = BigInt(t.balance);
@@ -8254,7 +7739,12 @@ function registerPortfolio(parent, getOpts) {
8254
7739
  portfolio.command("show").description("Show current portfolio positions").requiredOption("--address <address>", "Wallet address to query").action(async (opts) => {
8255
7740
  const mode = getOpts();
8256
7741
  const registry = Registry.loadEmbedded();
8257
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
7742
+ const _chain = parent.opts().chain;
7743
+ if (!_chain) {
7744
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
7745
+ return;
7746
+ }
7747
+ const chainName = _chain.toLowerCase();
8258
7748
  let chain;
8259
7749
  try {
8260
7750
  chain = registry.getChain(chainName);
@@ -8392,7 +7882,12 @@ function registerPortfolio(parent, getOpts) {
8392
7882
  });
8393
7883
  portfolio.command("snapshot").description("Take a new portfolio snapshot and save it locally").requiredOption("--address <address>", "Wallet address to snapshot").action(async (opts) => {
8394
7884
  const mode = getOpts();
8395
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
7885
+ const _chain = parent.opts().chain;
7886
+ if (!_chain) {
7887
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
7888
+ return;
7889
+ }
7890
+ const chainName = _chain.toLowerCase();
8396
7891
  const registry = Registry.loadEmbedded();
8397
7892
  if (!/^0x[0-9a-fA-F]{40}$/.test(opts.address)) {
8398
7893
  printOutput({ error: `Invalid address: ${opts.address}` }, mode);
@@ -8419,7 +7914,12 @@ function registerPortfolio(parent, getOpts) {
8419
7914
  });
8420
7915
  portfolio.command("pnl").description("Show PnL since the last snapshot").requiredOption("--address <address>", "Wallet address").option("--since <hours>", "Compare against snapshot from N hours ago (default: last snapshot)").action(async (opts) => {
8421
7916
  const mode = getOpts();
8422
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
7917
+ const _chain = parent.opts().chain;
7918
+ if (!_chain) {
7919
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
7920
+ return;
7921
+ }
7922
+ const chainName = _chain.toLowerCase();
8423
7923
  const registry = Registry.loadEmbedded();
8424
7924
  if (!/^0x[0-9a-fA-F]{40}$/.test(opts.address)) {
8425
7925
  printOutput({ error: `Invalid address: ${opts.address}` }, mode);
@@ -8464,7 +7964,12 @@ function registerPortfolio(parent, getOpts) {
8464
7964
  });
8465
7965
  portfolio.command("history").description("List saved portfolio snapshots with values").requiredOption("--address <address>", "Wallet address").option("--limit <n>", "Number of snapshots to show", "10").action(async (opts) => {
8466
7966
  const mode = getOpts();
8467
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
7967
+ const _chain = parent.opts().chain;
7968
+ if (!_chain) {
7969
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
7970
+ return;
7971
+ }
7972
+ const chainName = _chain.toLowerCase();
8468
7973
  if (!/^0x[0-9a-fA-F]{40}$/.test(opts.address)) {
8469
7974
  printOutput({ error: `Invalid address: ${opts.address}` }, mode);
8470
7975
  return;
@@ -8487,858 +7992,8 @@ function registerPortfolio(parent, getOpts) {
8487
7992
  });
8488
7993
  }
8489
7994
 
8490
- // src/commands/monitor.ts
8491
- async function checkChainLendingPositions(chainKey, registry, address, threshold) {
8492
- let chain;
8493
- try {
8494
- chain = registry.getChain(chainKey);
8495
- } catch {
8496
- return [];
8497
- }
8498
- const rpc = chain.effectiveRpcUrl();
8499
- const chainName = chain.name;
8500
- const protocols = registry.getProtocolsForChain(chainKey).filter(
8501
- (p) => p.category === ProtocolCategory.Lending
8502
- );
8503
- const results = await Promise.all(
8504
- protocols.map(async (proto) => {
8505
- try {
8506
- const adapter = createLending(proto, rpc);
8507
- const position = await adapter.getUserPosition(address);
8508
- const hf = position.health_factor ?? Infinity;
8509
- const totalBorrow = position.borrows?.reduce(
8510
- (sum, b) => sum + (b.value_usd ?? 0),
8511
- 0
8512
- ) ?? 0;
8513
- if (totalBorrow === 0) return null;
8514
- const totalSupply = position.supplies?.reduce(
8515
- (sum, s) => sum + (s.value_usd ?? 0),
8516
- 0
8517
- ) ?? 0;
8518
- return {
8519
- chain: chainName,
8520
- protocol: proto.name,
8521
- health_factor: hf === Infinity ? 999999 : Math.round(hf * 100) / 100,
8522
- total_supply_usd: Math.round(totalSupply * 100) / 100,
8523
- total_borrow_usd: Math.round(totalBorrow * 100) / 100,
8524
- alert: hf < threshold
8525
- };
8526
- } catch {
8527
- return null;
8528
- }
8529
- })
8530
- );
8531
- return results.filter((r) => r !== null);
8532
- }
8533
- function registerMonitor(parent, getOpts) {
8534
- parent.command("monitor").description("Monitor health factor with alerts").option("--protocol <protocol>", "Protocol slug (required unless --all-chains)").requiredOption("--address <address>", "Wallet address to monitor").option("--threshold <hf>", "Health factor alert threshold", "1.5").option("--interval <secs>", "Polling interval in seconds", "60").option("--once", "Run once instead of continuously").option("--all-chains", "Scan all chains for lending positions").action(async (opts) => {
8535
- const threshold = parseFloat(opts.threshold);
8536
- const address = opts.address;
8537
- if (opts.allChains) {
8538
- const registry = Registry.loadEmbedded();
8539
- const chainKeys = Array.from(registry.chains.keys());
8540
- const poll = async () => {
8541
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8542
- const chainResults = await Promise.all(
8543
- chainKeys.map(
8544
- (ck) => checkChainLendingPositions(ck, registry, address, threshold)
8545
- )
8546
- );
8547
- const positions = chainResults.flat();
8548
- const alertsCount = positions.filter((p) => p.alert).length;
8549
- const output = {
8550
- timestamp,
8551
- address,
8552
- threshold,
8553
- positions,
8554
- alerts_count: alertsCount
8555
- };
8556
- for (const pos of positions) {
8557
- if (pos.alert) {
8558
- process.stderr.write(
8559
- `ALERT: ${pos.chain}/${pos.protocol} HF=${pos.health_factor} < ${threshold}
8560
- `
8561
- );
8562
- }
8563
- }
8564
- printOutput(output, getOpts());
8565
- };
8566
- await poll();
8567
- if (!opts.once) {
8568
- const intervalMs = parseInt(opts.interval) * 1e3;
8569
- const timer = setInterval(poll, intervalMs);
8570
- process.on("SIGINT", () => {
8571
- clearInterval(timer);
8572
- process.exit(0);
8573
- });
8574
- }
8575
- } else {
8576
- if (!opts.protocol) {
8577
- printOutput({ error: "Either --protocol or --all-chains is required" }, getOpts());
8578
- process.exit(1);
8579
- }
8580
- const chainName = parent.opts().chain ?? "hyperevm";
8581
- const registry = Registry.loadEmbedded();
8582
- const chain = registry.getChain(chainName);
8583
- const protocol = registry.getProtocol(opts.protocol);
8584
- const adapter = createLending(protocol, chain.effectiveRpcUrl());
8585
- const poll = async () => {
8586
- try {
8587
- const position = await adapter.getUserPosition(address);
8588
- const hf = position.health_factor ?? Infinity;
8589
- const alert = hf < threshold;
8590
- printOutput({
8591
- protocol: protocol.name,
8592
- user: opts.address,
8593
- health_factor: hf,
8594
- threshold,
8595
- alert,
8596
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8597
- supplies: position.supplies,
8598
- borrows: position.borrows
8599
- }, getOpts());
8600
- } catch (e) {
8601
- printOutput({
8602
- error: e instanceof Error ? e.message : String(e),
8603
- protocol: protocol.name,
8604
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8605
- }, getOpts());
8606
- }
8607
- };
8608
- await poll();
8609
- if (!opts.once) {
8610
- const intervalMs = parseInt(opts.interval) * 1e3;
8611
- const timer = setInterval(poll, intervalMs);
8612
- process.on("SIGINT", () => {
8613
- clearInterval(timer);
8614
- process.exit(0);
8615
- });
8616
- }
8617
- }
8618
- });
8619
- }
8620
-
8621
- // src/commands/alert.ts
8622
- function registerAlert(parent, getOpts) {
8623
- parent.command("alert").description("Alert on DEX vs Oracle price deviation").option("--threshold <pct>", "Deviation threshold in percent", "5.0").option("--once", "Run once instead of continuously").option("--interval <secs>", "Polling interval in seconds", "60").action(async (opts) => {
8624
- const chainName = parent.opts().chain ?? "hyperevm";
8625
- const registry = Registry.loadEmbedded();
8626
- const chain = registry.getChain(chainName);
8627
- const rpcUrl = chain.effectiveRpcUrl();
8628
- const threshold = parseFloat(opts.threshold);
8629
- const dexProtocols = registry.getProtocolsByCategory("dex").filter((p) => p.chain === chainName);
8630
- const lendingProtocols = registry.getProtocolsByCategory("lending").filter((p) => p.chain === chainName);
8631
- const poll = async () => {
8632
- const alerts = [];
8633
- for (const p of dexProtocols) {
8634
- try {
8635
- const dex = createDex(p, rpcUrl);
8636
- alerts.push({
8637
- protocol: p.name,
8638
- type: "info",
8639
- message: `DEX ${dex.name()} active on ${chainName}`
8640
- });
8641
- } catch {
8642
- }
8643
- }
8644
- printOutput({
8645
- chain: chainName,
8646
- threshold_pct: threshold,
8647
- alerts,
8648
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8649
- }, getOpts());
8650
- };
8651
- await poll();
8652
- if (!opts.once) {
8653
- const intervalMs = parseInt(opts.interval) * 1e3;
8654
- const timer = setInterval(poll, intervalMs);
8655
- process.on("SIGINT", () => {
8656
- clearInterval(timer);
8657
- process.exit(0);
8658
- });
8659
- }
8660
- });
8661
- }
8662
-
8663
- // src/commands/scan.ts
8664
- import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
8665
- var AAVE_ORACLE_ABI = parseAbi33([
8666
- "function getAssetPrice(address asset) external view returns (uint256)"
8667
- ]);
8668
- var UNIV2_ROUTER_ABI = parseAbi33([
8669
- "function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory)"
8670
- ]);
8671
- var VTOKEN_ABI = parseAbi33([
8672
- "function exchangeRateStored() external view returns (uint256)"
8673
- ]);
8674
- var STABLECOINS = /* @__PURE__ */ new Set(["USDC", "USDT", "DAI", "USDT0"]);
8675
- function round2(x) {
8676
- return Math.round(x * 100) / 100;
8677
- }
8678
- function round4(x) {
8679
- return Math.round(x * 1e4) / 1e4;
8680
- }
8681
- function round6(x) {
8682
- return Math.round(x * 1e6) / 1e6;
8683
- }
8684
- function parseU256F64(data, decimals) {
8685
- if (!data || data.length < 66) return 0;
8686
- const raw = BigInt(data.slice(0, 66));
8687
- return Number(raw) / 10 ** decimals;
8688
- }
8689
- function parseAmountsOutLast(data, outDecimals) {
8690
- if (!data) return 0;
8691
- const hex = data.startsWith("0x") ? data.slice(2) : data;
8692
- if (hex.length < 128) return 0;
8693
- const num = parseInt(hex.slice(64, 128), 16);
8694
- if (num === 0) return 0;
8695
- const byteOff = 64 + (num - 1) * 32;
8696
- const hexOff = byteOff * 2;
8697
- if (hex.length < hexOff + 64) return 0;
8698
- const val = BigInt("0x" + hex.slice(hexOff, hexOff + 64));
8699
- return Number(val) / 10 ** outDecimals;
8700
- }
8701
- function registerScan(parent, getOpts) {
8702
- parent.command("scan").description("Multi-pattern exploit detection scanner").option("--chain <chain>", "Chain to scan", "hyperevm").option("--patterns <patterns>", "Comma-separated patterns: oracle,stable,exchange_rate", "oracle,stable,exchange_rate").option("--oracle-threshold <pct>", "Oracle divergence threshold (percent)", "5.0").option("--stable-threshold <price>", "Stablecoin depeg threshold (min price)", "0.98").option("--rate-threshold <pct>", "Exchange rate change threshold (percent)", "5.0").option("--interval <secs>", "Polling interval in seconds", "30").option("--once", "Single check then exit").option("--all-chains", "Scan all chains in parallel").action(async (opts) => {
8703
- try {
8704
- const registry = Registry.loadEmbedded();
8705
- const oracleThreshold = parseFloat(opts.oracleThreshold ?? "5.0");
8706
- const stableThreshold = parseFloat(opts.stableThreshold ?? "0.98");
8707
- const rateThreshold = parseFloat(opts.rateThreshold ?? "5.0");
8708
- const interval = parseInt(opts.interval ?? "30", 10);
8709
- const patterns = opts.patterns ?? "oracle,stable,exchange_rate";
8710
- const once = !!opts.once;
8711
- if (opts.allChains) {
8712
- const result = await runAllChains(registry, patterns, oracleThreshold, stableThreshold, rateThreshold);
8713
- printOutput(result, getOpts());
8714
- return;
8715
- }
8716
- const chainName = (opts.chain ?? "hyperevm").toLowerCase();
8717
- const chain = registry.getChain(chainName);
8718
- const rpc = chain.effectiveRpcUrl();
8719
- const pats = patterns.split(",").map((s) => s.trim());
8720
- const doOracle = pats.includes("oracle");
8721
- const doStable = pats.includes("stable");
8722
- const doRate = pats.includes("exchange_rate");
8723
- const allTokens = registry.tokens.get(chainName) ?? [];
8724
- const wrappedNative = chain.wrapped_native;
8725
- const quoteStable = (() => {
8726
- for (const sym of ["USDT", "USDC", "USDT0"]) {
8727
- try {
8728
- return registry.resolveToken(chainName, sym);
8729
- } catch {
8730
- }
8731
- }
8732
- return null;
8733
- })();
8734
- if (!quoteStable) {
8735
- printOutput({ error: `No stablecoin found on chain ${chainName}` }, getOpts());
8736
- return;
8737
- }
8738
- const scanTokens = allTokens.filter(
8739
- (t) => t.address !== "0x0000000000000000000000000000000000000000" && !STABLECOINS.has(t.symbol)
8740
- );
8741
- const oracles = registry.getProtocolsForChain(chainName).filter(
8742
- (p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2" || p.interface === "aave_v3_isolated")
8743
- ).flatMap((p) => {
8744
- const oracleAddr = p.contracts?.["oracle"];
8745
- if (!oracleAddr) return [];
8746
- const decimals = p.interface === "aave_v2" ? 18 : 8;
8747
- return [{ name: p.name, addr: oracleAddr, decimals }];
8748
- });
8749
- const dexProto = registry.getProtocolsForChain(chainName).find((p) => p.category === ProtocolCategory.Dex && p.interface === "uniswap_v2");
8750
- const dexRouter = dexProto?.contracts?.["router"];
8751
- const compoundForks = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "compound_v2").map((p) => ({
8752
- name: p.name,
8753
- vtokens: Object.entries(p.contracts ?? {}).filter(([k]) => k.startsWith("v")).map(([k, a]) => ({ key: k, addr: a }))
8754
- }));
8755
- const usdc = (() => {
8756
- try {
8757
- return registry.resolveToken(chainName, "USDC");
8758
- } catch {
8759
- return null;
8760
- }
8761
- })();
8762
- const usdt = (() => {
8763
- try {
8764
- return registry.resolveToken(chainName, "USDT");
8765
- } catch {
8766
- return null;
8767
- }
8768
- })();
8769
- const prevRates = /* @__PURE__ */ new Map();
8770
- const runOnce = async () => {
8771
- const timestamp = Math.floor(Date.now() / 1e3);
8772
- const t0 = Date.now();
8773
- const calls = [];
8774
- const callTypes = [];
8775
- if (doOracle) {
8776
- for (const oracle of oracles) {
8777
- for (const token of scanTokens) {
8778
- callTypes.push({ kind: "oracle", oracle: oracle.name, token: token.symbol, oracleDecimals: oracle.decimals });
8779
- calls.push([
8780
- oracle.addr,
8781
- encodeFunctionData29({ abi: AAVE_ORACLE_ABI, functionName: "getAssetPrice", args: [token.address] })
8782
- ]);
8783
- }
8784
- }
8785
- if (dexRouter) {
8786
- for (const token of scanTokens) {
8787
- const amountIn = BigInt(10) ** BigInt(token.decimals);
8788
- const path = wrappedNative && token.address.toLowerCase() === wrappedNative.toLowerCase() ? [token.address, quoteStable.address] : wrappedNative ? [token.address, wrappedNative, quoteStable.address] : [token.address, quoteStable.address];
8789
- callTypes.push({ kind: "dex", token: token.symbol, outDecimals: quoteStable.decimals });
8790
- calls.push([
8791
- dexRouter,
8792
- encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [amountIn, path] })
8793
- ]);
8794
- }
8795
- }
8796
- }
8797
- if (doStable && usdc && usdt && dexRouter) {
8798
- callTypes.push({ kind: "stable", from: "USDC", to: "USDT", outDecimals: usdt.decimals });
8799
- calls.push([
8800
- dexRouter,
8801
- encodeFunctionData29({
8802
- abi: UNIV2_ROUTER_ABI,
8803
- functionName: "getAmountsOut",
8804
- args: [BigInt(10) ** BigInt(usdc.decimals), [usdc.address, usdt.address]]
8805
- })
8806
- ]);
8807
- callTypes.push({ kind: "stable", from: "USDT", to: "USDC", outDecimals: usdc.decimals });
8808
- calls.push([
8809
- dexRouter,
8810
- encodeFunctionData29({
8811
- abi: UNIV2_ROUTER_ABI,
8812
- functionName: "getAmountsOut",
8813
- args: [BigInt(10) ** BigInt(usdt.decimals), [usdt.address, usdc.address]]
8814
- })
8815
- ]);
8816
- }
8817
- if (doRate) {
8818
- for (const fork of compoundForks) {
8819
- for (const { key, addr } of fork.vtokens) {
8820
- callTypes.push({ kind: "exchangeRate", protocol: fork.name, vtoken: key });
8821
- calls.push([addr, encodeFunctionData29({ abi: VTOKEN_ABI, functionName: "exchangeRateStored", args: [] })]);
8822
- }
8823
- }
8824
- }
8825
- if (calls.length === 0) {
8826
- printOutput({ error: `No scannable resources found on ${chainName}` }, getOpts());
8827
- return;
8828
- }
8829
- const results = await multicallRead(rpc, calls);
8830
- const scanMs = Date.now() - t0;
8831
- const alerts = [];
8832
- const oracleByToken = /* @__PURE__ */ new Map();
8833
- const dexByToken = /* @__PURE__ */ new Map();
8834
- const oracleData = {};
8835
- const dexData = {};
8836
- const stableData = {};
8837
- const stablePrices = [];
8838
- const rateData = {};
8839
- for (let i = 0; i < callTypes.length; i++) {
8840
- const ct = callTypes[i];
8841
- const raw = results[i] ?? null;
8842
- if (ct.kind === "oracle") {
8843
- const price = parseU256F64(raw, ct.oracleDecimals);
8844
- if (price > 0) {
8845
- const existing = oracleByToken.get(ct.token) ?? [];
8846
- existing.push({ oracle: ct.oracle, price });
8847
- oracleByToken.set(ct.token, existing);
8848
- oracleData[`${ct.oracle}/${ct.token}`] = round4(price);
8849
- }
8850
- } else if (ct.kind === "dex") {
8851
- const price = parseAmountsOutLast(raw, ct.outDecimals);
8852
- if (price > 0) {
8853
- dexByToken.set(ct.token, price);
8854
- dexData[ct.token] = round4(price);
8855
- }
8856
- } else if (ct.kind === "stable") {
8857
- const price = parseAmountsOutLast(raw, ct.outDecimals);
8858
- if (price <= 0) continue;
8859
- const pair = `${ct.from}/${ct.to}`;
8860
- stableData[pair] = round4(price);
8861
- stablePrices.push({ asset: ct.from, pair, price });
8862
- } else if (ct.kind === "exchangeRate") {
8863
- const rate = parseU256F64(raw, 18);
8864
- const key = `${ct.protocol}/${ct.vtoken}`;
8865
- rateData[key] = round6(rate);
8866
- if (rate > 0) {
8867
- const prev = prevRates.get(key);
8868
- if (prev !== void 0) {
8869
- const change = Math.abs((rate - prev) / prev * 100);
8870
- if (change > rateThreshold) {
8871
- const severity = change > 50 ? "critical" : change > 20 ? "high" : "medium";
8872
- alerts.push({
8873
- pattern: "exchange_rate_anomaly",
8874
- severity,
8875
- protocol: ct.protocol,
8876
- vtoken: ct.vtoken,
8877
- prev_rate: round6(prev),
8878
- curr_rate: round6(rate),
8879
- change_pct: round2(change),
8880
- action: `possible donation attack on ${ct.protocol} ${ct.vtoken}`
8881
- });
8882
- }
8883
- }
8884
- prevRates.set(key, rate);
8885
- }
8886
- }
8887
- }
8888
- if (stablePrices.length >= 2) {
8889
- const allBelow = stablePrices.every((s) => s.price < stableThreshold);
8890
- if (!allBelow) {
8891
- for (const { asset, pair, price } of stablePrices) {
8892
- if (price < stableThreshold) {
8893
- const severity = price < 0.95 ? "critical" : "high";
8894
- alerts.push({
8895
- pattern: "stablecoin_depeg",
8896
- severity,
8897
- asset,
8898
- pair,
8899
- price: round4(price),
8900
- threshold: stableThreshold,
8901
- action: `buy ${asset} at $${round4(price)}, wait for repeg`
8902
- });
8903
- }
8904
- }
8905
- }
8906
- } else {
8907
- for (const { asset, pair, price } of stablePrices) {
8908
- if (price < stableThreshold) {
8909
- const severity = price < 0.95 ? "critical" : "high";
8910
- alerts.push({
8911
- pattern: "stablecoin_depeg",
8912
- severity,
8913
- asset,
8914
- pair,
8915
- price: round4(price),
8916
- threshold: stableThreshold,
8917
- action: `buy ${asset} at $${round4(price)}, wait for repeg`
8918
- });
8919
- }
8920
- }
8921
- }
8922
- if (doOracle) {
8923
- for (const [token, oracleEntries] of oracleByToken) {
8924
- const dexPrice = dexByToken.get(token);
8925
- if (dexPrice === void 0) continue;
8926
- for (const { oracle, price: oraclePrice } of oracleEntries) {
8927
- if (dexPrice < oraclePrice && dexPrice < oraclePrice * 0.1) continue;
8928
- const deviation = Math.abs(dexPrice - oraclePrice) / oraclePrice * 100;
8929
- if (deviation > oracleThreshold) {
8930
- const severity = deviation > 100 ? "critical" : deviation > 20 ? "high" : "medium";
8931
- const action = dexPrice > oraclePrice ? `borrow ${token} from ${oracle}, sell on DEX` : `buy ${token} on DEX, use as collateral on ${oracle}`;
8932
- alerts.push({
8933
- pattern: "oracle_divergence",
8934
- severity,
8935
- asset: token,
8936
- oracle,
8937
- oracle_price: round4(oraclePrice),
8938
- dex_price: round4(dexPrice),
8939
- deviation_pct: round2(deviation),
8940
- action
8941
- });
8942
- }
8943
- }
8944
- }
8945
- }
8946
- const data = {};
8947
- if (Object.keys(oracleData).length > 0) data["oracle_prices"] = oracleData;
8948
- if (Object.keys(dexData).length > 0) data["dex_prices"] = dexData;
8949
- if (Object.keys(stableData).length > 0) data["stablecoin_pegs"] = stableData;
8950
- if (Object.keys(rateData).length > 0) data["exchange_rates"] = rateData;
8951
- const output = {
8952
- timestamp,
8953
- chain: chain.name,
8954
- scan_duration_ms: scanMs,
8955
- patterns,
8956
- alert_count: alerts.length,
8957
- alerts,
8958
- data
8959
- };
8960
- for (const alert of alerts) {
8961
- process.stderr.write(
8962
- `ALERT [${alert["severity"]}]: ${alert["pattern"]} \u2014 ${alert["action"]}
8963
- `
8964
- );
8965
- }
8966
- printOutput(output, getOpts());
8967
- };
8968
- await runOnce();
8969
- if (!once) {
8970
- const intervalMs = interval * 1e3;
8971
- const loop = async () => {
8972
- await new Promise((r) => setTimeout(r, intervalMs));
8973
- await runOnce();
8974
- void loop();
8975
- };
8976
- await loop();
8977
- }
8978
- } catch (err) {
8979
- printOutput({ error: String(err) }, getOpts());
8980
- process.exit(1);
8981
- }
8982
- });
8983
- }
8984
- async function runAllChains(registry, patterns, oracleThreshold, stableThreshold, _rateThreshold) {
8985
- const t0 = Date.now();
8986
- const chainKeys = Array.from(registry.chains.keys());
8987
- const tasks = chainKeys.map(async (ck) => {
8988
- try {
8989
- const chain = registry.getChain(ck);
8990
- const rpc = chain.effectiveRpcUrl();
8991
- const chainName = chain.name.toLowerCase();
8992
- const allTokens = registry.tokens.get(chainName) ?? [];
8993
- const wrappedNative = chain.wrapped_native;
8994
- const quoteStable = (() => {
8995
- for (const sym of ["USDT", "USDC", "USDT0"]) {
8996
- try {
8997
- return registry.resolveToken(chainName, sym);
8998
- } catch {
8999
- }
9000
- }
9001
- return null;
9002
- })();
9003
- if (!quoteStable) return null;
9004
- const scanTokens = allTokens.filter(
9005
- (t) => t.address !== "0x0000000000000000000000000000000000000000" && !STABLECOINS.has(t.symbol)
9006
- );
9007
- const pats = patterns.split(",").map((s) => s.trim());
9008
- const doOracle = pats.includes("oracle");
9009
- const doStable = pats.includes("stable");
9010
- const oracles = registry.getProtocolsForChain(chainName).filter(
9011
- (p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2" || p.interface === "aave_v3_isolated")
9012
- ).flatMap((p) => {
9013
- const oracleAddr = p.contracts?.["oracle"];
9014
- if (!oracleAddr) return [];
9015
- return [{ name: p.name, addr: oracleAddr, decimals: p.interface === "aave_v2" ? 18 : 8 }];
9016
- });
9017
- const dexProto = registry.getProtocolsForChain(chainName).find((p) => p.category === ProtocolCategory.Dex && p.interface === "uniswap_v2");
9018
- const dexRouter = dexProto?.contracts?.["router"];
9019
- const usdc = (() => {
9020
- try {
9021
- return registry.resolveToken(chainName, "USDC");
9022
- } catch {
9023
- return null;
9024
- }
9025
- })();
9026
- const usdt = (() => {
9027
- try {
9028
- return registry.resolveToken(chainName, "USDT");
9029
- } catch {
9030
- return null;
9031
- }
9032
- })();
9033
- const calls = [];
9034
- const cts = [];
9035
- if (doOracle) {
9036
- for (const oracle of oracles) {
9037
- for (const token of scanTokens) {
9038
- cts.push({ kind: "oracle", oracle: oracle.name, token: token.symbol, dec: oracle.decimals });
9039
- calls.push([oracle.addr, encodeFunctionData29({ abi: AAVE_ORACLE_ABI, functionName: "getAssetPrice", args: [token.address] })]);
9040
- }
9041
- }
9042
- if (dexRouter) {
9043
- for (const token of scanTokens) {
9044
- const path = wrappedNative && token.address.toLowerCase() === wrappedNative.toLowerCase() ? [token.address, quoteStable.address] : wrappedNative ? [token.address, wrappedNative, quoteStable.address] : [token.address, quoteStable.address];
9045
- cts.push({ kind: "dex", token: token.symbol, dec: quoteStable.decimals });
9046
- calls.push([dexRouter, encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [BigInt(10) ** BigInt(token.decimals), path] })]);
9047
- }
9048
- }
9049
- }
9050
- if (doStable && usdc && usdt && dexRouter) {
9051
- cts.push({ kind: "stable", from: "USDC", to: "USDT", dec: usdt.decimals });
9052
- calls.push([dexRouter, encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [BigInt(10) ** BigInt(usdc.decimals), [usdc.address, usdt.address]] })]);
9053
- cts.push({ kind: "stable", from: "USDT", to: "USDC", dec: usdc.decimals });
9054
- calls.push([dexRouter, encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [BigInt(10) ** BigInt(usdt.decimals), [usdt.address, usdc.address]] })]);
9055
- }
9056
- if (calls.length === 0) return null;
9057
- const ct0 = Date.now();
9058
- const results = await multicallRead(rpc, calls);
9059
- const scanMs = Date.now() - ct0;
9060
- const alerts = [];
9061
- const oracleByToken = /* @__PURE__ */ new Map();
9062
- const dexByToken = /* @__PURE__ */ new Map();
9063
- const stablePrices = [];
9064
- for (let i = 0; i < cts.length; i++) {
9065
- const ct = cts[i];
9066
- const raw = results[i] ?? null;
9067
- if (ct.kind === "oracle") {
9068
- const price = parseU256F64(raw, ct.dec);
9069
- if (price > 0) {
9070
- const existing = oracleByToken.get(ct.token) ?? [];
9071
- existing.push({ oracle: ct.oracle, price });
9072
- oracleByToken.set(ct.token, existing);
9073
- }
9074
- } else if (ct.kind === "dex") {
9075
- const price = parseAmountsOutLast(raw, ct.dec);
9076
- if (price > 0) dexByToken.set(ct.token, price);
9077
- } else if (ct.kind === "stable") {
9078
- const price = parseAmountsOutLast(raw, ct.dec);
9079
- if (price > 0) stablePrices.push({ asset: ct.from, pair: `${ct.from}/${ct.to}`, price });
9080
- }
9081
- }
9082
- if (stablePrices.length >= 2) {
9083
- const allBelow = stablePrices.every((s) => s.price < stableThreshold);
9084
- if (!allBelow) {
9085
- for (const { asset, pair, price } of stablePrices) {
9086
- if (price < stableThreshold) {
9087
- alerts.push({ pattern: "stablecoin_depeg", severity: price < 0.95 ? "critical" : "high", asset, pair, price: round4(price) });
9088
- }
9089
- }
9090
- }
9091
- }
9092
- for (const [token, oEntries] of oracleByToken) {
9093
- const dp = dexByToken.get(token);
9094
- if (dp === void 0) continue;
9095
- for (const { oracle, price: op } of oEntries) {
9096
- if (dp < op && dp < op * 0.1) continue;
9097
- const dev = Math.abs(dp - op) / op * 100;
9098
- if (dev > oracleThreshold) {
9099
- const sev = dev > 100 ? "critical" : dev > 20 ? "high" : "medium";
9100
- alerts.push({
9101
- pattern: "oracle_divergence",
9102
- severity: sev,
9103
- asset: token,
9104
- oracle,
9105
- oracle_price: round4(op),
9106
- dex_price: round4(dp),
9107
- deviation_pct: round2(dev),
9108
- action: dp > op ? `borrow ${token} from ${oracle}, sell on DEX` : `buy ${token} on DEX, collateral on ${oracle}`
9109
- });
9110
- }
9111
- }
9112
- }
9113
- return { chain: chain.name, scan_duration_ms: scanMs, alert_count: alerts.length, alerts };
9114
- } catch {
9115
- return null;
9116
- }
9117
- });
9118
- const chainResults = (await Promise.all(tasks)).filter(Boolean);
9119
- chainResults.sort((a, b) => {
9120
- const ac = a["alert_count"] ?? 0;
9121
- const bc = b["alert_count"] ?? 0;
9122
- return bc - ac;
9123
- });
9124
- const totalAlerts = chainResults.reduce((sum, r) => sum + (r["alert_count"] ?? 0), 0);
9125
- return {
9126
- mode: "all_chains",
9127
- chains_scanned: chainKeys.length,
9128
- scan_duration_ms: Date.now() - t0,
9129
- total_alerts: totalAlerts,
9130
- chains: chainResults
9131
- };
9132
- }
9133
-
9134
- // src/commands/positions.ts
9135
- import { encodeFunctionData as encodeFunctionData30, parseAbi as parseAbi34 } from "viem";
9136
- var ERC20_ABI6 = parseAbi34([
9137
- "function balanceOf(address owner) external view returns (uint256)"
9138
- ]);
9139
- var POOL_ABI5 = parseAbi34([
9140
- "function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)"
9141
- ]);
9142
- var ORACLE_ABI6 = parseAbi34([
9143
- "function getAssetPrice(address asset) external view returns (uint256)"
9144
- ]);
9145
- function round22(x) {
9146
- return Math.round(x * 100) / 100;
9147
- }
9148
- function round42(x) {
9149
- return Math.round(x * 1e4) / 1e4;
9150
- }
9151
- function estimateTokenValue(symbol, balance, nativePrice) {
9152
- const s = symbol.toUpperCase();
9153
- if (s.includes("USD") || s.includes("DAI")) return balance;
9154
- if (s.includes("BTC") || s.includes("FBTC")) return balance * 75e3;
9155
- if (["WETH", "ETH", "METH", "CBETH", "WSTETH"].includes(s)) return balance * 2350;
9156
- return balance * nativePrice;
9157
- }
9158
- function decodeU2563(data, offset = 0) {
9159
- if (!data || data.length < 2 + (offset + 32) * 2) return 0n;
9160
- const hex = data.slice(2 + offset * 64, 2 + offset * 64 + 64);
9161
- return BigInt("0x" + hex);
9162
- }
9163
- async function scanSingleChain(chainName, rpc, user, tokens, lendingPools, oracleAddr, wrappedNative) {
9164
- const calls = [];
9165
- const callTypes = [];
9166
- for (const token of tokens) {
9167
- if (token.address !== "0x0000000000000000000000000000000000000000") {
9168
- callTypes.push({ kind: "token", symbol: token.symbol, decimals: token.decimals });
9169
- calls.push([
9170
- token.address,
9171
- encodeFunctionData30({ abi: ERC20_ABI6, functionName: "balanceOf", args: [user] })
9172
- ]);
9173
- }
9174
- }
9175
- for (const { name, pool, iface } of lendingPools) {
9176
- callTypes.push({ kind: "lending", protocol: name, iface });
9177
- calls.push([
9178
- pool,
9179
- encodeFunctionData30({ abi: POOL_ABI5, functionName: "getUserAccountData", args: [user] })
9180
- ]);
9181
- }
9182
- if (oracleAddr) {
9183
- callTypes.push({ kind: "native_price" });
9184
- calls.push([
9185
- oracleAddr,
9186
- encodeFunctionData30({ abi: ORACLE_ABI6, functionName: "getAssetPrice", args: [wrappedNative] })
9187
- ]);
9188
- }
9189
- if (calls.length === 0) return null;
9190
- let results;
9191
- try {
9192
- results = await multicallRead(rpc, calls);
9193
- } catch {
9194
- return null;
9195
- }
9196
- const nativePrice = oracleAddr ? Number(decodeU2563(results[results.length - 1])) / 1e8 : 0;
9197
- const tokenBalances = [];
9198
- const lendingPositions = [];
9199
- let chainValue = 0;
9200
- let totalColl = 0;
9201
- let totalDebt = 0;
9202
- for (let i = 0; i < callTypes.length; i++) {
9203
- const ct = callTypes[i];
9204
- const data = results[i] ?? null;
9205
- if (ct.kind === "token") {
9206
- const balance = decodeU2563(data);
9207
- if (balance > 0n) {
9208
- const balF64 = Number(balance) / 10 ** ct.decimals;
9209
- const valueUsd = estimateTokenValue(ct.symbol, balF64, nativePrice);
9210
- if (valueUsd > 0.01) {
9211
- chainValue += valueUsd;
9212
- tokenBalances.push({
9213
- symbol: ct.symbol,
9214
- balance: round42(balF64),
9215
- value_usd: round22(valueUsd)
9216
- });
9217
- }
9218
- }
9219
- } else if (ct.kind === "lending") {
9220
- if (data && data.length >= 2 + 192 * 2) {
9221
- const priceDecimals = ct.iface === "aave_v2" ? 18 : 8;
9222
- const divisor = 10 ** priceDecimals;
9223
- const collateral = Number(decodeU2563(data, 0)) / divisor;
9224
- const debt = Number(decodeU2563(data, 1)) / divisor;
9225
- const hfRaw = decodeU2563(data, 5);
9226
- let hf = null;
9227
- if (hfRaw <= BigInt("0xffffffffffffffffffffffffffffffff")) {
9228
- const v = Number(hfRaw) / 1e18;
9229
- hf = v > 1e10 ? null : round22(v);
9230
- }
9231
- if (collateral > 0.01 || debt > 0.01) {
9232
- const net = collateral - debt;
9233
- chainValue += net;
9234
- totalColl += collateral;
9235
- totalDebt += debt;
9236
- lendingPositions.push({
9237
- protocol: ct.protocol,
9238
- collateral_usd: round22(collateral),
9239
- debt_usd: round22(debt),
9240
- net_usd: round22(net),
9241
- health_factor: hf
9242
- });
9243
- }
9244
- }
9245
- }
9246
- }
9247
- if (tokenBalances.length === 0 && lendingPositions.length === 0) return null;
9248
- return {
9249
- chain_name: chainName,
9250
- native_price: nativePrice,
9251
- chain_value: chainValue,
9252
- collateral: totalColl,
9253
- debt: totalDebt,
9254
- token_balances: tokenBalances,
9255
- lending_positions: lendingPositions
9256
- };
9257
- }
9258
- function registerPositions(parent, getOpts) {
9259
- parent.command("positions").description("Cross-chain position scanner: find all your positions everywhere").requiredOption("--address <address>", "Wallet address to scan").option("--chains <chains>", "Comma-separated chain names (omit for all)").action(async (opts) => {
9260
- const mode = getOpts();
9261
- const registry = Registry.loadEmbedded();
9262
- const user = opts.address;
9263
- if (!/^0x[0-9a-fA-F]{40}$/.test(user)) {
9264
- printOutput({ error: `Invalid address: ${opts.address}` }, mode);
9265
- return;
9266
- }
9267
- const chainFilter = opts.chains ? opts.chains.split(",").map((s) => s.trim().toLowerCase()) : null;
9268
- const chainKeys = chainFilter ?? Array.from(registry.chains.keys());
9269
- const start = Date.now();
9270
- const scanParams = [];
9271
- for (const chainKey of chainKeys) {
9272
- let chain;
9273
- try {
9274
- chain = registry.getChain(chainKey);
9275
- } catch {
9276
- continue;
9277
- }
9278
- const rpc = chain.effectiveRpcUrl();
9279
- const rawTokens = registry.tokens.get(chainKey) ?? [];
9280
- const tokens = rawTokens.map((t) => ({
9281
- address: t.address,
9282
- symbol: t.symbol,
9283
- decimals: t.decimals
9284
- }));
9285
- const chainProtocols = registry.getProtocolsForChain(chainKey);
9286
- const lendingPools = chainProtocols.filter(
9287
- (p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2")
9288
- ).filter((p) => p.contracts?.["pool"]).map((p) => ({
9289
- name: p.name,
9290
- pool: p.contracts["pool"],
9291
- iface: p.interface
9292
- }));
9293
- const oracleEntry = chainProtocols.find(
9294
- (p) => p.interface === "aave_v3" && p.contracts?.["oracle"]
9295
- );
9296
- const oracleAddr = oracleEntry?.contracts?.["oracle"];
9297
- const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
9298
- scanParams.push({ chainName: chain.name, rpc, tokens, lendingPools, oracleAddr, wrappedNative });
9299
- }
9300
- const chainResultsRaw = await Promise.all(
9301
- scanParams.map(
9302
- (p) => scanSingleChain(p.chainName, p.rpc, user, p.tokens, p.lendingPools, p.oracleAddr, p.wrappedNative)
9303
- )
9304
- );
9305
- let grandTotalUsd = 0;
9306
- let totalCollateralUsd = 0;
9307
- let totalDebtUsd = 0;
9308
- const chainResults = chainResultsRaw.filter((r) => r !== null).map((r) => {
9309
- grandTotalUsd += r.chain_value;
9310
- totalCollateralUsd += r.collateral;
9311
- totalDebtUsd += r.debt;
9312
- return {
9313
- chain: r.chain_name,
9314
- native_price_usd: round22(r.native_price),
9315
- chain_total_usd: round22(r.chain_value),
9316
- token_balances: r.token_balances,
9317
- lending_positions: r.lending_positions
9318
- };
9319
- }).sort((a, b) => b.chain_total_usd - a.chain_total_usd);
9320
- const scanMs = Date.now() - start;
9321
- printOutput(
9322
- {
9323
- address: user,
9324
- scan_duration_ms: scanMs,
9325
- chains_scanned: chainKeys.length,
9326
- chains_with_positions: chainResults.length,
9327
- summary: {
9328
- total_value_usd: round22(grandTotalUsd),
9329
- total_collateral_usd: round22(totalCollateralUsd),
9330
- total_debt_usd: round22(totalDebtUsd),
9331
- net_lending_usd: round22(totalCollateralUsd - totalDebtUsd)
9332
- },
9333
- chains: chainResults
9334
- },
9335
- mode
9336
- );
9337
- });
9338
- }
9339
-
9340
7995
  // src/commands/price.ts
9341
- function round23(x) {
7996
+ function round2(x) {
9342
7997
  return Math.round(x * 100) / 100;
9343
7998
  }
9344
7999
  function resolveAsset2(registry, chain, asset) {
@@ -9353,7 +8008,12 @@ function registerPrice(parent, getOpts) {
9353
8008
  parent.command("price").description("Query asset prices from oracles and DEXes").requiredOption("--asset <token>", "Token symbol or address").option("--source <source>", "Price source: oracle, dex, or all", "all").action(async (opts) => {
9354
8009
  const mode = getOpts();
9355
8010
  const registry = Registry.loadEmbedded();
9356
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
8011
+ const _chain = parent.opts().chain;
8012
+ if (!_chain) {
8013
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8014
+ return;
8015
+ }
8016
+ const chainName = _chain.toLowerCase();
9357
8017
  let chain;
9358
8018
  try {
9359
8019
  chain = registry.getChain(chainName);
@@ -9466,10 +8126,10 @@ function registerPrice(parent, getOpts) {
9466
8126
  prices: allPrices.map((p) => ({
9467
8127
  source: p.source,
9468
8128
  source_type: p.source_type,
9469
- price: round23(p.price_f64)
8129
+ price: round2(p.price_f64)
9470
8130
  })),
9471
- max_spread_pct: round23(maxSpreadPct),
9472
- oracle_vs_dex_spread_pct: round23(oracleVsDexSpreadPct)
8131
+ max_spread_pct: round2(maxSpreadPct),
8132
+ oracle_vs_dex_spread_pct: round2(oracleVsDexSpreadPct)
9473
8133
  };
9474
8134
  printOutput(report, mode);
9475
8135
  });
@@ -9480,7 +8140,11 @@ import { createPublicClient as createPublicClient23, http as http23, formatEther
9480
8140
  function registerWallet(parent, getOpts) {
9481
8141
  const wallet = parent.command("wallet").description("Wallet management");
9482
8142
  wallet.command("balance").description("Show native token balance").requiredOption("--address <address>", "Wallet address to query").action(async (opts) => {
9483
- const chainName = parent.opts().chain ?? "hyperevm";
8143
+ const chainName = parent.opts().chain;
8144
+ if (!chainName) {
8145
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8146
+ return;
8147
+ }
9484
8148
  const registry = Registry.loadEmbedded();
9485
8149
  const chain = registry.getChain(chainName);
9486
8150
  const client = createPublicClient23({ transport: http23(chain.effectiveRpcUrl()) });
@@ -9504,7 +8168,11 @@ import { createPublicClient as createPublicClient24, http as http24, maxUint256
9504
8168
  function registerToken(parent, getOpts, makeExecutor2) {
9505
8169
  const token = parent.command("token").description("Token operations: approve, allowance, transfer, balance");
9506
8170
  token.command("balance").description("Query token balance for an address").requiredOption("--token <token>", "Token symbol or address").requiredOption("--owner <address>", "Wallet address to query").action(async (opts) => {
9507
- const chainName = parent.opts().chain ?? "hyperevm";
8171
+ const chainName = parent.opts().chain;
8172
+ if (!chainName) {
8173
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8174
+ return;
8175
+ }
9508
8176
  const registry = Registry.loadEmbedded();
9509
8177
  const chain = registry.getChain(chainName);
9510
8178
  const client = createPublicClient24({ transport: http24(chain.effectiveRpcUrl()) });
@@ -9524,7 +8192,11 @@ function registerToken(parent, getOpts, makeExecutor2) {
9524
8192
  });
9525
8193
  token.command("approve").description("Approve a spender for a token").requiredOption("--token <token>", "Token symbol or address").requiredOption("--spender <address>", "Spender address").option("--amount <amount>", "Amount to approve (use 'max' for unlimited)", "max").action(async (opts) => {
9526
8194
  const executor = makeExecutor2();
9527
- const chainName = parent.opts().chain ?? "hyperevm";
8195
+ const chainName = parent.opts().chain;
8196
+ if (!chainName) {
8197
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8198
+ return;
8199
+ }
9528
8200
  const registry = Registry.loadEmbedded();
9529
8201
  const tokenAddr = opts.token.startsWith("0x") ? opts.token : registry.resolveToken(chainName, opts.token).address;
9530
8202
  const amount = opts.amount === "max" ? maxUint256 : BigInt(opts.amount);
@@ -9533,7 +8205,11 @@ function registerToken(parent, getOpts, makeExecutor2) {
9533
8205
  printOutput(result, getOpts());
9534
8206
  });
9535
8207
  token.command("allowance").description("Check token allowance").requiredOption("--token <token>", "Token symbol or address").requiredOption("--owner <address>", "Owner address").requiredOption("--spender <address>", "Spender address").action(async (opts) => {
9536
- const chainName = parent.opts().chain ?? "hyperevm";
8208
+ const chainName = parent.opts().chain;
8209
+ if (!chainName) {
8210
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8211
+ return;
8212
+ }
9537
8213
  const registry = Registry.loadEmbedded();
9538
8214
  const chain = registry.getChain(chainName);
9539
8215
  const client = createPublicClient24({ transport: http24(chain.effectiveRpcUrl()) });
@@ -9548,7 +8224,11 @@ function registerToken(parent, getOpts, makeExecutor2) {
9548
8224
  });
9549
8225
  token.command("transfer").description("Transfer tokens to an address").requiredOption("--token <token>", "Token symbol or address").requiredOption("--to <address>", "Recipient address").requiredOption("--amount <amount>", "Amount to transfer (in wei)").action(async (opts) => {
9550
8226
  const executor = makeExecutor2();
9551
- const chainName = parent.opts().chain ?? "hyperevm";
8227
+ const chainName = parent.opts().chain;
8228
+ if (!chainName) {
8229
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8230
+ return;
8231
+ }
9552
8232
  const registry = Registry.loadEmbedded();
9553
8233
  const tokenAddr = opts.token.startsWith("0x") ? opts.token : registry.resolveToken(chainName, opts.token).address;
9554
8234
  const tx = buildTransfer(tokenAddr, opts.to, BigInt(opts.amount));
@@ -9557,192 +8237,6 @@ function registerToken(parent, getOpts, makeExecutor2) {
9557
8237
  });
9558
8238
  }
9559
8239
 
9560
- // src/commands/whales.ts
9561
- import { encodeFunctionData as encodeFunctionData31, parseAbi as parseAbi35 } from "viem";
9562
- var POOL_ABI6 = parseAbi35([
9563
- "function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)"
9564
- ]);
9565
- function round24(x) {
9566
- return Math.round(x * 100) / 100;
9567
- }
9568
- function round43(x) {
9569
- return Math.round(x * 1e4) / 1e4;
9570
- }
9571
- function decodeU2564(data, wordOffset = 0) {
9572
- if (!data || data.length < 2 + (wordOffset + 1) * 64) return 0n;
9573
- const hex = data.slice(2 + wordOffset * 64, 2 + wordOffset * 64 + 64);
9574
- return BigInt("0x" + hex);
9575
- }
9576
- function getExplorerApi(chainId, explorerUrl) {
9577
- const routescanChains = [1, 43114, 10, 5e3];
9578
- if (routescanChains.includes(chainId)) {
9579
- return {
9580
- base: `https://api.routescan.io/v2/network/mainnet/evm/${chainId}/etherscan/api`
9581
- };
9582
- }
9583
- const apiKey = process.env["ETHERSCAN_API_KEY"];
9584
- if (apiKey) {
9585
- return {
9586
- base: `https://api.etherscan.io/v2/api?chainid=${chainId}`,
9587
- apiKey
9588
- };
9589
- }
9590
- return null;
9591
- }
9592
- function registerWhales(parent, getOpts) {
9593
- parent.command("whales").description("Find top token holders (whales) and their positions").requiredOption("--token <token>", "Token symbol or address").option("--top <n>", "Number of top holders to show", "10").option("--positions", "Also scan each whale's lending positions").action(async (opts) => {
9594
- const mode = getOpts();
9595
- const registry = Registry.loadEmbedded();
9596
- const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
9597
- let chain;
9598
- try {
9599
- chain = registry.getChain(chainName);
9600
- } catch {
9601
- printOutput({ error: `Chain not found: ${chainName}` }, mode);
9602
- return;
9603
- }
9604
- const rpc = chain.effectiveRpcUrl();
9605
- const top = parseInt(opts.top, 10) || 10;
9606
- let token;
9607
- try {
9608
- token = registry.resolveToken(chainName, opts.token);
9609
- } catch {
9610
- printOutput({ error: `Token not found: ${opts.token}` }, mode);
9611
- return;
9612
- }
9613
- const explorerApi = getExplorerApi(chain.chain_id, chain.explorer_url);
9614
- if (!explorerApi) {
9615
- printOutput(
9616
- {
9617
- error: `No explorer API available for ${chain.name} (chain_id: ${chain.chain_id}). Set ETHERSCAN_API_KEY to enable.`
9618
- },
9619
- mode
9620
- );
9621
- return;
9622
- }
9623
- const tokenAddr = token.address;
9624
- let url = `${explorerApi.base}?module=token&action=tokenholderlist&contractaddress=${tokenAddr}&page=1&offset=${top}`;
9625
- if (explorerApi.apiKey) {
9626
- url += `&apikey=${explorerApi.apiKey}`;
9627
- }
9628
- let body;
9629
- try {
9630
- const resp = await fetch(url);
9631
- body = await resp.json();
9632
- } catch (e) {
9633
- printOutput({ error: `Explorer API request failed: ${e instanceof Error ? e.message : String(e)}` }, mode);
9634
- return;
9635
- }
9636
- if (body.status !== "1") {
9637
- const msg = typeof body.result === "string" ? body.result : "Unknown error";
9638
- if (msg.includes("API Key") || msg.includes("apikey")) {
9639
- printOutput(
9640
- { error: "Explorer API requires API key. Set ETHERSCAN_API_KEY environment variable." },
9641
- mode
9642
- );
9643
- return;
9644
- }
9645
- printOutput({ error: `Explorer API error: ${msg}` }, mode);
9646
- return;
9647
- }
9648
- const holders = Array.isArray(body.result) ? body.result : [];
9649
- const whaleList = [];
9650
- for (const h of holders) {
9651
- const addrStr = h["TokenHolderAddress"] ?? "";
9652
- const qtyStr = h["TokenHolderQuantity"] ?? "0";
9653
- if (/^0x[0-9a-fA-F]{40}$/.test(addrStr)) {
9654
- const raw = BigInt(qtyStr || "0");
9655
- const balance = Number(raw) / 10 ** token.decimals;
9656
- whaleList.push({ address: addrStr, balance });
9657
- }
9658
- }
9659
- const whaleData = [];
9660
- if (opts.positions && whaleList.length > 0) {
9661
- const lendingPools = registry.getProtocolsForChain(chainName).filter(
9662
- (p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2")
9663
- ).filter((p) => p.contracts?.["pool"]).map((p) => ({
9664
- name: p.name,
9665
- pool: p.contracts["pool"],
9666
- iface: p.interface
9667
- }));
9668
- const calls = [];
9669
- for (const whale of whaleList) {
9670
- for (const { pool } of lendingPools) {
9671
- calls.push([
9672
- pool,
9673
- encodeFunctionData31({ abi: POOL_ABI6, functionName: "getUserAccountData", args: [whale.address] })
9674
- ]);
9675
- }
9676
- }
9677
- let results = [];
9678
- if (calls.length > 0) {
9679
- try {
9680
- results = await multicallRead(rpc, calls);
9681
- } catch {
9682
- results = [];
9683
- }
9684
- }
9685
- const poolsPerWhale = lendingPools.length;
9686
- for (let wi = 0; wi < whaleList.length; wi++) {
9687
- const whale = whaleList[wi];
9688
- const positions = [];
9689
- for (let pi = 0; pi < lendingPools.length; pi++) {
9690
- const { name: protoName, iface } = lendingPools[pi];
9691
- const idx = wi * poolsPerWhale + pi;
9692
- const data = results[idx] ?? null;
9693
- if (data && data.length >= 2 + 192 * 2) {
9694
- const dec = iface === "aave_v2" ? 18 : 8;
9695
- const divisor = 10 ** dec;
9696
- const collateral = Number(decodeU2564(data, 0)) / divisor;
9697
- const debt = Number(decodeU2564(data, 1)) / divisor;
9698
- const hfRaw = decodeU2564(data, 5);
9699
- let hf = null;
9700
- if (hfRaw <= BigInt("0xffffffffffffffffffffffffffffffff")) {
9701
- const v = Number(hfRaw) / 1e18;
9702
- hf = v > 1e10 ? null : round24(v);
9703
- }
9704
- if (collateral > 0.01 || debt > 0.01) {
9705
- positions.push({
9706
- protocol: protoName,
9707
- collateral_usd: round24(collateral),
9708
- debt_usd: round24(debt),
9709
- health_factor: hf
9710
- });
9711
- }
9712
- }
9713
- }
9714
- whaleData.push({
9715
- rank: wi + 1,
9716
- address: whale.address,
9717
- balance: round43(whale.balance),
9718
- positions
9719
- });
9720
- }
9721
- } else {
9722
- for (let wi = 0; wi < whaleList.length; wi++) {
9723
- const whale = whaleList[wi];
9724
- whaleData.push({
9725
- rank: wi + 1,
9726
- address: whale.address,
9727
- balance: round43(whale.balance)
9728
- });
9729
- }
9730
- }
9731
- printOutput(
9732
- {
9733
- chain: chain.name,
9734
- token: opts.token,
9735
- token_address: tokenAddr,
9736
- decimals: token.decimals,
9737
- top,
9738
- holders: whaleData,
9739
- explorer: chain.explorer_url ?? ""
9740
- },
9741
- mode
9742
- );
9743
- });
9744
- }
9745
-
9746
8240
  // src/commands/bridge.ts
9747
8241
  var LIFI_API = "https://li.quest/v1";
9748
8242
  var DLN_API = "https://dln.debridge.finance/v1.0/dln/order";
@@ -9841,7 +8335,11 @@ async function getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc) {
9841
8335
  }
9842
8336
  function registerBridge(parent, getOpts) {
9843
8337
  parent.command("bridge").description("Cross-chain bridge: move assets between chains").requiredOption("--token <token>", "Token symbol or address").requiredOption("--amount <amount>", "Amount in wei").requiredOption("--to-chain <chain>", "Destination chain name").option("--recipient <address>", "Recipient address on destination chain").option("--slippage <bps>", "Slippage in bps (LI.FI only)", "50").option("--provider <name>", "Bridge provider: lifi, debridge, cctp", "lifi").action(async (opts) => {
9844
- const chainName = parent.opts().chain ?? "hyperevm";
8338
+ const chainName = parent.opts().chain;
8339
+ if (!chainName) {
8340
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8341
+ return;
8342
+ }
9845
8343
  const registry = Registry.loadEmbedded();
9846
8344
  const fromChain = registry.getChain(chainName);
9847
8345
  const toChain = registry.getChain(opts.toChain);
@@ -9897,11 +8395,11 @@ function registerBridge(parent, getOpts) {
9897
8395
  const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
9898
8396
  const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
9899
8397
  const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
9900
- const { encodeFunctionData: encodeFunctionData33, parseAbi: parseAbi36 } = await import("viem");
9901
- const tokenMessengerAbi = parseAbi36([
8398
+ const { encodeFunctionData: encodeFunctionData29, parseAbi: parseAbi33 } = await import("viem");
8399
+ const tokenMessengerAbi = parseAbi33([
9902
8400
  "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) external returns (uint64 nonce)"
9903
8401
  ]);
9904
- const data = encodeFunctionData33({
8402
+ const data = encodeFunctionData29({
9905
8403
  abi: tokenMessengerAbi,
9906
8404
  functionName: "depositForBurn",
9907
8405
  args: [
@@ -10045,7 +8543,11 @@ async function liquidSwapRoute(tokenIn, tokenOut, amountIn, slippagePct) {
10045
8543
  function registerSwap(parent, getOpts, makeExecutor2) {
10046
8544
  parent.command("swap").description("Swap tokens via DEX aggregator (KyberSwap, OpenOcean, LiquidSwap)").requiredOption("--from <token>", "Input token symbol or address").requiredOption("--to <token>", "Output token symbol or address").requiredOption("--amount <amount>", "Amount of input token in wei").option("--provider <name>", "Aggregator: kyber, openocean, liquid", "kyber").option("--slippage <bps>", "Slippage tolerance in bps", "50").action(async (opts) => {
10047
8545
  const executor = makeExecutor2();
10048
- const chainName = parent.opts().chain ?? "hyperevm";
8546
+ const chainName = parent.opts().chain;
8547
+ if (!chainName) {
8548
+ printOutput({ error: "--chain is required (e.g. --chain hyperevm)" }, getOpts());
8549
+ return;
8550
+ }
10049
8551
  const registry = Registry.loadEmbedded();
10050
8552
  const provider = opts.provider.toLowerCase();
10051
8553
  const slippageBps = parseInt(opts.slippage, 10);
@@ -10174,16 +8676,16 @@ function registerSwap(parent, getOpts, makeExecutor2) {
10174
8676
  // src/commands/setup.ts
10175
8677
  import pc2 from "picocolors";
10176
8678
  import { createInterface } from "readline";
10177
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
10178
- import { resolve as resolve3 } from "path";
10179
- var DEFI_DIR = resolve3(process.env.HOME || "~", ".defi");
10180
- var ENV_FILE = resolve3(DEFI_DIR, ".env");
8679
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
8680
+ import { resolve as resolve4 } from "path";
8681
+ var DEFI_DIR = resolve4(process.env.HOME || "~", ".defi");
8682
+ var ENV_FILE = resolve4(DEFI_DIR, ".env");
10181
8683
  function ensureDefiDir() {
10182
8684
  if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
10183
8685
  }
10184
8686
  function loadEnvFile() {
10185
8687
  if (!existsSync3(ENV_FILE)) return {};
10186
- const lines = readFileSync3(ENV_FILE, "utf-8").split("\n");
8688
+ const lines = readFileSync4(ENV_FILE, "utf-8").split("\n");
10187
8689
  const env = {};
10188
8690
  for (const line of lines) {
10189
8691
  const trimmed = line.trim();
@@ -10328,10 +8830,10 @@ var BANNER = `
10328
8830
 
10329
8831
  2 chains \xB7 21 protocols \xB7 by HypurrQuant
10330
8832
 
10331
- Lending, LP provision, farming, gauges, vaults,
10332
- yield comparison \u2014 all from your terminal.
8833
+ Lending, LP farming, DEX swap, yield comparison
8834
+ \u2014 all from your terminal.
10333
8835
  `;
10334
- var program = new Command().name("defi").description("DeFi CLI \u2014 Multi-chain DeFi toolkit").version(_pkg.version).addHelpText("before", BANNER).option("--json", "Output as JSON").option("--ndjson", "Output as newline-delimited JSON").option("--fields <fields>", "Select specific output fields (comma-separated)").option("--chain <chain>", "Target chain", "hyperevm").option("--dry-run", "Dry-run mode (default, no broadcast)", true).option("--broadcast", "Actually broadcast the transaction");
8836
+ var program = new Command().name("defi").description("DeFi CLI \u2014 Multi-chain DeFi toolkit").version(_pkg.version).addHelpText("before", BANNER).option("--json", "Output as JSON").option("--ndjson", "Output as newline-delimited JSON").option("--fields <fields>", "Select specific output fields (comma-separated)").option("--chain <chain>", "Target chain").option("--dry-run", "Dry-run mode (default, no broadcast)", true).option("--broadcast", "Actually broadcast the transaction");
10335
8837
  function getOutputMode() {
10336
8838
  const opts = program.opts();
10337
8839
  return parseOutputMode(opts);
@@ -10339,68 +8841,26 @@ function getOutputMode() {
10339
8841
  function makeExecutor() {
10340
8842
  const opts = program.opts();
10341
8843
  const registry = Registry.loadEmbedded();
10342
- const chain = registry.getChain(opts.chain ?? "hyperevm");
8844
+ if (!opts.chain) {
8845
+ process.stderr.write("Error: --chain is required for this command (e.g. --chain hyperevm)\n");
8846
+ process.exit(1);
8847
+ }
8848
+ const chain = registry.getChain(opts.chain);
10343
8849
  return new Executor(!!opts.broadcast, chain.effectiveRpcUrl(), chain.explorer_url);
10344
8850
  }
10345
8851
  registerStatus(program, getOutputMode);
10346
8852
  registerSchema(program, getOutputMode);
10347
8853
  registerLP(program, getOutputMode, makeExecutor);
10348
8854
  registerLending(program, getOutputMode, makeExecutor);
10349
- registerCdp(program, getOutputMode, makeExecutor);
10350
- registerVault(program, getOutputMode, makeExecutor);
10351
8855
  registerYield(program, getOutputMode, makeExecutor);
10352
8856
  registerPortfolio(program, getOutputMode);
10353
- registerMonitor(program, getOutputMode);
10354
- registerAlert(program, getOutputMode);
10355
- registerScan(program, getOutputMode);
10356
- registerPositions(program, getOutputMode);
10357
8857
  registerPrice(program, getOutputMode);
10358
8858
  registerWallet(program, getOutputMode);
10359
8859
  registerToken(program, getOutputMode, makeExecutor);
10360
- registerWhales(program, getOutputMode);
10361
8860
  registerBridge(program, getOutputMode);
10362
8861
  registerSwap(program, getOutputMode, makeExecutor);
10363
8862
  registerSetup(program);
10364
8863
  export {
10365
8864
  program
10366
8865
  };
10367
- /*! Bundled license information:
10368
-
10369
- smol-toml/dist/error.js:
10370
- smol-toml/dist/util.js:
10371
- smol-toml/dist/date.js:
10372
- smol-toml/dist/primitive.js:
10373
- smol-toml/dist/extract.js:
10374
- smol-toml/dist/struct.js:
10375
- smol-toml/dist/parse.js:
10376
- smol-toml/dist/stringify.js:
10377
- smol-toml/dist/index.js:
10378
- (*!
10379
- * Copyright (c) Squirrel Chat et al., All rights reserved.
10380
- * SPDX-License-Identifier: BSD-3-Clause
10381
- *
10382
- * Redistribution and use in source and binary forms, with or without
10383
- * modification, are permitted provided that the following conditions are met:
10384
- *
10385
- * 1. Redistributions of source code must retain the above copyright notice, this
10386
- * list of conditions and the following disclaimer.
10387
- * 2. Redistributions in binary form must reproduce the above copyright notice,
10388
- * this list of conditions and the following disclaimer in the
10389
- * documentation and/or other materials provided with the distribution.
10390
- * 3. Neither the name of the copyright holder nor the names of its contributors
10391
- * may be used to endorse or promote products derived from this software without
10392
- * specific prior written permission.
10393
- *
10394
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
10395
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10396
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
10397
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
10398
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
10399
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
10400
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
10401
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
10402
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
10403
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10404
- *)
10405
- */
10406
8866
  //# sourceMappingURL=index.js.map