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