@hypurrquant/defi-cli 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -199
- package/config/pools.example.toml +31 -0
- package/dist/index.js +297 -2082
- package/dist/index.js.map +1 -1
- package/dist/main.js +303 -2093
- package/dist/main.js.map +1 -1
- package/dist/mcp-server.js +318 -844
- package/dist/mcp-server.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -15,693 +15,7 @@ import { encodeFunctionData as encodeFunctionData2, decodeFunctionResult, parseA
|
|
|
15
15
|
import { readFileSync, readdirSync } from "fs";
|
|
16
16
|
import { resolve } from "path";
|
|
17
17
|
import { fileURLToPath } from "url";
|
|
18
|
-
|
|
19
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
|
|
20
|
-
function getLineColFromPtr(string, ptr) {
|
|
21
|
-
let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
|
|
22
|
-
return [lines.length, lines.pop().length + 1];
|
|
23
|
-
}
|
|
24
|
-
function makeCodeBlock(string, line, column) {
|
|
25
|
-
let lines = string.split(/\r\n|\n|\r/g);
|
|
26
|
-
let codeblock = "";
|
|
27
|
-
let numberLen = (Math.log10(line + 1) | 0) + 1;
|
|
28
|
-
for (let i = line - 1; i <= line + 1; i++) {
|
|
29
|
-
let l = lines[i - 1];
|
|
30
|
-
if (!l)
|
|
31
|
-
continue;
|
|
32
|
-
codeblock += i.toString().padEnd(numberLen, " ");
|
|
33
|
-
codeblock += ": ";
|
|
34
|
-
codeblock += l;
|
|
35
|
-
codeblock += "\n";
|
|
36
|
-
if (i === line) {
|
|
37
|
-
codeblock += " ".repeat(numberLen + column + 2);
|
|
38
|
-
codeblock += "^\n";
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return codeblock;
|
|
42
|
-
}
|
|
43
|
-
var TomlError = class extends Error {
|
|
44
|
-
line;
|
|
45
|
-
column;
|
|
46
|
-
codeblock;
|
|
47
|
-
constructor(message, options) {
|
|
48
|
-
const [line, column] = getLineColFromPtr(options.toml, options.ptr);
|
|
49
|
-
const codeblock = makeCodeBlock(options.toml, line, column);
|
|
50
|
-
super(`Invalid TOML document: ${message}
|
|
51
|
-
|
|
52
|
-
${codeblock}`, options);
|
|
53
|
-
this.line = line;
|
|
54
|
-
this.column = column;
|
|
55
|
-
this.codeblock = codeblock;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/util.js
|
|
60
|
-
function isEscaped(str, ptr) {
|
|
61
|
-
let i = 0;
|
|
62
|
-
while (str[ptr - ++i] === "\\")
|
|
63
|
-
;
|
|
64
|
-
return --i && i % 2;
|
|
65
|
-
}
|
|
66
|
-
function indexOfNewline(str, start = 0, end = str.length) {
|
|
67
|
-
let idx = str.indexOf("\n", start);
|
|
68
|
-
if (str[idx - 1] === "\r")
|
|
69
|
-
idx--;
|
|
70
|
-
return idx <= end ? idx : -1;
|
|
71
|
-
}
|
|
72
|
-
function skipComment(str, ptr) {
|
|
73
|
-
for (let i = ptr; i < str.length; i++) {
|
|
74
|
-
let c = str[i];
|
|
75
|
-
if (c === "\n")
|
|
76
|
-
return i;
|
|
77
|
-
if (c === "\r" && str[i + 1] === "\n")
|
|
78
|
-
return i + 1;
|
|
79
|
-
if (c < " " && c !== " " || c === "\x7F") {
|
|
80
|
-
throw new TomlError("control characters are not allowed in comments", {
|
|
81
|
-
toml: str,
|
|
82
|
-
ptr
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return str.length;
|
|
87
|
-
}
|
|
88
|
-
function skipVoid(str, ptr, banNewLines, banComments) {
|
|
89
|
-
let c;
|
|
90
|
-
while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
|
|
91
|
-
ptr++;
|
|
92
|
-
return banComments || c !== "#" ? ptr : skipVoid(str, skipComment(str, ptr), banNewLines);
|
|
93
|
-
}
|
|
94
|
-
function skipUntil(str, ptr, sep, end, banNewLines = false) {
|
|
95
|
-
if (!end) {
|
|
96
|
-
ptr = indexOfNewline(str, ptr);
|
|
97
|
-
return ptr < 0 ? str.length : ptr;
|
|
98
|
-
}
|
|
99
|
-
for (let i = ptr; i < str.length; i++) {
|
|
100
|
-
let c = str[i];
|
|
101
|
-
if (c === "#") {
|
|
102
|
-
i = indexOfNewline(str, i);
|
|
103
|
-
} else if (c === sep) {
|
|
104
|
-
return i + 1;
|
|
105
|
-
} else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
|
|
106
|
-
return i;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
throw new TomlError("cannot find end of structure", {
|
|
110
|
-
toml: str,
|
|
111
|
-
ptr
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
function getStringEnd(str, seek) {
|
|
115
|
-
let first = str[seek];
|
|
116
|
-
let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
|
|
117
|
-
seek += target.length - 1;
|
|
118
|
-
do
|
|
119
|
-
seek = str.indexOf(target, ++seek);
|
|
120
|
-
while (seek > -1 && first !== "'" && isEscaped(str, seek));
|
|
121
|
-
if (seek > -1) {
|
|
122
|
-
seek += target.length;
|
|
123
|
-
if (target.length > 1) {
|
|
124
|
-
if (str[seek] === first)
|
|
125
|
-
seek++;
|
|
126
|
-
if (str[seek] === first)
|
|
127
|
-
seek++;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return seek;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/date.js
|
|
134
|
-
var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
|
|
135
|
-
var TomlDate = class _TomlDate extends Date {
|
|
136
|
-
#hasDate = false;
|
|
137
|
-
#hasTime = false;
|
|
138
|
-
#offset = null;
|
|
139
|
-
constructor(date) {
|
|
140
|
-
let hasDate = true;
|
|
141
|
-
let hasTime = true;
|
|
142
|
-
let offset = "Z";
|
|
143
|
-
if (typeof date === "string") {
|
|
144
|
-
let match = date.match(DATE_TIME_RE);
|
|
145
|
-
if (match) {
|
|
146
|
-
if (!match[1]) {
|
|
147
|
-
hasDate = false;
|
|
148
|
-
date = `0000-01-01T${date}`;
|
|
149
|
-
}
|
|
150
|
-
hasTime = !!match[2];
|
|
151
|
-
hasTime && date[10] === " " && (date = date.replace(" ", "T"));
|
|
152
|
-
if (match[2] && +match[2] > 23) {
|
|
153
|
-
date = "";
|
|
154
|
-
} else {
|
|
155
|
-
offset = match[3] || null;
|
|
156
|
-
date = date.toUpperCase();
|
|
157
|
-
if (!offset && hasTime)
|
|
158
|
-
date += "Z";
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
date = "";
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
super(date);
|
|
165
|
-
if (!isNaN(this.getTime())) {
|
|
166
|
-
this.#hasDate = hasDate;
|
|
167
|
-
this.#hasTime = hasTime;
|
|
168
|
-
this.#offset = offset;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
isDateTime() {
|
|
172
|
-
return this.#hasDate && this.#hasTime;
|
|
173
|
-
}
|
|
174
|
-
isLocal() {
|
|
175
|
-
return !this.#hasDate || !this.#hasTime || !this.#offset;
|
|
176
|
-
}
|
|
177
|
-
isDate() {
|
|
178
|
-
return this.#hasDate && !this.#hasTime;
|
|
179
|
-
}
|
|
180
|
-
isTime() {
|
|
181
|
-
return this.#hasTime && !this.#hasDate;
|
|
182
|
-
}
|
|
183
|
-
isValid() {
|
|
184
|
-
return this.#hasDate || this.#hasTime;
|
|
185
|
-
}
|
|
186
|
-
toISOString() {
|
|
187
|
-
let iso = super.toISOString();
|
|
188
|
-
if (this.isDate())
|
|
189
|
-
return iso.slice(0, 10);
|
|
190
|
-
if (this.isTime())
|
|
191
|
-
return iso.slice(11, 23);
|
|
192
|
-
if (this.#offset === null)
|
|
193
|
-
return iso.slice(0, -1);
|
|
194
|
-
if (this.#offset === "Z")
|
|
195
|
-
return iso;
|
|
196
|
-
let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
|
|
197
|
-
offset = this.#offset[0] === "-" ? offset : -offset;
|
|
198
|
-
let offsetDate = new Date(this.getTime() - offset * 6e4);
|
|
199
|
-
return offsetDate.toISOString().slice(0, -1) + this.#offset;
|
|
200
|
-
}
|
|
201
|
-
static wrapAsOffsetDateTime(jsDate, offset = "Z") {
|
|
202
|
-
let date = new _TomlDate(jsDate);
|
|
203
|
-
date.#offset = offset;
|
|
204
|
-
return date;
|
|
205
|
-
}
|
|
206
|
-
static wrapAsLocalDateTime(jsDate) {
|
|
207
|
-
let date = new _TomlDate(jsDate);
|
|
208
|
-
date.#offset = null;
|
|
209
|
-
return date;
|
|
210
|
-
}
|
|
211
|
-
static wrapAsLocalDate(jsDate) {
|
|
212
|
-
let date = new _TomlDate(jsDate);
|
|
213
|
-
date.#hasTime = false;
|
|
214
|
-
date.#offset = null;
|
|
215
|
-
return date;
|
|
216
|
-
}
|
|
217
|
-
static wrapAsLocalTime(jsDate) {
|
|
218
|
-
let date = new _TomlDate(jsDate);
|
|
219
|
-
date.#hasDate = false;
|
|
220
|
-
date.#offset = null;
|
|
221
|
-
return date;
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/primitive.js
|
|
226
|
-
var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
|
|
227
|
-
var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
|
|
228
|
-
var LEADING_ZERO = /^[+-]?0[0-9_]/;
|
|
229
|
-
var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
|
|
230
|
-
var ESC_MAP = {
|
|
231
|
-
b: "\b",
|
|
232
|
-
t: " ",
|
|
233
|
-
n: "\n",
|
|
234
|
-
f: "\f",
|
|
235
|
-
r: "\r",
|
|
236
|
-
e: "\x1B",
|
|
237
|
-
'"': '"',
|
|
238
|
-
"\\": "\\"
|
|
239
|
-
};
|
|
240
|
-
function parseString(str, ptr = 0, endPtr = str.length) {
|
|
241
|
-
let isLiteral = str[ptr] === "'";
|
|
242
|
-
let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
|
|
243
|
-
if (isMultiline) {
|
|
244
|
-
endPtr -= 2;
|
|
245
|
-
if (str[ptr += 2] === "\r")
|
|
246
|
-
ptr++;
|
|
247
|
-
if (str[ptr] === "\n")
|
|
248
|
-
ptr++;
|
|
249
|
-
}
|
|
250
|
-
let tmp = 0;
|
|
251
|
-
let isEscape;
|
|
252
|
-
let parsed = "";
|
|
253
|
-
let sliceStart = ptr;
|
|
254
|
-
while (ptr < endPtr - 1) {
|
|
255
|
-
let c = str[ptr++];
|
|
256
|
-
if (c === "\n" || c === "\r" && str[ptr] === "\n") {
|
|
257
|
-
if (!isMultiline) {
|
|
258
|
-
throw new TomlError("newlines are not allowed in strings", {
|
|
259
|
-
toml: str,
|
|
260
|
-
ptr: ptr - 1
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
} else if (c < " " && c !== " " || c === "\x7F") {
|
|
264
|
-
throw new TomlError("control characters are not allowed in strings", {
|
|
265
|
-
toml: str,
|
|
266
|
-
ptr: ptr - 1
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
if (isEscape) {
|
|
270
|
-
isEscape = false;
|
|
271
|
-
if (c === "x" || c === "u" || c === "U") {
|
|
272
|
-
let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
|
|
273
|
-
if (!ESCAPE_REGEX.test(code)) {
|
|
274
|
-
throw new TomlError("invalid unicode escape", {
|
|
275
|
-
toml: str,
|
|
276
|
-
ptr: tmp
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
try {
|
|
280
|
-
parsed += String.fromCodePoint(parseInt(code, 16));
|
|
281
|
-
} catch {
|
|
282
|
-
throw new TomlError("invalid unicode escape", {
|
|
283
|
-
toml: str,
|
|
284
|
-
ptr: tmp
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
} else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
|
|
288
|
-
ptr = skipVoid(str, ptr - 1, true);
|
|
289
|
-
if (str[ptr] !== "\n" && str[ptr] !== "\r") {
|
|
290
|
-
throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
|
|
291
|
-
toml: str,
|
|
292
|
-
ptr: tmp
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
ptr = skipVoid(str, ptr);
|
|
296
|
-
} else if (c in ESC_MAP) {
|
|
297
|
-
parsed += ESC_MAP[c];
|
|
298
|
-
} else {
|
|
299
|
-
throw new TomlError("unrecognized escape sequence", {
|
|
300
|
-
toml: str,
|
|
301
|
-
ptr: tmp
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
sliceStart = ptr;
|
|
305
|
-
} else if (!isLiteral && c === "\\") {
|
|
306
|
-
tmp = ptr - 1;
|
|
307
|
-
isEscape = true;
|
|
308
|
-
parsed += str.slice(sliceStart, tmp);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return parsed + str.slice(sliceStart, endPtr - 1);
|
|
312
|
-
}
|
|
313
|
-
function parseValue(value, toml, ptr, integersAsBigInt) {
|
|
314
|
-
if (value === "true")
|
|
315
|
-
return true;
|
|
316
|
-
if (value === "false")
|
|
317
|
-
return false;
|
|
318
|
-
if (value === "-inf")
|
|
319
|
-
return -Infinity;
|
|
320
|
-
if (value === "inf" || value === "+inf")
|
|
321
|
-
return Infinity;
|
|
322
|
-
if (value === "nan" || value === "+nan" || value === "-nan")
|
|
323
|
-
return NaN;
|
|
324
|
-
if (value === "-0")
|
|
325
|
-
return integersAsBigInt ? 0n : 0;
|
|
326
|
-
let isInt = INT_REGEX.test(value);
|
|
327
|
-
if (isInt || FLOAT_REGEX.test(value)) {
|
|
328
|
-
if (LEADING_ZERO.test(value)) {
|
|
329
|
-
throw new TomlError("leading zeroes are not allowed", {
|
|
330
|
-
toml,
|
|
331
|
-
ptr
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
value = value.replace(/_/g, "");
|
|
335
|
-
let numeric = +value;
|
|
336
|
-
if (isNaN(numeric)) {
|
|
337
|
-
throw new TomlError("invalid number", {
|
|
338
|
-
toml,
|
|
339
|
-
ptr
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
if (isInt) {
|
|
343
|
-
if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
|
|
344
|
-
throw new TomlError("integer value cannot be represented losslessly", {
|
|
345
|
-
toml,
|
|
346
|
-
ptr
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
if (isInt || integersAsBigInt === true)
|
|
350
|
-
numeric = BigInt(value);
|
|
351
|
-
}
|
|
352
|
-
return numeric;
|
|
353
|
-
}
|
|
354
|
-
const date = new TomlDate(value);
|
|
355
|
-
if (!date.isValid()) {
|
|
356
|
-
throw new TomlError("invalid value", {
|
|
357
|
-
toml,
|
|
358
|
-
ptr
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
return date;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/extract.js
|
|
365
|
-
function sliceAndTrimEndOf(str, startPtr, endPtr) {
|
|
366
|
-
let value = str.slice(startPtr, endPtr);
|
|
367
|
-
let commentIdx = value.indexOf("#");
|
|
368
|
-
if (commentIdx > -1) {
|
|
369
|
-
skipComment(str, commentIdx);
|
|
370
|
-
value = value.slice(0, commentIdx);
|
|
371
|
-
}
|
|
372
|
-
return [value.trimEnd(), commentIdx];
|
|
373
|
-
}
|
|
374
|
-
function extractValue(str, ptr, end, depth, integersAsBigInt) {
|
|
375
|
-
if (depth === 0) {
|
|
376
|
-
throw new TomlError("document contains excessively nested structures. aborting.", {
|
|
377
|
-
toml: str,
|
|
378
|
-
ptr
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
let c = str[ptr];
|
|
382
|
-
if (c === "[" || c === "{") {
|
|
383
|
-
let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
|
|
384
|
-
if (end) {
|
|
385
|
-
endPtr2 = skipVoid(str, endPtr2);
|
|
386
|
-
if (str[endPtr2] === ",")
|
|
387
|
-
endPtr2++;
|
|
388
|
-
else if (str[endPtr2] !== end) {
|
|
389
|
-
throw new TomlError("expected comma or end of structure", {
|
|
390
|
-
toml: str,
|
|
391
|
-
ptr: endPtr2
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return [value, endPtr2];
|
|
396
|
-
}
|
|
397
|
-
let endPtr;
|
|
398
|
-
if (c === '"' || c === "'") {
|
|
399
|
-
endPtr = getStringEnd(str, ptr);
|
|
400
|
-
let parsed = parseString(str, ptr, endPtr);
|
|
401
|
-
if (end) {
|
|
402
|
-
endPtr = skipVoid(str, endPtr);
|
|
403
|
-
if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
|
|
404
|
-
throw new TomlError("unexpected character encountered", {
|
|
405
|
-
toml: str,
|
|
406
|
-
ptr: endPtr
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
endPtr += +(str[endPtr] === ",");
|
|
410
|
-
}
|
|
411
|
-
return [parsed, endPtr];
|
|
412
|
-
}
|
|
413
|
-
endPtr = skipUntil(str, ptr, ",", end);
|
|
414
|
-
let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
|
|
415
|
-
if (!slice[0]) {
|
|
416
|
-
throw new TomlError("incomplete key-value declaration: no value specified", {
|
|
417
|
-
toml: str,
|
|
418
|
-
ptr
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
if (end && slice[1] > -1) {
|
|
422
|
-
endPtr = skipVoid(str, ptr + slice[1]);
|
|
423
|
-
endPtr += +(str[endPtr] === ",");
|
|
424
|
-
}
|
|
425
|
-
return [
|
|
426
|
-
parseValue(slice[0], str, ptr, integersAsBigInt),
|
|
427
|
-
endPtr
|
|
428
|
-
];
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/struct.js
|
|
432
|
-
var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
|
|
433
|
-
function parseKey(str, ptr, end = "=") {
|
|
434
|
-
let dot = ptr - 1;
|
|
435
|
-
let parsed = [];
|
|
436
|
-
let endPtr = str.indexOf(end, ptr);
|
|
437
|
-
if (endPtr < 0) {
|
|
438
|
-
throw new TomlError("incomplete key-value: cannot find end of key", {
|
|
439
|
-
toml: str,
|
|
440
|
-
ptr
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
do {
|
|
444
|
-
let c = str[ptr = ++dot];
|
|
445
|
-
if (c !== " " && c !== " ") {
|
|
446
|
-
if (c === '"' || c === "'") {
|
|
447
|
-
if (c === str[ptr + 1] && c === str[ptr + 2]) {
|
|
448
|
-
throw new TomlError("multiline strings are not allowed in keys", {
|
|
449
|
-
toml: str,
|
|
450
|
-
ptr
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
let eos = getStringEnd(str, ptr);
|
|
454
|
-
if (eos < 0) {
|
|
455
|
-
throw new TomlError("unfinished string encountered", {
|
|
456
|
-
toml: str,
|
|
457
|
-
ptr
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
dot = str.indexOf(".", eos);
|
|
461
|
-
let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
|
|
462
|
-
let newLine = indexOfNewline(strEnd);
|
|
463
|
-
if (newLine > -1) {
|
|
464
|
-
throw new TomlError("newlines are not allowed in keys", {
|
|
465
|
-
toml: str,
|
|
466
|
-
ptr: ptr + dot + newLine
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
if (strEnd.trimStart()) {
|
|
470
|
-
throw new TomlError("found extra tokens after the string part", {
|
|
471
|
-
toml: str,
|
|
472
|
-
ptr: eos
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
if (endPtr < eos) {
|
|
476
|
-
endPtr = str.indexOf(end, eos);
|
|
477
|
-
if (endPtr < 0) {
|
|
478
|
-
throw new TomlError("incomplete key-value: cannot find end of key", {
|
|
479
|
-
toml: str,
|
|
480
|
-
ptr
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
parsed.push(parseString(str, ptr, eos));
|
|
485
|
-
} else {
|
|
486
|
-
dot = str.indexOf(".", ptr);
|
|
487
|
-
let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
|
|
488
|
-
if (!KEY_PART_RE.test(part)) {
|
|
489
|
-
throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
|
|
490
|
-
toml: str,
|
|
491
|
-
ptr
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
parsed.push(part.trimEnd());
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
} while (dot + 1 && dot < endPtr);
|
|
498
|
-
return [parsed, skipVoid(str, endPtr + 1, true, true)];
|
|
499
|
-
}
|
|
500
|
-
function parseInlineTable(str, ptr, depth, integersAsBigInt) {
|
|
501
|
-
let res = {};
|
|
502
|
-
let seen = /* @__PURE__ */ new Set();
|
|
503
|
-
let c;
|
|
504
|
-
ptr++;
|
|
505
|
-
while ((c = str[ptr++]) !== "}" && c) {
|
|
506
|
-
if (c === ",") {
|
|
507
|
-
throw new TomlError("expected value, found comma", {
|
|
508
|
-
toml: str,
|
|
509
|
-
ptr: ptr - 1
|
|
510
|
-
});
|
|
511
|
-
} else if (c === "#")
|
|
512
|
-
ptr = skipComment(str, ptr);
|
|
513
|
-
else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
|
|
514
|
-
let k;
|
|
515
|
-
let t = res;
|
|
516
|
-
let hasOwn = false;
|
|
517
|
-
let [key, keyEndPtr] = parseKey(str, ptr - 1);
|
|
518
|
-
for (let i = 0; i < key.length; i++) {
|
|
519
|
-
if (i)
|
|
520
|
-
t = hasOwn ? t[k] : t[k] = {};
|
|
521
|
-
k = key[i];
|
|
522
|
-
if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
|
|
523
|
-
throw new TomlError("trying to redefine an already defined value", {
|
|
524
|
-
toml: str,
|
|
525
|
-
ptr
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
if (!hasOwn && k === "__proto__") {
|
|
529
|
-
Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
if (hasOwn) {
|
|
533
|
-
throw new TomlError("trying to redefine an already defined value", {
|
|
534
|
-
toml: str,
|
|
535
|
-
ptr
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
|
|
539
|
-
seen.add(value);
|
|
540
|
-
t[k] = value;
|
|
541
|
-
ptr = valueEndPtr;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (!c) {
|
|
545
|
-
throw new TomlError("unfinished table encountered", {
|
|
546
|
-
toml: str,
|
|
547
|
-
ptr
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
return [res, ptr];
|
|
551
|
-
}
|
|
552
|
-
function parseArray(str, ptr, depth, integersAsBigInt) {
|
|
553
|
-
let res = [];
|
|
554
|
-
let c;
|
|
555
|
-
ptr++;
|
|
556
|
-
while ((c = str[ptr++]) !== "]" && c) {
|
|
557
|
-
if (c === ",") {
|
|
558
|
-
throw new TomlError("expected value, found comma", {
|
|
559
|
-
toml: str,
|
|
560
|
-
ptr: ptr - 1
|
|
561
|
-
});
|
|
562
|
-
} else if (c === "#")
|
|
563
|
-
ptr = skipComment(str, ptr);
|
|
564
|
-
else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
|
|
565
|
-
let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
|
|
566
|
-
res.push(e[0]);
|
|
567
|
-
ptr = e[1];
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
if (!c) {
|
|
571
|
-
throw new TomlError("unfinished array encountered", {
|
|
572
|
-
toml: str,
|
|
573
|
-
ptr
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
return [res, ptr];
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// ../../node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/parse.js
|
|
580
|
-
function peekTable(key, table, meta, type) {
|
|
581
|
-
let t = table;
|
|
582
|
-
let m = meta;
|
|
583
|
-
let k;
|
|
584
|
-
let hasOwn = false;
|
|
585
|
-
let state;
|
|
586
|
-
for (let i = 0; i < key.length; i++) {
|
|
587
|
-
if (i) {
|
|
588
|
-
t = hasOwn ? t[k] : t[k] = {};
|
|
589
|
-
m = (state = m[k]).c;
|
|
590
|
-
if (type === 0 && (state.t === 1 || state.t === 2)) {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
if (state.t === 2) {
|
|
594
|
-
let l = t.length - 1;
|
|
595
|
-
t = t[l];
|
|
596
|
-
m = m[l].c;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
k = key[i];
|
|
600
|
-
if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
|
|
601
|
-
return null;
|
|
602
|
-
}
|
|
603
|
-
if (!hasOwn) {
|
|
604
|
-
if (k === "__proto__") {
|
|
605
|
-
Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
|
|
606
|
-
Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
|
|
607
|
-
}
|
|
608
|
-
m[k] = {
|
|
609
|
-
t: i < key.length - 1 && type === 2 ? 3 : type,
|
|
610
|
-
d: false,
|
|
611
|
-
i: 0,
|
|
612
|
-
c: {}
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
state = m[k];
|
|
617
|
-
if (state.t !== type && !(type === 1 && state.t === 3)) {
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
if (type === 2) {
|
|
621
|
-
if (!state.d) {
|
|
622
|
-
state.d = true;
|
|
623
|
-
t[k] = [];
|
|
624
|
-
}
|
|
625
|
-
t[k].push(t = {});
|
|
626
|
-
state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
|
|
627
|
-
}
|
|
628
|
-
if (state.d) {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
state.d = true;
|
|
632
|
-
if (type === 1) {
|
|
633
|
-
t = hasOwn ? t[k] : t[k] = {};
|
|
634
|
-
} else if (type === 0 && hasOwn) {
|
|
635
|
-
return null;
|
|
636
|
-
}
|
|
637
|
-
return [k, t, state.c];
|
|
638
|
-
}
|
|
639
|
-
function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
640
|
-
let res = {};
|
|
641
|
-
let meta = {};
|
|
642
|
-
let tbl = res;
|
|
643
|
-
let m = meta;
|
|
644
|
-
for (let ptr = skipVoid(toml, 0); ptr < toml.length; ) {
|
|
645
|
-
if (toml[ptr] === "[") {
|
|
646
|
-
let isTableArray = toml[++ptr] === "[";
|
|
647
|
-
let k = parseKey(toml, ptr += +isTableArray, "]");
|
|
648
|
-
if (isTableArray) {
|
|
649
|
-
if (toml[k[1] - 1] !== "]") {
|
|
650
|
-
throw new TomlError("expected end of table declaration", {
|
|
651
|
-
toml,
|
|
652
|
-
ptr: k[1] - 1
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
k[1]++;
|
|
656
|
-
}
|
|
657
|
-
let p = peekTable(
|
|
658
|
-
k[0],
|
|
659
|
-
res,
|
|
660
|
-
meta,
|
|
661
|
-
isTableArray ? 2 : 1
|
|
662
|
-
/* Type.EXPLICIT */
|
|
663
|
-
);
|
|
664
|
-
if (!p) {
|
|
665
|
-
throw new TomlError("trying to redefine an already defined table or value", {
|
|
666
|
-
toml,
|
|
667
|
-
ptr
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
m = p[2];
|
|
671
|
-
tbl = p[1];
|
|
672
|
-
ptr = k[1];
|
|
673
|
-
} else {
|
|
674
|
-
let k = parseKey(toml, ptr);
|
|
675
|
-
let p = peekTable(
|
|
676
|
-
k[0],
|
|
677
|
-
tbl,
|
|
678
|
-
m,
|
|
679
|
-
0
|
|
680
|
-
/* Type.DOTTED */
|
|
681
|
-
);
|
|
682
|
-
if (!p) {
|
|
683
|
-
throw new TomlError("trying to redefine an already defined table or value", {
|
|
684
|
-
toml,
|
|
685
|
-
ptr
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
|
|
689
|
-
p[1][p[0]] = v[0];
|
|
690
|
-
ptr = v[1];
|
|
691
|
-
}
|
|
692
|
-
ptr = skipVoid(toml, ptr, true);
|
|
693
|
-
if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") {
|
|
694
|
-
throw new TomlError("each key-value declaration must be followed by an end-of-line", {
|
|
695
|
-
toml,
|
|
696
|
-
ptr
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
ptr = skipVoid(toml, ptr);
|
|
700
|
-
}
|
|
701
|
-
return res;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// ../defi-core/dist/index.js
|
|
18
|
+
import { parse } from "smol-toml";
|
|
705
19
|
import { existsSync } from "fs";
|
|
706
20
|
var TxStatus = /* @__PURE__ */ ((TxStatus2) => {
|
|
707
21
|
TxStatus2["DryRun"] = "dry_run";
|
|
@@ -6388,155 +5702,6 @@ var HINT_HELPERS_ABI = parseAbi21([
|
|
|
6388
5702
|
var SORTED_TROVES_ABI = parseAbi21([
|
|
6389
5703
|
"function findInsertPosition(uint256 _annualInterestRate, uint256 _prevId, uint256 _nextId) external view returns (uint256 prevId, uint256 nextId)"
|
|
6390
5704
|
]);
|
|
6391
|
-
var FelixCdpAdapter = class {
|
|
6392
|
-
protocolName;
|
|
6393
|
-
borrowerOperations;
|
|
6394
|
-
troveManager;
|
|
6395
|
-
hintHelpers;
|
|
6396
|
-
sortedTroves;
|
|
6397
|
-
rpcUrl;
|
|
6398
|
-
constructor(entry, rpcUrl) {
|
|
6399
|
-
this.protocolName = entry.name;
|
|
6400
|
-
this.rpcUrl = rpcUrl;
|
|
6401
|
-
const contracts = entry.contracts ?? {};
|
|
6402
|
-
const bo = contracts["borrower_operations"];
|
|
6403
|
-
if (!bo) throw DefiError.contractError("Missing 'borrower_operations' contract");
|
|
6404
|
-
this.borrowerOperations = bo;
|
|
6405
|
-
this.troveManager = contracts["trove_manager"];
|
|
6406
|
-
this.hintHelpers = contracts["hint_helpers"];
|
|
6407
|
-
this.sortedTroves = contracts["sorted_troves"];
|
|
6408
|
-
}
|
|
6409
|
-
name() {
|
|
6410
|
-
return this.protocolName;
|
|
6411
|
-
}
|
|
6412
|
-
async getHints(interestRate) {
|
|
6413
|
-
if (!this.hintHelpers || !this.sortedTroves || !this.rpcUrl) {
|
|
6414
|
-
return [0n, 0n];
|
|
6415
|
-
}
|
|
6416
|
-
const client = createPublicClient16({ transport: http16(this.rpcUrl) });
|
|
6417
|
-
const approxResult = await client.readContract({
|
|
6418
|
-
address: this.hintHelpers,
|
|
6419
|
-
abi: HINT_HELPERS_ABI,
|
|
6420
|
-
functionName: "getApproxHint",
|
|
6421
|
-
args: [0n, interestRate, 15n, 42n]
|
|
6422
|
-
}).catch(() => null);
|
|
6423
|
-
if (!approxResult) return [0n, 0n];
|
|
6424
|
-
const [hintId] = approxResult;
|
|
6425
|
-
const insertResult = await client.readContract({
|
|
6426
|
-
address: this.sortedTroves,
|
|
6427
|
-
abi: SORTED_TROVES_ABI,
|
|
6428
|
-
functionName: "findInsertPosition",
|
|
6429
|
-
args: [interestRate, hintId, hintId]
|
|
6430
|
-
}).catch(() => null);
|
|
6431
|
-
if (!insertResult) return [0n, 0n];
|
|
6432
|
-
const [prevId, nextId] = insertResult;
|
|
6433
|
-
return [prevId, nextId];
|
|
6434
|
-
}
|
|
6435
|
-
async buildOpen(params) {
|
|
6436
|
-
const interestRate = 50000000000000000n;
|
|
6437
|
-
const [upperHint, lowerHint] = await this.getHints(interestRate);
|
|
6438
|
-
const hasHints = upperHint !== 0n || lowerHint !== 0n;
|
|
6439
|
-
const data = encodeFunctionData20({
|
|
6440
|
-
abi: BORROWER_OPS_ABI,
|
|
6441
|
-
functionName: "openTrove",
|
|
6442
|
-
args: [
|
|
6443
|
-
params.recipient,
|
|
6444
|
-
0n,
|
|
6445
|
-
params.collateral_amount,
|
|
6446
|
-
params.debt_amount,
|
|
6447
|
-
upperHint,
|
|
6448
|
-
lowerHint,
|
|
6449
|
-
interestRate,
|
|
6450
|
-
BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
|
6451
|
-
// U256::MAX
|
|
6452
|
-
params.recipient,
|
|
6453
|
-
params.recipient,
|
|
6454
|
-
params.recipient
|
|
6455
|
-
]
|
|
6456
|
-
});
|
|
6457
|
-
return {
|
|
6458
|
-
description: `[${this.protocolName}] Open trove: collateral=${params.collateral_amount}, debt=${params.debt_amount} (hints=${hasHints ? "optimized" : "none"})`,
|
|
6459
|
-
to: this.borrowerOperations,
|
|
6460
|
-
data,
|
|
6461
|
-
value: 0n,
|
|
6462
|
-
gas_estimate: hasHints ? 5e5 : 5e6
|
|
6463
|
-
};
|
|
6464
|
-
}
|
|
6465
|
-
async buildAdjust(params) {
|
|
6466
|
-
const collChange = params.collateral_delta ?? 0n;
|
|
6467
|
-
const debtChange = params.debt_delta ?? 0n;
|
|
6468
|
-
const data = encodeFunctionData20({
|
|
6469
|
-
abi: BORROWER_OPS_ABI,
|
|
6470
|
-
functionName: "adjustTrove",
|
|
6471
|
-
args: [
|
|
6472
|
-
params.cdp_id,
|
|
6473
|
-
collChange,
|
|
6474
|
-
params.add_collateral,
|
|
6475
|
-
debtChange,
|
|
6476
|
-
params.add_debt,
|
|
6477
|
-
0n,
|
|
6478
|
-
0n,
|
|
6479
|
-
BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
|
6480
|
-
]
|
|
6481
|
-
});
|
|
6482
|
-
return {
|
|
6483
|
-
description: `[${this.protocolName}] Adjust trove ${params.cdp_id}`,
|
|
6484
|
-
to: this.borrowerOperations,
|
|
6485
|
-
data,
|
|
6486
|
-
value: 0n,
|
|
6487
|
-
gas_estimate: 4e5
|
|
6488
|
-
};
|
|
6489
|
-
}
|
|
6490
|
-
async buildClose(params) {
|
|
6491
|
-
const data = encodeFunctionData20({
|
|
6492
|
-
abi: BORROWER_OPS_ABI,
|
|
6493
|
-
functionName: "closeTrove",
|
|
6494
|
-
args: [params.cdp_id]
|
|
6495
|
-
});
|
|
6496
|
-
return {
|
|
6497
|
-
description: `[${this.protocolName}] Close trove ${params.cdp_id}`,
|
|
6498
|
-
to: this.borrowerOperations,
|
|
6499
|
-
data,
|
|
6500
|
-
value: 0n,
|
|
6501
|
-
gas_estimate: 35e4
|
|
6502
|
-
};
|
|
6503
|
-
}
|
|
6504
|
-
async getCdpInfo(cdpId) {
|
|
6505
|
-
if (!this.rpcUrl) throw DefiError.rpcError(`[${this.protocolName}] getCdpInfo requires RPC \u2014 set HYPEREVM_RPC_URL`);
|
|
6506
|
-
if (!this.troveManager) throw DefiError.contractError(`[${this.protocolName}] trove_manager contract not configured`);
|
|
6507
|
-
const client = createPublicClient16({ transport: http16(this.rpcUrl) });
|
|
6508
|
-
const data = await client.readContract({
|
|
6509
|
-
address: this.troveManager,
|
|
6510
|
-
abi: TROVE_MANAGER_ABI,
|
|
6511
|
-
functionName: "getLatestTroveData",
|
|
6512
|
-
args: [cdpId]
|
|
6513
|
-
}).catch((e) => {
|
|
6514
|
-
throw DefiError.invalidParam(`[${this.protocolName}] Trove ${cdpId} not found: ${e}`);
|
|
6515
|
-
});
|
|
6516
|
-
const [entireDebt, entireColl] = data;
|
|
6517
|
-
if (entireDebt === 0n && entireColl === 0n) {
|
|
6518
|
-
throw DefiError.invalidParam(`[${this.protocolName}] Trove ${cdpId} does not exist`);
|
|
6519
|
-
}
|
|
6520
|
-
const collRatio = entireDebt > 0n ? Number(entireColl) / Number(entireDebt) : 0;
|
|
6521
|
-
return {
|
|
6522
|
-
protocol: this.protocolName,
|
|
6523
|
-
cdp_id: cdpId,
|
|
6524
|
-
collateral: {
|
|
6525
|
-
token: zeroAddress11,
|
|
6526
|
-
symbol: "WHYPE",
|
|
6527
|
-
amount: entireColl,
|
|
6528
|
-
decimals: 18
|
|
6529
|
-
},
|
|
6530
|
-
debt: {
|
|
6531
|
-
token: zeroAddress11,
|
|
6532
|
-
symbol: "feUSD",
|
|
6533
|
-
amount: entireDebt,
|
|
6534
|
-
decimals: 18
|
|
6535
|
-
},
|
|
6536
|
-
collateral_ratio: collRatio
|
|
6537
|
-
};
|
|
6538
|
-
}
|
|
6539
|
-
};
|
|
6540
5705
|
var PRICE_FEED_ABI = parseAbi222([
|
|
6541
5706
|
"function fetchPrice() external view returns (uint256 price, bool isNewOracleFailureDetected)",
|
|
6542
5707
|
"function lastGoodPrice() external view returns (uint256)"
|
|
@@ -6792,14 +5957,6 @@ function createLending(entry, rpcUrl) {
|
|
|
6792
5957
|
throw DefiError.unsupported(`Lending interface '${entry.interface}' not yet implemented`);
|
|
6793
5958
|
}
|
|
6794
5959
|
}
|
|
6795
|
-
function createCdp(entry, rpcUrl) {
|
|
6796
|
-
switch (entry.interface) {
|
|
6797
|
-
case "liquity_v2":
|
|
6798
|
-
return new FelixCdpAdapter(entry, rpcUrl);
|
|
6799
|
-
default:
|
|
6800
|
-
throw DefiError.unsupported(`CDP interface '${entry.interface}' not yet implemented`);
|
|
6801
|
-
}
|
|
6802
|
-
}
|
|
6803
5960
|
function createVault(entry, rpcUrl) {
|
|
6804
5961
|
switch (entry.interface) {
|
|
6805
5962
|
case "erc4626":
|
|
@@ -6895,6 +6052,21 @@ var DexSpotPrice = class {
|
|
|
6895
6052
|
}
|
|
6896
6053
|
};
|
|
6897
6054
|
|
|
6055
|
+
// src/whitelist.ts
|
|
6056
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
6057
|
+
import { resolve as resolve2 } from "path";
|
|
6058
|
+
import { parse as parse2 } from "smol-toml";
|
|
6059
|
+
function loadWhitelist() {
|
|
6060
|
+
const path = resolve2(process.env["HOME"] ?? "~", ".defi", "pools.toml");
|
|
6061
|
+
try {
|
|
6062
|
+
const raw = readFileSync2(path, "utf-8");
|
|
6063
|
+
const parsed = parse2(raw);
|
|
6064
|
+
return parsed.whitelist ?? [];
|
|
6065
|
+
} catch {
|
|
6066
|
+
return [];
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
|
|
6898
6070
|
// src/commands/lp.ts
|
|
6899
6071
|
function resolveAccount(optOwner) {
|
|
6900
6072
|
if (optOwner) return optOwner;
|
|
@@ -7228,35 +6400,269 @@ function registerLP(parent, getOpts, makeExecutor2) {
|
|
|
7228
6400
|
);
|
|
7229
6401
|
printOutput(results, getOpts());
|
|
7230
6402
|
});
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
const
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
6403
|
+
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) => {
|
|
6404
|
+
const budgetUsd = parseFloat(opts.budget);
|
|
6405
|
+
if (isNaN(budgetUsd) || budgetUsd <= 0) {
|
|
6406
|
+
printOutput({ error: `Invalid budget: ${opts.budget}` }, getOpts());
|
|
6407
|
+
process.exit(1);
|
|
6408
|
+
return;
|
|
6409
|
+
}
|
|
6410
|
+
let whitelist = loadWhitelist();
|
|
6411
|
+
if (whitelist.length === 0) {
|
|
6412
|
+
printOutput(
|
|
6413
|
+
{ error: "No pools whitelisted. Create ~/.defi/pools.toml (see config/pools.example.toml)" },
|
|
6414
|
+
getOpts()
|
|
6415
|
+
);
|
|
6416
|
+
process.exit(1);
|
|
6417
|
+
return;
|
|
6418
|
+
}
|
|
6419
|
+
const chainFilter = opts.chain?.toLowerCase();
|
|
6420
|
+
if (chainFilter) {
|
|
6421
|
+
whitelist = whitelist.filter((e) => e.chain.toLowerCase() === chainFilter);
|
|
6422
|
+
if (whitelist.length === 0) {
|
|
6423
|
+
printOutput(
|
|
6424
|
+
{ error: `No whitelisted pools found for chain '${chainFilter}'` },
|
|
6425
|
+
getOpts()
|
|
6426
|
+
);
|
|
6427
|
+
process.exit(1);
|
|
6428
|
+
return;
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
7258
6431
|
const registry = Registry.loadEmbedded();
|
|
7259
|
-
const
|
|
6432
|
+
const scanned = await Promise.all(
|
|
6433
|
+
whitelist.map(async (entry) => {
|
|
6434
|
+
try {
|
|
6435
|
+
const chainName = entry.chain.toLowerCase();
|
|
6436
|
+
let chain;
|
|
6437
|
+
try {
|
|
6438
|
+
chain = registry.getChain(chainName);
|
|
6439
|
+
} catch {
|
|
6440
|
+
return { entry, scan_error: `Unknown chain '${chainName}'` };
|
|
6441
|
+
}
|
|
6442
|
+
const rpcUrl = chain.effectiveRpcUrl();
|
|
6443
|
+
if (entry.type === "lending" && entry.asset) {
|
|
6444
|
+
const protos = registry.getProtocolsForChain(chainName).filter(
|
|
6445
|
+
(p) => p.category === ProtocolCategory.Lending && p.slug === entry.protocol
|
|
6446
|
+
);
|
|
6447
|
+
if (protos.length === 0) {
|
|
6448
|
+
return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
|
|
6449
|
+
}
|
|
6450
|
+
const proto = protos[0];
|
|
6451
|
+
const assetAddr = registry.resolveToken(chainName, entry.asset).address;
|
|
6452
|
+
const adapter = createLending(proto, rpcUrl);
|
|
6453
|
+
const rates = await adapter.getRates(assetAddr);
|
|
6454
|
+
return { entry, apy: rates.supply_apy };
|
|
6455
|
+
}
|
|
6456
|
+
if (entry.type === "lb" && entry.pool) {
|
|
6457
|
+
const protos = registry.getProtocolsForChain(chainName).filter((p) => p.slug === entry.protocol);
|
|
6458
|
+
if (protos.length === 0) {
|
|
6459
|
+
return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
|
|
6460
|
+
}
|
|
6461
|
+
const proto = protos[0];
|
|
6462
|
+
if (proto.interface === "uniswap_v2" && proto.contracts?.["lb_factory"]) {
|
|
6463
|
+
const adapter = createMerchantMoeLB(proto, rpcUrl);
|
|
6464
|
+
const pools = await adapter.discoverRewardedPools();
|
|
6465
|
+
const match = pools.find(
|
|
6466
|
+
(p) => p.pool.toLowerCase() === entry.pool.toLowerCase() || `${p.symbolX}/${p.symbolY}`.toLowerCase() === entry.pool.toLowerCase() || `${p.symbolY}/${p.symbolX}`.toLowerCase() === entry.pool.toLowerCase()
|
|
6467
|
+
);
|
|
6468
|
+
if (match) {
|
|
6469
|
+
return { entry, apr: match.aprPercent, active: !match.stopped };
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
return { entry, scan_error: "Pool not found in LB discovery" };
|
|
6473
|
+
}
|
|
6474
|
+
if (entry.type === "farming" && entry.pool) {
|
|
6475
|
+
const protos = registry.getProtocolsForChain(chainName).filter((p) => p.slug === entry.protocol);
|
|
6476
|
+
if (protos.length === 0) {
|
|
6477
|
+
return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
|
|
6478
|
+
}
|
|
6479
|
+
const proto = protos[0];
|
|
6480
|
+
if (proto.interface === "algebra_v3" && proto.contracts?.["farming_center"]) {
|
|
6481
|
+
const adapter = createKittenSwapFarming(proto, rpcUrl);
|
|
6482
|
+
const pools = await adapter.discoverFarmingPools();
|
|
6483
|
+
const match = pools.find(
|
|
6484
|
+
(p) => p.pool.toLowerCase() === entry.pool.toLowerCase()
|
|
6485
|
+
);
|
|
6486
|
+
if (match) {
|
|
6487
|
+
return { entry, active: match.active };
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
return { entry, scan_error: "Pool not found in farming discovery" };
|
|
6491
|
+
}
|
|
6492
|
+
if (entry.type === "gauge" && entry.pool) {
|
|
6493
|
+
const protos = registry.getProtocolsForChain(chainName).filter((p) => p.slug === entry.protocol);
|
|
6494
|
+
if (protos.length === 0) {
|
|
6495
|
+
return { entry, scan_error: `Protocol not found: ${entry.protocol}` };
|
|
6496
|
+
}
|
|
6497
|
+
const proto = protos[0];
|
|
6498
|
+
if (["solidly_v2", "solidly_cl", "algebra_v3", "hybra"].includes(proto.interface)) {
|
|
6499
|
+
const adapter = createGauge(proto, rpcUrl);
|
|
6500
|
+
if (adapter.discoverGaugedPools) {
|
|
6501
|
+
const pools = await adapter.discoverGaugedPools();
|
|
6502
|
+
const poolAddr = entry.pool.startsWith("0x") ? entry.pool.toLowerCase() : void 0;
|
|
6503
|
+
const match = pools.find(
|
|
6504
|
+
(p) => poolAddr && p.pool.toLowerCase() === poolAddr || `${p.token0}/${p.token1}`.toLowerCase() === entry.pool.toLowerCase() || `${p.token1}/${p.token0}`.toLowerCase() === entry.pool.toLowerCase()
|
|
6505
|
+
);
|
|
6506
|
+
return { entry, active: !!match };
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
return { entry, scan_error: "Gauge discovery not supported for this protocol" };
|
|
6510
|
+
}
|
|
6511
|
+
return { entry, scan_error: "Unsupported entry type or missing pool/asset field" };
|
|
6512
|
+
} catch (err) {
|
|
6513
|
+
return { entry, scan_error: String(err) };
|
|
6514
|
+
}
|
|
6515
|
+
})
|
|
6516
|
+
);
|
|
6517
|
+
const RESERVE_PCT = 0.2;
|
|
6518
|
+
const deployableBudget = budgetUsd * (1 - RESERVE_PCT);
|
|
6519
|
+
const reserveUsd = budgetUsd * RESERVE_PCT;
|
|
6520
|
+
const ranked = [...scanned].sort((a, b) => {
|
|
6521
|
+
const scoreA = a.apy ?? a.apr ?? (a.active ? 1 : 0);
|
|
6522
|
+
const scoreB = b.apy ?? b.apr ?? (b.active ? 1 : 0);
|
|
6523
|
+
return scoreB - scoreA;
|
|
6524
|
+
});
|
|
6525
|
+
const allocations = [];
|
|
6526
|
+
let remainingBudget = deployableBudget;
|
|
6527
|
+
for (const s of ranked) {
|
|
6528
|
+
if (remainingBudget <= 0) break;
|
|
6529
|
+
const maxAlloc = budgetUsd * (s.entry.max_allocation_pct / 100);
|
|
6530
|
+
const alloc = Math.min(maxAlloc, remainingBudget);
|
|
6531
|
+
if (alloc <= 0) continue;
|
|
6532
|
+
const item = {
|
|
6533
|
+
protocol: s.entry.protocol,
|
|
6534
|
+
chain: s.entry.chain,
|
|
6535
|
+
type: s.entry.type,
|
|
6536
|
+
amount_usd: Math.round(alloc * 100) / 100
|
|
6537
|
+
};
|
|
6538
|
+
if (s.entry.pool) item["pool"] = s.entry.pool;
|
|
6539
|
+
if (s.entry.asset) item["asset"] = s.entry.asset;
|
|
6540
|
+
if (s.apy !== void 0) item["apy"] = s.apy;
|
|
6541
|
+
if (s.apr !== void 0) item["apr"] = s.apr;
|
|
6542
|
+
if (s.active !== void 0) item["active"] = s.active;
|
|
6543
|
+
if (s.scan_error) item["scan_error"] = s.scan_error;
|
|
6544
|
+
allocations.push(item);
|
|
6545
|
+
remainingBudget -= alloc;
|
|
6546
|
+
}
|
|
6547
|
+
const totalReserved = reserveUsd + remainingBudget;
|
|
6548
|
+
allocations.push({
|
|
6549
|
+
reserve: true,
|
|
6550
|
+
amount_usd: Math.round(totalReserved * 100) / 100,
|
|
6551
|
+
note: "20% safety margin (hardcoded) + unallocated remainder"
|
|
6552
|
+
});
|
|
6553
|
+
let estimatedAnnualYieldUsd = 0;
|
|
6554
|
+
for (const alloc of allocations) {
|
|
6555
|
+
if (alloc["reserve"]) continue;
|
|
6556
|
+
const amt = alloc["amount_usd"];
|
|
6557
|
+
const rate = alloc["apy"] ?? alloc["apr"];
|
|
6558
|
+
if (rate !== void 0 && rate > 0) {
|
|
6559
|
+
estimatedAnnualYieldUsd += amt * rate;
|
|
6560
|
+
}
|
|
6561
|
+
}
|
|
6562
|
+
const estimatedDailyYieldUsd = estimatedAnnualYieldUsd / 365;
|
|
6563
|
+
const isBroadcast = !!opts.broadcast;
|
|
6564
|
+
const plan = {
|
|
6565
|
+
budget_usd: budgetUsd,
|
|
6566
|
+
deployable_usd: Math.round(deployableBudget * 100) / 100,
|
|
6567
|
+
reserve_pct: RESERVE_PCT * 100,
|
|
6568
|
+
allocations,
|
|
6569
|
+
estimated_daily_yield_usd: Math.round(estimatedDailyYieldUsd * 100) / 100,
|
|
6570
|
+
estimated_annual_yield_usd: Math.round(estimatedAnnualYieldUsd * 100) / 100,
|
|
6571
|
+
execution: isBroadcast ? "broadcast" : "dry_run"
|
|
6572
|
+
};
|
|
6573
|
+
printOutput(plan, getOpts());
|
|
6574
|
+
if (!isBroadcast) return;
|
|
6575
|
+
process.stderr.write("\nExecuting autopilot plan...\n");
|
|
6576
|
+
const executor = makeExecutor2();
|
|
6577
|
+
const execResults = [];
|
|
6578
|
+
let allocIndex = 0;
|
|
6579
|
+
const actionAllocs = allocations.filter((a) => !a["reserve"]);
|
|
6580
|
+
for (const alloc of actionAllocs) {
|
|
6581
|
+
allocIndex++;
|
|
6582
|
+
const chainName = alloc["chain"].toLowerCase();
|
|
6583
|
+
let chain;
|
|
6584
|
+
try {
|
|
6585
|
+
chain = registry.getChain(chainName);
|
|
6586
|
+
} catch {
|
|
6587
|
+
process.stderr.write(`
|
|
6588
|
+
--- ${allocIndex}/${actionAllocs.length}: ${alloc["protocol"]} \u2014 unknown chain '${chainName}', skipping ---
|
|
6589
|
+
`);
|
|
6590
|
+
execResults.push({ ...alloc, exec_status: "skipped", exec_error: `Unknown chain '${chainName}'` });
|
|
6591
|
+
continue;
|
|
6592
|
+
}
|
|
6593
|
+
const rpc = chain.effectiveRpcUrl();
|
|
6594
|
+
process.stderr.write(`
|
|
6595
|
+
--- ${allocIndex}/${actionAllocs.length}: ${alloc["protocol"]} (${alloc["type"]}) $${alloc["amount_usd"]} ---
|
|
6596
|
+
`);
|
|
6597
|
+
if (alloc["type"] === "lending" && alloc["asset"]) {
|
|
6598
|
+
try {
|
|
6599
|
+
const protocol = registry.getProtocol(alloc["protocol"]);
|
|
6600
|
+
const adapter = createLending(protocol, rpc);
|
|
6601
|
+
const tokenInfo = registry.resolveToken(chainName, alloc["asset"]);
|
|
6602
|
+
const assetAddr = tokenInfo.address;
|
|
6603
|
+
const decimals = tokenInfo.decimals ?? 18;
|
|
6604
|
+
const amountWei = BigInt(Math.floor(alloc["amount_usd"] * 10 ** decimals));
|
|
6605
|
+
const wallet = resolveAccount();
|
|
6606
|
+
const tx = await adapter.buildSupply({
|
|
6607
|
+
protocol: alloc["protocol"],
|
|
6608
|
+
asset: assetAddr,
|
|
6609
|
+
amount: amountWei,
|
|
6610
|
+
on_behalf_of: wallet
|
|
6611
|
+
});
|
|
6612
|
+
process.stderr.write(` Supplying ${amountWei} wei of ${alloc["asset"]} to ${alloc["protocol"]}...
|
|
6613
|
+
`);
|
|
6614
|
+
const result = await executor.execute(tx);
|
|
6615
|
+
process.stderr.write(` Status: ${result.status}
|
|
6616
|
+
`);
|
|
6617
|
+
const explorerUrl = result.details?.["explorer_url"];
|
|
6618
|
+
if (explorerUrl) process.stderr.write(` Explorer: ${explorerUrl}
|
|
6619
|
+
`);
|
|
6620
|
+
execResults.push({ ...alloc, exec_status: result.status, tx_hash: result.tx_hash });
|
|
6621
|
+
} catch (err) {
|
|
6622
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6623
|
+
process.stderr.write(` Error: ${msg}
|
|
6624
|
+
`);
|
|
6625
|
+
execResults.push({ ...alloc, exec_status: "error", exec_error: msg });
|
|
6626
|
+
}
|
|
6627
|
+
continue;
|
|
6628
|
+
}
|
|
6629
|
+
const lpMsg = `LP execution for type '${alloc["type"]}' pool '${alloc["pool"] ?? ""}' \u2014 requires manual token preparation (swap + addLiquidity not yet automated)`;
|
|
6630
|
+
process.stderr.write(` Warning: ${lpMsg}
|
|
6631
|
+
`);
|
|
6632
|
+
execResults.push({ ...alloc, exec_status: "skipped", exec_note: lpMsg });
|
|
6633
|
+
}
|
|
6634
|
+
process.stderr.write("\nAutopilot execution complete.\n");
|
|
6635
|
+
printOutput({ execution_results: execResults }, getOpts());
|
|
6636
|
+
});
|
|
6637
|
+
}
|
|
6638
|
+
|
|
6639
|
+
// src/commands/lending.ts
|
|
6640
|
+
function registerLending(parent, getOpts, makeExecutor2) {
|
|
6641
|
+
const lending = parent.command("lending").description("Lending operations: supply, borrow, repay, withdraw, rates, position");
|
|
6642
|
+
lending.command("rates").description("Show current lending rates").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--asset <token>", "Token symbol or address").action(async (opts) => {
|
|
6643
|
+
const chainName = parent.opts().chain ?? "hyperevm";
|
|
6644
|
+
const registry = Registry.loadEmbedded();
|
|
6645
|
+
const chain = registry.getChain(chainName);
|
|
6646
|
+
const protocol = registry.getProtocol(opts.protocol);
|
|
6647
|
+
const adapter = createLending(protocol, chain.effectiveRpcUrl());
|
|
6648
|
+
const asset = opts.asset.startsWith("0x") ? opts.asset : registry.resolveToken(chainName, opts.asset).address;
|
|
6649
|
+
const rates = await adapter.getRates(asset);
|
|
6650
|
+
printOutput(rates, getOpts());
|
|
6651
|
+
});
|
|
6652
|
+
lending.command("position").description("Show current lending position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--address <address>", "Wallet address to query").action(async (opts) => {
|
|
6653
|
+
const chainName = parent.opts().chain ?? "hyperevm";
|
|
6654
|
+
const registry = Registry.loadEmbedded();
|
|
6655
|
+
const chain = registry.getChain(chainName);
|
|
6656
|
+
const protocol = registry.getProtocol(opts.protocol);
|
|
6657
|
+
const adapter = createLending(protocol, chain.effectiveRpcUrl());
|
|
6658
|
+
const position = await adapter.getUserPosition(opts.address);
|
|
6659
|
+
printOutput(position, getOpts());
|
|
6660
|
+
});
|
|
6661
|
+
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) => {
|
|
6662
|
+
const executor = makeExecutor2();
|
|
6663
|
+
const chainName = parent.opts().chain ?? "hyperevm";
|
|
6664
|
+
const registry = Registry.loadEmbedded();
|
|
6665
|
+
const chain = registry.getChain(chainName);
|
|
7260
6666
|
const protocol = registry.getProtocol(opts.protocol);
|
|
7261
6667
|
const adapter = createLending(protocol, chain.effectiveRpcUrl());
|
|
7262
6668
|
const asset = opts.asset.startsWith("0x") ? opts.asset : registry.resolveToken(chainName, opts.asset).address;
|
|
@@ -7318,115 +6724,6 @@ function registerLending(parent, getOpts, makeExecutor2) {
|
|
|
7318
6724
|
});
|
|
7319
6725
|
}
|
|
7320
6726
|
|
|
7321
|
-
// src/commands/cdp.ts
|
|
7322
|
-
function registerCdp(parent, getOpts, makeExecutor2) {
|
|
7323
|
-
const cdp = parent.command("cdp").description("CDP operations: open, adjust, close, info");
|
|
7324
|
-
cdp.command("open").description("Open a new CDP position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--collateral <token>", "Collateral token address").requiredOption("--amount <amount>", "Collateral amount in wei").requiredOption("--mint <amount>", "Stablecoin to mint in wei").option("--recipient <address>", "Recipient address").action(async (opts) => {
|
|
7325
|
-
const executor = makeExecutor2();
|
|
7326
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7327
|
-
const registry = Registry.loadEmbedded();
|
|
7328
|
-
const chain = registry.getChain(chainName);
|
|
7329
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7330
|
-
const adapter = createCdp(protocol, chain.effectiveRpcUrl());
|
|
7331
|
-
const recipient = opts.recipient ?? process.env.DEFI_WALLET_ADDRESS ?? "0x0000000000000000000000000000000000000001";
|
|
7332
|
-
const tx = await adapter.buildOpen({
|
|
7333
|
-
protocol: protocol.name,
|
|
7334
|
-
collateral: opts.collateral,
|
|
7335
|
-
collateral_amount: BigInt(opts.amount),
|
|
7336
|
-
debt_amount: BigInt(opts.mint),
|
|
7337
|
-
recipient
|
|
7338
|
-
});
|
|
7339
|
-
const result = await executor.execute(tx);
|
|
7340
|
-
printOutput(result, getOpts());
|
|
7341
|
-
});
|
|
7342
|
-
cdp.command("info").description("Show CDP position info, or protocol overview if --position is omitted").requiredOption("--protocol <protocol>", "Protocol slug").option("--position <id>", "CDP/trove ID (omit for protocol overview)").action(async (opts) => {
|
|
7343
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7344
|
-
const registry = Registry.loadEmbedded();
|
|
7345
|
-
const chain = registry.getChain(chainName);
|
|
7346
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7347
|
-
if (opts.position === void 0) {
|
|
7348
|
-
printOutput({
|
|
7349
|
-
name: protocol.name,
|
|
7350
|
-
slug: protocol.slug,
|
|
7351
|
-
chain: chainName,
|
|
7352
|
-
contracts: protocol.contracts ?? {}
|
|
7353
|
-
}, getOpts());
|
|
7354
|
-
return;
|
|
7355
|
-
}
|
|
7356
|
-
const adapter = createCdp(protocol, chain.effectiveRpcUrl());
|
|
7357
|
-
const info = await adapter.getCdpInfo(BigInt(opts.position));
|
|
7358
|
-
printOutput(info, getOpts());
|
|
7359
|
-
});
|
|
7360
|
-
cdp.command("adjust").description("Adjust an existing CDP position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--position <id>", "CDP/trove ID").option("--add-collateral <amount>", "Add collateral in wei").option("--withdraw-collateral <amount>", "Withdraw collateral in wei").option("--mint <amount>", "Mint additional stablecoin").option("--repay <amount>", "Repay stablecoin").action(async (opts) => {
|
|
7361
|
-
const executor = makeExecutor2();
|
|
7362
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7363
|
-
const registry = Registry.loadEmbedded();
|
|
7364
|
-
const chain = registry.getChain(chainName);
|
|
7365
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7366
|
-
const adapter = createCdp(protocol, chain.effectiveRpcUrl());
|
|
7367
|
-
const tx = await adapter.buildAdjust({
|
|
7368
|
-
protocol: protocol.name,
|
|
7369
|
-
cdp_id: BigInt(opts.position),
|
|
7370
|
-
collateral_delta: opts.addCollateral ? BigInt(opts.addCollateral) : opts.withdrawCollateral ? BigInt(opts.withdrawCollateral) : void 0,
|
|
7371
|
-
debt_delta: opts.mint ? BigInt(opts.mint) : opts.repay ? BigInt(opts.repay) : void 0,
|
|
7372
|
-
add_collateral: !!opts.addCollateral,
|
|
7373
|
-
add_debt: !!opts.mint
|
|
7374
|
-
});
|
|
7375
|
-
const result = await executor.execute(tx);
|
|
7376
|
-
printOutput(result, getOpts());
|
|
7377
|
-
});
|
|
7378
|
-
cdp.command("close").description("Close a CDP position").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--position <id>", "CDP/trove ID").action(async (opts) => {
|
|
7379
|
-
const executor = makeExecutor2();
|
|
7380
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7381
|
-
const registry = Registry.loadEmbedded();
|
|
7382
|
-
const chain = registry.getChain(chainName);
|
|
7383
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7384
|
-
const adapter = createCdp(protocol, chain.effectiveRpcUrl());
|
|
7385
|
-
const tx = await adapter.buildClose({ protocol: protocol.name, cdp_id: BigInt(opts.position) });
|
|
7386
|
-
const result = await executor.execute(tx);
|
|
7387
|
-
printOutput(result, getOpts());
|
|
7388
|
-
});
|
|
7389
|
-
}
|
|
7390
|
-
|
|
7391
|
-
// src/commands/vault.ts
|
|
7392
|
-
function registerVault(parent, getOpts, makeExecutor2) {
|
|
7393
|
-
const vault = parent.command("vault").description("Vault operations: deposit, withdraw, info");
|
|
7394
|
-
vault.command("deposit").description("Deposit assets into a vault").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--amount <amount>", "Amount in wei").option("--receiver <address>", "Receiver address for vault shares").action(async (opts) => {
|
|
7395
|
-
const executor = makeExecutor2();
|
|
7396
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7397
|
-
const registry = Registry.loadEmbedded();
|
|
7398
|
-
const chain = registry.getChain(chainName);
|
|
7399
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7400
|
-
const adapter = createVault(protocol, chain.effectiveRpcUrl());
|
|
7401
|
-
const receiver = opts.receiver ?? process.env.DEFI_WALLET_ADDRESS ?? "0x0000000000000000000000000000000000000001";
|
|
7402
|
-
const tx = await adapter.buildDeposit(BigInt(opts.amount), receiver);
|
|
7403
|
-
const result = await executor.execute(tx);
|
|
7404
|
-
printOutput(result, getOpts());
|
|
7405
|
-
});
|
|
7406
|
-
vault.command("withdraw").description("Withdraw assets from a vault").requiredOption("--protocol <protocol>", "Protocol slug").requiredOption("--amount <amount>", "Amount in wei (shares)").option("--receiver <address>", "Receiver address").option("--owner <address>", "Owner address").action(async (opts) => {
|
|
7407
|
-
const executor = makeExecutor2();
|
|
7408
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7409
|
-
const registry = Registry.loadEmbedded();
|
|
7410
|
-
const chain = registry.getChain(chainName);
|
|
7411
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7412
|
-
const adapter = createVault(protocol, chain.effectiveRpcUrl());
|
|
7413
|
-
const receiver = opts.receiver ?? process.env.DEFI_WALLET_ADDRESS ?? "0x0000000000000000000000000000000000000001";
|
|
7414
|
-
const owner = opts.owner ?? receiver;
|
|
7415
|
-
const tx = await adapter.buildWithdraw(BigInt(opts.amount), receiver, owner);
|
|
7416
|
-
const result = await executor.execute(tx);
|
|
7417
|
-
printOutput(result, getOpts());
|
|
7418
|
-
});
|
|
7419
|
-
vault.command("info").description("Show vault info (TVL, APY, shares)").requiredOption("--protocol <protocol>", "Protocol slug").action(async (opts) => {
|
|
7420
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
7421
|
-
const registry = Registry.loadEmbedded();
|
|
7422
|
-
const chain = registry.getChain(chainName);
|
|
7423
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
7424
|
-
const adapter = createVault(protocol, chain.effectiveRpcUrl());
|
|
7425
|
-
const info = await adapter.getVaultInfo();
|
|
7426
|
-
printOutput(info, getOpts());
|
|
7427
|
-
});
|
|
7428
|
-
}
|
|
7429
|
-
|
|
7430
6727
|
// src/commands/yield.ts
|
|
7431
6728
|
function resolveAsset(registry, chain, asset) {
|
|
7432
6729
|
if (/^0x[0-9a-fA-F]{40}$/.test(asset)) {
|
|
@@ -8040,9 +7337,9 @@ function registerYield(parent, getOpts, makeExecutor2) {
|
|
|
8040
7337
|
import { encodeFunctionData as encodeFunctionData28, parseAbi as parseAbi31 } from "viem";
|
|
8041
7338
|
|
|
8042
7339
|
// src/portfolio-tracker.ts
|
|
8043
|
-
import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as
|
|
7340
|
+
import { mkdirSync, writeFileSync, readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
8044
7341
|
import { homedir } from "os";
|
|
8045
|
-
import { resolve as
|
|
7342
|
+
import { resolve as resolve3 } from "path";
|
|
8046
7343
|
import { encodeFunctionData as encodeFunctionData27, parseAbi as parseAbi30 } from "viem";
|
|
8047
7344
|
var ERC20_ABI4 = parseAbi30([
|
|
8048
7345
|
"function balanceOf(address owner) external view returns (uint256)"
|
|
@@ -8059,7 +7356,7 @@ function decodeU256Word(data, wordOffset = 0) {
|
|
|
8059
7356
|
return BigInt("0x" + hex);
|
|
8060
7357
|
}
|
|
8061
7358
|
function snapshotDir() {
|
|
8062
|
-
return
|
|
7359
|
+
return resolve3(homedir(), ".defi-cli", "snapshots");
|
|
8063
7360
|
}
|
|
8064
7361
|
async function takeSnapshot(chainName, wallet, registry) {
|
|
8065
7362
|
const chain = registry.getChain(chainName);
|
|
@@ -8173,7 +7470,7 @@ function saveSnapshot(snapshot) {
|
|
|
8173
7470
|
const dir = snapshotDir();
|
|
8174
7471
|
mkdirSync(dir, { recursive: true });
|
|
8175
7472
|
const filename = `${snapshot.chain}_${snapshot.wallet}_${snapshot.timestamp}.json`;
|
|
8176
|
-
const filepath =
|
|
7473
|
+
const filepath = resolve3(dir, filename);
|
|
8177
7474
|
writeFileSync(filepath, JSON.stringify(snapshot, (_k, v) => typeof v === "bigint" ? v.toString() : v, 2));
|
|
8178
7475
|
return filepath;
|
|
8179
7476
|
}
|
|
@@ -8183,7 +7480,7 @@ function loadSnapshots(chain, wallet, limit = 10) {
|
|
|
8183
7480
|
const prefix = `${chain}_${wallet}_`;
|
|
8184
7481
|
const files = readdirSync2(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".json")).sort().reverse().slice(0, limit);
|
|
8185
7482
|
return files.map((f) => {
|
|
8186
|
-
const raw = JSON.parse(
|
|
7483
|
+
const raw = JSON.parse(readFileSync3(resolve3(dir, f), "utf-8"));
|
|
8187
7484
|
if (Array.isArray(raw.tokens)) {
|
|
8188
7485
|
for (const t of raw.tokens) {
|
|
8189
7486
|
if (typeof t.balance === "string") t.balance = BigInt(t.balance);
|
|
@@ -8487,858 +7784,8 @@ function registerPortfolio(parent, getOpts) {
|
|
|
8487
7784
|
});
|
|
8488
7785
|
}
|
|
8489
7786
|
|
|
8490
|
-
// src/commands/monitor.ts
|
|
8491
|
-
async function checkChainLendingPositions(chainKey, registry, address, threshold) {
|
|
8492
|
-
let chain;
|
|
8493
|
-
try {
|
|
8494
|
-
chain = registry.getChain(chainKey);
|
|
8495
|
-
} catch {
|
|
8496
|
-
return [];
|
|
8497
|
-
}
|
|
8498
|
-
const rpc = chain.effectiveRpcUrl();
|
|
8499
|
-
const chainName = chain.name;
|
|
8500
|
-
const protocols = registry.getProtocolsForChain(chainKey).filter(
|
|
8501
|
-
(p) => p.category === ProtocolCategory.Lending
|
|
8502
|
-
);
|
|
8503
|
-
const results = await Promise.all(
|
|
8504
|
-
protocols.map(async (proto) => {
|
|
8505
|
-
try {
|
|
8506
|
-
const adapter = createLending(proto, rpc);
|
|
8507
|
-
const position = await adapter.getUserPosition(address);
|
|
8508
|
-
const hf = position.health_factor ?? Infinity;
|
|
8509
|
-
const totalBorrow = position.borrows?.reduce(
|
|
8510
|
-
(sum, b) => sum + (b.value_usd ?? 0),
|
|
8511
|
-
0
|
|
8512
|
-
) ?? 0;
|
|
8513
|
-
if (totalBorrow === 0) return null;
|
|
8514
|
-
const totalSupply = position.supplies?.reduce(
|
|
8515
|
-
(sum, s) => sum + (s.value_usd ?? 0),
|
|
8516
|
-
0
|
|
8517
|
-
) ?? 0;
|
|
8518
|
-
return {
|
|
8519
|
-
chain: chainName,
|
|
8520
|
-
protocol: proto.name,
|
|
8521
|
-
health_factor: hf === Infinity ? 999999 : Math.round(hf * 100) / 100,
|
|
8522
|
-
total_supply_usd: Math.round(totalSupply * 100) / 100,
|
|
8523
|
-
total_borrow_usd: Math.round(totalBorrow * 100) / 100,
|
|
8524
|
-
alert: hf < threshold
|
|
8525
|
-
};
|
|
8526
|
-
} catch {
|
|
8527
|
-
return null;
|
|
8528
|
-
}
|
|
8529
|
-
})
|
|
8530
|
-
);
|
|
8531
|
-
return results.filter((r) => r !== null);
|
|
8532
|
-
}
|
|
8533
|
-
function registerMonitor(parent, getOpts) {
|
|
8534
|
-
parent.command("monitor").description("Monitor health factor with alerts").option("--protocol <protocol>", "Protocol slug (required unless --all-chains)").requiredOption("--address <address>", "Wallet address to monitor").option("--threshold <hf>", "Health factor alert threshold", "1.5").option("--interval <secs>", "Polling interval in seconds", "60").option("--once", "Run once instead of continuously").option("--all-chains", "Scan all chains for lending positions").action(async (opts) => {
|
|
8535
|
-
const threshold = parseFloat(opts.threshold);
|
|
8536
|
-
const address = opts.address;
|
|
8537
|
-
if (opts.allChains) {
|
|
8538
|
-
const registry = Registry.loadEmbedded();
|
|
8539
|
-
const chainKeys = Array.from(registry.chains.keys());
|
|
8540
|
-
const poll = async () => {
|
|
8541
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8542
|
-
const chainResults = await Promise.all(
|
|
8543
|
-
chainKeys.map(
|
|
8544
|
-
(ck) => checkChainLendingPositions(ck, registry, address, threshold)
|
|
8545
|
-
)
|
|
8546
|
-
);
|
|
8547
|
-
const positions = chainResults.flat();
|
|
8548
|
-
const alertsCount = positions.filter((p) => p.alert).length;
|
|
8549
|
-
const output = {
|
|
8550
|
-
timestamp,
|
|
8551
|
-
address,
|
|
8552
|
-
threshold,
|
|
8553
|
-
positions,
|
|
8554
|
-
alerts_count: alertsCount
|
|
8555
|
-
};
|
|
8556
|
-
for (const pos of positions) {
|
|
8557
|
-
if (pos.alert) {
|
|
8558
|
-
process.stderr.write(
|
|
8559
|
-
`ALERT: ${pos.chain}/${pos.protocol} HF=${pos.health_factor} < ${threshold}
|
|
8560
|
-
`
|
|
8561
|
-
);
|
|
8562
|
-
}
|
|
8563
|
-
}
|
|
8564
|
-
printOutput(output, getOpts());
|
|
8565
|
-
};
|
|
8566
|
-
await poll();
|
|
8567
|
-
if (!opts.once) {
|
|
8568
|
-
const intervalMs = parseInt(opts.interval) * 1e3;
|
|
8569
|
-
const timer = setInterval(poll, intervalMs);
|
|
8570
|
-
process.on("SIGINT", () => {
|
|
8571
|
-
clearInterval(timer);
|
|
8572
|
-
process.exit(0);
|
|
8573
|
-
});
|
|
8574
|
-
}
|
|
8575
|
-
} else {
|
|
8576
|
-
if (!opts.protocol) {
|
|
8577
|
-
printOutput({ error: "Either --protocol or --all-chains is required" }, getOpts());
|
|
8578
|
-
process.exit(1);
|
|
8579
|
-
}
|
|
8580
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
8581
|
-
const registry = Registry.loadEmbedded();
|
|
8582
|
-
const chain = registry.getChain(chainName);
|
|
8583
|
-
const protocol = registry.getProtocol(opts.protocol);
|
|
8584
|
-
const adapter = createLending(protocol, chain.effectiveRpcUrl());
|
|
8585
|
-
const poll = async () => {
|
|
8586
|
-
try {
|
|
8587
|
-
const position = await adapter.getUserPosition(address);
|
|
8588
|
-
const hf = position.health_factor ?? Infinity;
|
|
8589
|
-
const alert = hf < threshold;
|
|
8590
|
-
printOutput({
|
|
8591
|
-
protocol: protocol.name,
|
|
8592
|
-
user: opts.address,
|
|
8593
|
-
health_factor: hf,
|
|
8594
|
-
threshold,
|
|
8595
|
-
alert,
|
|
8596
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8597
|
-
supplies: position.supplies,
|
|
8598
|
-
borrows: position.borrows
|
|
8599
|
-
}, getOpts());
|
|
8600
|
-
} catch (e) {
|
|
8601
|
-
printOutput({
|
|
8602
|
-
error: e instanceof Error ? e.message : String(e),
|
|
8603
|
-
protocol: protocol.name,
|
|
8604
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8605
|
-
}, getOpts());
|
|
8606
|
-
}
|
|
8607
|
-
};
|
|
8608
|
-
await poll();
|
|
8609
|
-
if (!opts.once) {
|
|
8610
|
-
const intervalMs = parseInt(opts.interval) * 1e3;
|
|
8611
|
-
const timer = setInterval(poll, intervalMs);
|
|
8612
|
-
process.on("SIGINT", () => {
|
|
8613
|
-
clearInterval(timer);
|
|
8614
|
-
process.exit(0);
|
|
8615
|
-
});
|
|
8616
|
-
}
|
|
8617
|
-
}
|
|
8618
|
-
});
|
|
8619
|
-
}
|
|
8620
|
-
|
|
8621
|
-
// src/commands/alert.ts
|
|
8622
|
-
function registerAlert(parent, getOpts) {
|
|
8623
|
-
parent.command("alert").description("Alert on DEX vs Oracle price deviation").option("--threshold <pct>", "Deviation threshold in percent", "5.0").option("--once", "Run once instead of continuously").option("--interval <secs>", "Polling interval in seconds", "60").action(async (opts) => {
|
|
8624
|
-
const chainName = parent.opts().chain ?? "hyperevm";
|
|
8625
|
-
const registry = Registry.loadEmbedded();
|
|
8626
|
-
const chain = registry.getChain(chainName);
|
|
8627
|
-
const rpcUrl = chain.effectiveRpcUrl();
|
|
8628
|
-
const threshold = parseFloat(opts.threshold);
|
|
8629
|
-
const dexProtocols = registry.getProtocolsByCategory("dex").filter((p) => p.chain === chainName);
|
|
8630
|
-
const lendingProtocols = registry.getProtocolsByCategory("lending").filter((p) => p.chain === chainName);
|
|
8631
|
-
const poll = async () => {
|
|
8632
|
-
const alerts = [];
|
|
8633
|
-
for (const p of dexProtocols) {
|
|
8634
|
-
try {
|
|
8635
|
-
const dex = createDex(p, rpcUrl);
|
|
8636
|
-
alerts.push({
|
|
8637
|
-
protocol: p.name,
|
|
8638
|
-
type: "info",
|
|
8639
|
-
message: `DEX ${dex.name()} active on ${chainName}`
|
|
8640
|
-
});
|
|
8641
|
-
} catch {
|
|
8642
|
-
}
|
|
8643
|
-
}
|
|
8644
|
-
printOutput({
|
|
8645
|
-
chain: chainName,
|
|
8646
|
-
threshold_pct: threshold,
|
|
8647
|
-
alerts,
|
|
8648
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8649
|
-
}, getOpts());
|
|
8650
|
-
};
|
|
8651
|
-
await poll();
|
|
8652
|
-
if (!opts.once) {
|
|
8653
|
-
const intervalMs = parseInt(opts.interval) * 1e3;
|
|
8654
|
-
const timer = setInterval(poll, intervalMs);
|
|
8655
|
-
process.on("SIGINT", () => {
|
|
8656
|
-
clearInterval(timer);
|
|
8657
|
-
process.exit(0);
|
|
8658
|
-
});
|
|
8659
|
-
}
|
|
8660
|
-
});
|
|
8661
|
-
}
|
|
8662
|
-
|
|
8663
|
-
// src/commands/scan.ts
|
|
8664
|
-
import { encodeFunctionData as encodeFunctionData29, parseAbi as parseAbi33 } from "viem";
|
|
8665
|
-
var AAVE_ORACLE_ABI = parseAbi33([
|
|
8666
|
-
"function getAssetPrice(address asset) external view returns (uint256)"
|
|
8667
|
-
]);
|
|
8668
|
-
var UNIV2_ROUTER_ABI = parseAbi33([
|
|
8669
|
-
"function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory)"
|
|
8670
|
-
]);
|
|
8671
|
-
var VTOKEN_ABI = parseAbi33([
|
|
8672
|
-
"function exchangeRateStored() external view returns (uint256)"
|
|
8673
|
-
]);
|
|
8674
|
-
var STABLECOINS = /* @__PURE__ */ new Set(["USDC", "USDT", "DAI", "USDT0"]);
|
|
8675
|
-
function round2(x) {
|
|
8676
|
-
return Math.round(x * 100) / 100;
|
|
8677
|
-
}
|
|
8678
|
-
function round4(x) {
|
|
8679
|
-
return Math.round(x * 1e4) / 1e4;
|
|
8680
|
-
}
|
|
8681
|
-
function round6(x) {
|
|
8682
|
-
return Math.round(x * 1e6) / 1e6;
|
|
8683
|
-
}
|
|
8684
|
-
function parseU256F64(data, decimals) {
|
|
8685
|
-
if (!data || data.length < 66) return 0;
|
|
8686
|
-
const raw = BigInt(data.slice(0, 66));
|
|
8687
|
-
return Number(raw) / 10 ** decimals;
|
|
8688
|
-
}
|
|
8689
|
-
function parseAmountsOutLast(data, outDecimals) {
|
|
8690
|
-
if (!data) return 0;
|
|
8691
|
-
const hex = data.startsWith("0x") ? data.slice(2) : data;
|
|
8692
|
-
if (hex.length < 128) return 0;
|
|
8693
|
-
const num = parseInt(hex.slice(64, 128), 16);
|
|
8694
|
-
if (num === 0) return 0;
|
|
8695
|
-
const byteOff = 64 + (num - 1) * 32;
|
|
8696
|
-
const hexOff = byteOff * 2;
|
|
8697
|
-
if (hex.length < hexOff + 64) return 0;
|
|
8698
|
-
const val = BigInt("0x" + hex.slice(hexOff, hexOff + 64));
|
|
8699
|
-
return Number(val) / 10 ** outDecimals;
|
|
8700
|
-
}
|
|
8701
|
-
function registerScan(parent, getOpts) {
|
|
8702
|
-
parent.command("scan").description("Multi-pattern exploit detection scanner").option("--chain <chain>", "Chain to scan", "hyperevm").option("--patterns <patterns>", "Comma-separated patterns: oracle,stable,exchange_rate", "oracle,stable,exchange_rate").option("--oracle-threshold <pct>", "Oracle divergence threshold (percent)", "5.0").option("--stable-threshold <price>", "Stablecoin depeg threshold (min price)", "0.98").option("--rate-threshold <pct>", "Exchange rate change threshold (percent)", "5.0").option("--interval <secs>", "Polling interval in seconds", "30").option("--once", "Single check then exit").option("--all-chains", "Scan all chains in parallel").action(async (opts) => {
|
|
8703
|
-
try {
|
|
8704
|
-
const registry = Registry.loadEmbedded();
|
|
8705
|
-
const oracleThreshold = parseFloat(opts.oracleThreshold ?? "5.0");
|
|
8706
|
-
const stableThreshold = parseFloat(opts.stableThreshold ?? "0.98");
|
|
8707
|
-
const rateThreshold = parseFloat(opts.rateThreshold ?? "5.0");
|
|
8708
|
-
const interval = parseInt(opts.interval ?? "30", 10);
|
|
8709
|
-
const patterns = opts.patterns ?? "oracle,stable,exchange_rate";
|
|
8710
|
-
const once = !!opts.once;
|
|
8711
|
-
if (opts.allChains) {
|
|
8712
|
-
const result = await runAllChains(registry, patterns, oracleThreshold, stableThreshold, rateThreshold);
|
|
8713
|
-
printOutput(result, getOpts());
|
|
8714
|
-
return;
|
|
8715
|
-
}
|
|
8716
|
-
const chainName = (opts.chain ?? "hyperevm").toLowerCase();
|
|
8717
|
-
const chain = registry.getChain(chainName);
|
|
8718
|
-
const rpc = chain.effectiveRpcUrl();
|
|
8719
|
-
const pats = patterns.split(",").map((s) => s.trim());
|
|
8720
|
-
const doOracle = pats.includes("oracle");
|
|
8721
|
-
const doStable = pats.includes("stable");
|
|
8722
|
-
const doRate = pats.includes("exchange_rate");
|
|
8723
|
-
const allTokens = registry.tokens.get(chainName) ?? [];
|
|
8724
|
-
const wrappedNative = chain.wrapped_native;
|
|
8725
|
-
const quoteStable = (() => {
|
|
8726
|
-
for (const sym of ["USDT", "USDC", "USDT0"]) {
|
|
8727
|
-
try {
|
|
8728
|
-
return registry.resolveToken(chainName, sym);
|
|
8729
|
-
} catch {
|
|
8730
|
-
}
|
|
8731
|
-
}
|
|
8732
|
-
return null;
|
|
8733
|
-
})();
|
|
8734
|
-
if (!quoteStable) {
|
|
8735
|
-
printOutput({ error: `No stablecoin found on chain ${chainName}` }, getOpts());
|
|
8736
|
-
return;
|
|
8737
|
-
}
|
|
8738
|
-
const scanTokens = allTokens.filter(
|
|
8739
|
-
(t) => t.address !== "0x0000000000000000000000000000000000000000" && !STABLECOINS.has(t.symbol)
|
|
8740
|
-
);
|
|
8741
|
-
const oracles = registry.getProtocolsForChain(chainName).filter(
|
|
8742
|
-
(p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2" || p.interface === "aave_v3_isolated")
|
|
8743
|
-
).flatMap((p) => {
|
|
8744
|
-
const oracleAddr = p.contracts?.["oracle"];
|
|
8745
|
-
if (!oracleAddr) return [];
|
|
8746
|
-
const decimals = p.interface === "aave_v2" ? 18 : 8;
|
|
8747
|
-
return [{ name: p.name, addr: oracleAddr, decimals }];
|
|
8748
|
-
});
|
|
8749
|
-
const dexProto = registry.getProtocolsForChain(chainName).find((p) => p.category === ProtocolCategory.Dex && p.interface === "uniswap_v2");
|
|
8750
|
-
const dexRouter = dexProto?.contracts?.["router"];
|
|
8751
|
-
const compoundForks = registry.getProtocolsForChain(chainName).filter((p) => p.category === ProtocolCategory.Lending && p.interface === "compound_v2").map((p) => ({
|
|
8752
|
-
name: p.name,
|
|
8753
|
-
vtokens: Object.entries(p.contracts ?? {}).filter(([k]) => k.startsWith("v")).map(([k, a]) => ({ key: k, addr: a }))
|
|
8754
|
-
}));
|
|
8755
|
-
const usdc = (() => {
|
|
8756
|
-
try {
|
|
8757
|
-
return registry.resolveToken(chainName, "USDC");
|
|
8758
|
-
} catch {
|
|
8759
|
-
return null;
|
|
8760
|
-
}
|
|
8761
|
-
})();
|
|
8762
|
-
const usdt = (() => {
|
|
8763
|
-
try {
|
|
8764
|
-
return registry.resolveToken(chainName, "USDT");
|
|
8765
|
-
} catch {
|
|
8766
|
-
return null;
|
|
8767
|
-
}
|
|
8768
|
-
})();
|
|
8769
|
-
const prevRates = /* @__PURE__ */ new Map();
|
|
8770
|
-
const runOnce = async () => {
|
|
8771
|
-
const timestamp = Math.floor(Date.now() / 1e3);
|
|
8772
|
-
const t0 = Date.now();
|
|
8773
|
-
const calls = [];
|
|
8774
|
-
const callTypes = [];
|
|
8775
|
-
if (doOracle) {
|
|
8776
|
-
for (const oracle of oracles) {
|
|
8777
|
-
for (const token of scanTokens) {
|
|
8778
|
-
callTypes.push({ kind: "oracle", oracle: oracle.name, token: token.symbol, oracleDecimals: oracle.decimals });
|
|
8779
|
-
calls.push([
|
|
8780
|
-
oracle.addr,
|
|
8781
|
-
encodeFunctionData29({ abi: AAVE_ORACLE_ABI, functionName: "getAssetPrice", args: [token.address] })
|
|
8782
|
-
]);
|
|
8783
|
-
}
|
|
8784
|
-
}
|
|
8785
|
-
if (dexRouter) {
|
|
8786
|
-
for (const token of scanTokens) {
|
|
8787
|
-
const amountIn = BigInt(10) ** BigInt(token.decimals);
|
|
8788
|
-
const path = wrappedNative && token.address.toLowerCase() === wrappedNative.toLowerCase() ? [token.address, quoteStable.address] : wrappedNative ? [token.address, wrappedNative, quoteStable.address] : [token.address, quoteStable.address];
|
|
8789
|
-
callTypes.push({ kind: "dex", token: token.symbol, outDecimals: quoteStable.decimals });
|
|
8790
|
-
calls.push([
|
|
8791
|
-
dexRouter,
|
|
8792
|
-
encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [amountIn, path] })
|
|
8793
|
-
]);
|
|
8794
|
-
}
|
|
8795
|
-
}
|
|
8796
|
-
}
|
|
8797
|
-
if (doStable && usdc && usdt && dexRouter) {
|
|
8798
|
-
callTypes.push({ kind: "stable", from: "USDC", to: "USDT", outDecimals: usdt.decimals });
|
|
8799
|
-
calls.push([
|
|
8800
|
-
dexRouter,
|
|
8801
|
-
encodeFunctionData29({
|
|
8802
|
-
abi: UNIV2_ROUTER_ABI,
|
|
8803
|
-
functionName: "getAmountsOut",
|
|
8804
|
-
args: [BigInt(10) ** BigInt(usdc.decimals), [usdc.address, usdt.address]]
|
|
8805
|
-
})
|
|
8806
|
-
]);
|
|
8807
|
-
callTypes.push({ kind: "stable", from: "USDT", to: "USDC", outDecimals: usdc.decimals });
|
|
8808
|
-
calls.push([
|
|
8809
|
-
dexRouter,
|
|
8810
|
-
encodeFunctionData29({
|
|
8811
|
-
abi: UNIV2_ROUTER_ABI,
|
|
8812
|
-
functionName: "getAmountsOut",
|
|
8813
|
-
args: [BigInt(10) ** BigInt(usdt.decimals), [usdt.address, usdc.address]]
|
|
8814
|
-
})
|
|
8815
|
-
]);
|
|
8816
|
-
}
|
|
8817
|
-
if (doRate) {
|
|
8818
|
-
for (const fork of compoundForks) {
|
|
8819
|
-
for (const { key, addr } of fork.vtokens) {
|
|
8820
|
-
callTypes.push({ kind: "exchangeRate", protocol: fork.name, vtoken: key });
|
|
8821
|
-
calls.push([addr, encodeFunctionData29({ abi: VTOKEN_ABI, functionName: "exchangeRateStored", args: [] })]);
|
|
8822
|
-
}
|
|
8823
|
-
}
|
|
8824
|
-
}
|
|
8825
|
-
if (calls.length === 0) {
|
|
8826
|
-
printOutput({ error: `No scannable resources found on ${chainName}` }, getOpts());
|
|
8827
|
-
return;
|
|
8828
|
-
}
|
|
8829
|
-
const results = await multicallRead(rpc, calls);
|
|
8830
|
-
const scanMs = Date.now() - t0;
|
|
8831
|
-
const alerts = [];
|
|
8832
|
-
const oracleByToken = /* @__PURE__ */ new Map();
|
|
8833
|
-
const dexByToken = /* @__PURE__ */ new Map();
|
|
8834
|
-
const oracleData = {};
|
|
8835
|
-
const dexData = {};
|
|
8836
|
-
const stableData = {};
|
|
8837
|
-
const stablePrices = [];
|
|
8838
|
-
const rateData = {};
|
|
8839
|
-
for (let i = 0; i < callTypes.length; i++) {
|
|
8840
|
-
const ct = callTypes[i];
|
|
8841
|
-
const raw = results[i] ?? null;
|
|
8842
|
-
if (ct.kind === "oracle") {
|
|
8843
|
-
const price = parseU256F64(raw, ct.oracleDecimals);
|
|
8844
|
-
if (price > 0) {
|
|
8845
|
-
const existing = oracleByToken.get(ct.token) ?? [];
|
|
8846
|
-
existing.push({ oracle: ct.oracle, price });
|
|
8847
|
-
oracleByToken.set(ct.token, existing);
|
|
8848
|
-
oracleData[`${ct.oracle}/${ct.token}`] = round4(price);
|
|
8849
|
-
}
|
|
8850
|
-
} else if (ct.kind === "dex") {
|
|
8851
|
-
const price = parseAmountsOutLast(raw, ct.outDecimals);
|
|
8852
|
-
if (price > 0) {
|
|
8853
|
-
dexByToken.set(ct.token, price);
|
|
8854
|
-
dexData[ct.token] = round4(price);
|
|
8855
|
-
}
|
|
8856
|
-
} else if (ct.kind === "stable") {
|
|
8857
|
-
const price = parseAmountsOutLast(raw, ct.outDecimals);
|
|
8858
|
-
if (price <= 0) continue;
|
|
8859
|
-
const pair = `${ct.from}/${ct.to}`;
|
|
8860
|
-
stableData[pair] = round4(price);
|
|
8861
|
-
stablePrices.push({ asset: ct.from, pair, price });
|
|
8862
|
-
} else if (ct.kind === "exchangeRate") {
|
|
8863
|
-
const rate = parseU256F64(raw, 18);
|
|
8864
|
-
const key = `${ct.protocol}/${ct.vtoken}`;
|
|
8865
|
-
rateData[key] = round6(rate);
|
|
8866
|
-
if (rate > 0) {
|
|
8867
|
-
const prev = prevRates.get(key);
|
|
8868
|
-
if (prev !== void 0) {
|
|
8869
|
-
const change = Math.abs((rate - prev) / prev * 100);
|
|
8870
|
-
if (change > rateThreshold) {
|
|
8871
|
-
const severity = change > 50 ? "critical" : change > 20 ? "high" : "medium";
|
|
8872
|
-
alerts.push({
|
|
8873
|
-
pattern: "exchange_rate_anomaly",
|
|
8874
|
-
severity,
|
|
8875
|
-
protocol: ct.protocol,
|
|
8876
|
-
vtoken: ct.vtoken,
|
|
8877
|
-
prev_rate: round6(prev),
|
|
8878
|
-
curr_rate: round6(rate),
|
|
8879
|
-
change_pct: round2(change),
|
|
8880
|
-
action: `possible donation attack on ${ct.protocol} ${ct.vtoken}`
|
|
8881
|
-
});
|
|
8882
|
-
}
|
|
8883
|
-
}
|
|
8884
|
-
prevRates.set(key, rate);
|
|
8885
|
-
}
|
|
8886
|
-
}
|
|
8887
|
-
}
|
|
8888
|
-
if (stablePrices.length >= 2) {
|
|
8889
|
-
const allBelow = stablePrices.every((s) => s.price < stableThreshold);
|
|
8890
|
-
if (!allBelow) {
|
|
8891
|
-
for (const { asset, pair, price } of stablePrices) {
|
|
8892
|
-
if (price < stableThreshold) {
|
|
8893
|
-
const severity = price < 0.95 ? "critical" : "high";
|
|
8894
|
-
alerts.push({
|
|
8895
|
-
pattern: "stablecoin_depeg",
|
|
8896
|
-
severity,
|
|
8897
|
-
asset,
|
|
8898
|
-
pair,
|
|
8899
|
-
price: round4(price),
|
|
8900
|
-
threshold: stableThreshold,
|
|
8901
|
-
action: `buy ${asset} at $${round4(price)}, wait for repeg`
|
|
8902
|
-
});
|
|
8903
|
-
}
|
|
8904
|
-
}
|
|
8905
|
-
}
|
|
8906
|
-
} else {
|
|
8907
|
-
for (const { asset, pair, price } of stablePrices) {
|
|
8908
|
-
if (price < stableThreshold) {
|
|
8909
|
-
const severity = price < 0.95 ? "critical" : "high";
|
|
8910
|
-
alerts.push({
|
|
8911
|
-
pattern: "stablecoin_depeg",
|
|
8912
|
-
severity,
|
|
8913
|
-
asset,
|
|
8914
|
-
pair,
|
|
8915
|
-
price: round4(price),
|
|
8916
|
-
threshold: stableThreshold,
|
|
8917
|
-
action: `buy ${asset} at $${round4(price)}, wait for repeg`
|
|
8918
|
-
});
|
|
8919
|
-
}
|
|
8920
|
-
}
|
|
8921
|
-
}
|
|
8922
|
-
if (doOracle) {
|
|
8923
|
-
for (const [token, oracleEntries] of oracleByToken) {
|
|
8924
|
-
const dexPrice = dexByToken.get(token);
|
|
8925
|
-
if (dexPrice === void 0) continue;
|
|
8926
|
-
for (const { oracle, price: oraclePrice } of oracleEntries) {
|
|
8927
|
-
if (dexPrice < oraclePrice && dexPrice < oraclePrice * 0.1) continue;
|
|
8928
|
-
const deviation = Math.abs(dexPrice - oraclePrice) / oraclePrice * 100;
|
|
8929
|
-
if (deviation > oracleThreshold) {
|
|
8930
|
-
const severity = deviation > 100 ? "critical" : deviation > 20 ? "high" : "medium";
|
|
8931
|
-
const action = dexPrice > oraclePrice ? `borrow ${token} from ${oracle}, sell on DEX` : `buy ${token} on DEX, use as collateral on ${oracle}`;
|
|
8932
|
-
alerts.push({
|
|
8933
|
-
pattern: "oracle_divergence",
|
|
8934
|
-
severity,
|
|
8935
|
-
asset: token,
|
|
8936
|
-
oracle,
|
|
8937
|
-
oracle_price: round4(oraclePrice),
|
|
8938
|
-
dex_price: round4(dexPrice),
|
|
8939
|
-
deviation_pct: round2(deviation),
|
|
8940
|
-
action
|
|
8941
|
-
});
|
|
8942
|
-
}
|
|
8943
|
-
}
|
|
8944
|
-
}
|
|
8945
|
-
}
|
|
8946
|
-
const data = {};
|
|
8947
|
-
if (Object.keys(oracleData).length > 0) data["oracle_prices"] = oracleData;
|
|
8948
|
-
if (Object.keys(dexData).length > 0) data["dex_prices"] = dexData;
|
|
8949
|
-
if (Object.keys(stableData).length > 0) data["stablecoin_pegs"] = stableData;
|
|
8950
|
-
if (Object.keys(rateData).length > 0) data["exchange_rates"] = rateData;
|
|
8951
|
-
const output = {
|
|
8952
|
-
timestamp,
|
|
8953
|
-
chain: chain.name,
|
|
8954
|
-
scan_duration_ms: scanMs,
|
|
8955
|
-
patterns,
|
|
8956
|
-
alert_count: alerts.length,
|
|
8957
|
-
alerts,
|
|
8958
|
-
data
|
|
8959
|
-
};
|
|
8960
|
-
for (const alert of alerts) {
|
|
8961
|
-
process.stderr.write(
|
|
8962
|
-
`ALERT [${alert["severity"]}]: ${alert["pattern"]} \u2014 ${alert["action"]}
|
|
8963
|
-
`
|
|
8964
|
-
);
|
|
8965
|
-
}
|
|
8966
|
-
printOutput(output, getOpts());
|
|
8967
|
-
};
|
|
8968
|
-
await runOnce();
|
|
8969
|
-
if (!once) {
|
|
8970
|
-
const intervalMs = interval * 1e3;
|
|
8971
|
-
const loop = async () => {
|
|
8972
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
8973
|
-
await runOnce();
|
|
8974
|
-
void loop();
|
|
8975
|
-
};
|
|
8976
|
-
await loop();
|
|
8977
|
-
}
|
|
8978
|
-
} catch (err) {
|
|
8979
|
-
printOutput({ error: String(err) }, getOpts());
|
|
8980
|
-
process.exit(1);
|
|
8981
|
-
}
|
|
8982
|
-
});
|
|
8983
|
-
}
|
|
8984
|
-
async function runAllChains(registry, patterns, oracleThreshold, stableThreshold, _rateThreshold) {
|
|
8985
|
-
const t0 = Date.now();
|
|
8986
|
-
const chainKeys = Array.from(registry.chains.keys());
|
|
8987
|
-
const tasks = chainKeys.map(async (ck) => {
|
|
8988
|
-
try {
|
|
8989
|
-
const chain = registry.getChain(ck);
|
|
8990
|
-
const rpc = chain.effectiveRpcUrl();
|
|
8991
|
-
const chainName = chain.name.toLowerCase();
|
|
8992
|
-
const allTokens = registry.tokens.get(chainName) ?? [];
|
|
8993
|
-
const wrappedNative = chain.wrapped_native;
|
|
8994
|
-
const quoteStable = (() => {
|
|
8995
|
-
for (const sym of ["USDT", "USDC", "USDT0"]) {
|
|
8996
|
-
try {
|
|
8997
|
-
return registry.resolveToken(chainName, sym);
|
|
8998
|
-
} catch {
|
|
8999
|
-
}
|
|
9000
|
-
}
|
|
9001
|
-
return null;
|
|
9002
|
-
})();
|
|
9003
|
-
if (!quoteStable) return null;
|
|
9004
|
-
const scanTokens = allTokens.filter(
|
|
9005
|
-
(t) => t.address !== "0x0000000000000000000000000000000000000000" && !STABLECOINS.has(t.symbol)
|
|
9006
|
-
);
|
|
9007
|
-
const pats = patterns.split(",").map((s) => s.trim());
|
|
9008
|
-
const doOracle = pats.includes("oracle");
|
|
9009
|
-
const doStable = pats.includes("stable");
|
|
9010
|
-
const oracles = registry.getProtocolsForChain(chainName).filter(
|
|
9011
|
-
(p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2" || p.interface === "aave_v3_isolated")
|
|
9012
|
-
).flatMap((p) => {
|
|
9013
|
-
const oracleAddr = p.contracts?.["oracle"];
|
|
9014
|
-
if (!oracleAddr) return [];
|
|
9015
|
-
return [{ name: p.name, addr: oracleAddr, decimals: p.interface === "aave_v2" ? 18 : 8 }];
|
|
9016
|
-
});
|
|
9017
|
-
const dexProto = registry.getProtocolsForChain(chainName).find((p) => p.category === ProtocolCategory.Dex && p.interface === "uniswap_v2");
|
|
9018
|
-
const dexRouter = dexProto?.contracts?.["router"];
|
|
9019
|
-
const usdc = (() => {
|
|
9020
|
-
try {
|
|
9021
|
-
return registry.resolveToken(chainName, "USDC");
|
|
9022
|
-
} catch {
|
|
9023
|
-
return null;
|
|
9024
|
-
}
|
|
9025
|
-
})();
|
|
9026
|
-
const usdt = (() => {
|
|
9027
|
-
try {
|
|
9028
|
-
return registry.resolveToken(chainName, "USDT");
|
|
9029
|
-
} catch {
|
|
9030
|
-
return null;
|
|
9031
|
-
}
|
|
9032
|
-
})();
|
|
9033
|
-
const calls = [];
|
|
9034
|
-
const cts = [];
|
|
9035
|
-
if (doOracle) {
|
|
9036
|
-
for (const oracle of oracles) {
|
|
9037
|
-
for (const token of scanTokens) {
|
|
9038
|
-
cts.push({ kind: "oracle", oracle: oracle.name, token: token.symbol, dec: oracle.decimals });
|
|
9039
|
-
calls.push([oracle.addr, encodeFunctionData29({ abi: AAVE_ORACLE_ABI, functionName: "getAssetPrice", args: [token.address] })]);
|
|
9040
|
-
}
|
|
9041
|
-
}
|
|
9042
|
-
if (dexRouter) {
|
|
9043
|
-
for (const token of scanTokens) {
|
|
9044
|
-
const path = wrappedNative && token.address.toLowerCase() === wrappedNative.toLowerCase() ? [token.address, quoteStable.address] : wrappedNative ? [token.address, wrappedNative, quoteStable.address] : [token.address, quoteStable.address];
|
|
9045
|
-
cts.push({ kind: "dex", token: token.symbol, dec: quoteStable.decimals });
|
|
9046
|
-
calls.push([dexRouter, encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [BigInt(10) ** BigInt(token.decimals), path] })]);
|
|
9047
|
-
}
|
|
9048
|
-
}
|
|
9049
|
-
}
|
|
9050
|
-
if (doStable && usdc && usdt && dexRouter) {
|
|
9051
|
-
cts.push({ kind: "stable", from: "USDC", to: "USDT", dec: usdt.decimals });
|
|
9052
|
-
calls.push([dexRouter, encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [BigInt(10) ** BigInt(usdc.decimals), [usdc.address, usdt.address]] })]);
|
|
9053
|
-
cts.push({ kind: "stable", from: "USDT", to: "USDC", dec: usdc.decimals });
|
|
9054
|
-
calls.push([dexRouter, encodeFunctionData29({ abi: UNIV2_ROUTER_ABI, functionName: "getAmountsOut", args: [BigInt(10) ** BigInt(usdt.decimals), [usdt.address, usdc.address]] })]);
|
|
9055
|
-
}
|
|
9056
|
-
if (calls.length === 0) return null;
|
|
9057
|
-
const ct0 = Date.now();
|
|
9058
|
-
const results = await multicallRead(rpc, calls);
|
|
9059
|
-
const scanMs = Date.now() - ct0;
|
|
9060
|
-
const alerts = [];
|
|
9061
|
-
const oracleByToken = /* @__PURE__ */ new Map();
|
|
9062
|
-
const dexByToken = /* @__PURE__ */ new Map();
|
|
9063
|
-
const stablePrices = [];
|
|
9064
|
-
for (let i = 0; i < cts.length; i++) {
|
|
9065
|
-
const ct = cts[i];
|
|
9066
|
-
const raw = results[i] ?? null;
|
|
9067
|
-
if (ct.kind === "oracle") {
|
|
9068
|
-
const price = parseU256F64(raw, ct.dec);
|
|
9069
|
-
if (price > 0) {
|
|
9070
|
-
const existing = oracleByToken.get(ct.token) ?? [];
|
|
9071
|
-
existing.push({ oracle: ct.oracle, price });
|
|
9072
|
-
oracleByToken.set(ct.token, existing);
|
|
9073
|
-
}
|
|
9074
|
-
} else if (ct.kind === "dex") {
|
|
9075
|
-
const price = parseAmountsOutLast(raw, ct.dec);
|
|
9076
|
-
if (price > 0) dexByToken.set(ct.token, price);
|
|
9077
|
-
} else if (ct.kind === "stable") {
|
|
9078
|
-
const price = parseAmountsOutLast(raw, ct.dec);
|
|
9079
|
-
if (price > 0) stablePrices.push({ asset: ct.from, pair: `${ct.from}/${ct.to}`, price });
|
|
9080
|
-
}
|
|
9081
|
-
}
|
|
9082
|
-
if (stablePrices.length >= 2) {
|
|
9083
|
-
const allBelow = stablePrices.every((s) => s.price < stableThreshold);
|
|
9084
|
-
if (!allBelow) {
|
|
9085
|
-
for (const { asset, pair, price } of stablePrices) {
|
|
9086
|
-
if (price < stableThreshold) {
|
|
9087
|
-
alerts.push({ pattern: "stablecoin_depeg", severity: price < 0.95 ? "critical" : "high", asset, pair, price: round4(price) });
|
|
9088
|
-
}
|
|
9089
|
-
}
|
|
9090
|
-
}
|
|
9091
|
-
}
|
|
9092
|
-
for (const [token, oEntries] of oracleByToken) {
|
|
9093
|
-
const dp = dexByToken.get(token);
|
|
9094
|
-
if (dp === void 0) continue;
|
|
9095
|
-
for (const { oracle, price: op } of oEntries) {
|
|
9096
|
-
if (dp < op && dp < op * 0.1) continue;
|
|
9097
|
-
const dev = Math.abs(dp - op) / op * 100;
|
|
9098
|
-
if (dev > oracleThreshold) {
|
|
9099
|
-
const sev = dev > 100 ? "critical" : dev > 20 ? "high" : "medium";
|
|
9100
|
-
alerts.push({
|
|
9101
|
-
pattern: "oracle_divergence",
|
|
9102
|
-
severity: sev,
|
|
9103
|
-
asset: token,
|
|
9104
|
-
oracle,
|
|
9105
|
-
oracle_price: round4(op),
|
|
9106
|
-
dex_price: round4(dp),
|
|
9107
|
-
deviation_pct: round2(dev),
|
|
9108
|
-
action: dp > op ? `borrow ${token} from ${oracle}, sell on DEX` : `buy ${token} on DEX, collateral on ${oracle}`
|
|
9109
|
-
});
|
|
9110
|
-
}
|
|
9111
|
-
}
|
|
9112
|
-
}
|
|
9113
|
-
return { chain: chain.name, scan_duration_ms: scanMs, alert_count: alerts.length, alerts };
|
|
9114
|
-
} catch {
|
|
9115
|
-
return null;
|
|
9116
|
-
}
|
|
9117
|
-
});
|
|
9118
|
-
const chainResults = (await Promise.all(tasks)).filter(Boolean);
|
|
9119
|
-
chainResults.sort((a, b) => {
|
|
9120
|
-
const ac = a["alert_count"] ?? 0;
|
|
9121
|
-
const bc = b["alert_count"] ?? 0;
|
|
9122
|
-
return bc - ac;
|
|
9123
|
-
});
|
|
9124
|
-
const totalAlerts = chainResults.reduce((sum, r) => sum + (r["alert_count"] ?? 0), 0);
|
|
9125
|
-
return {
|
|
9126
|
-
mode: "all_chains",
|
|
9127
|
-
chains_scanned: chainKeys.length,
|
|
9128
|
-
scan_duration_ms: Date.now() - t0,
|
|
9129
|
-
total_alerts: totalAlerts,
|
|
9130
|
-
chains: chainResults
|
|
9131
|
-
};
|
|
9132
|
-
}
|
|
9133
|
-
|
|
9134
|
-
// src/commands/positions.ts
|
|
9135
|
-
import { encodeFunctionData as encodeFunctionData30, parseAbi as parseAbi34 } from "viem";
|
|
9136
|
-
var ERC20_ABI6 = parseAbi34([
|
|
9137
|
-
"function balanceOf(address owner) external view returns (uint256)"
|
|
9138
|
-
]);
|
|
9139
|
-
var POOL_ABI5 = parseAbi34([
|
|
9140
|
-
"function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)"
|
|
9141
|
-
]);
|
|
9142
|
-
var ORACLE_ABI6 = parseAbi34([
|
|
9143
|
-
"function getAssetPrice(address asset) external view returns (uint256)"
|
|
9144
|
-
]);
|
|
9145
|
-
function round22(x) {
|
|
9146
|
-
return Math.round(x * 100) / 100;
|
|
9147
|
-
}
|
|
9148
|
-
function round42(x) {
|
|
9149
|
-
return Math.round(x * 1e4) / 1e4;
|
|
9150
|
-
}
|
|
9151
|
-
function estimateTokenValue(symbol, balance, nativePrice) {
|
|
9152
|
-
const s = symbol.toUpperCase();
|
|
9153
|
-
if (s.includes("USD") || s.includes("DAI")) return balance;
|
|
9154
|
-
if (s.includes("BTC") || s.includes("FBTC")) return balance * 75e3;
|
|
9155
|
-
if (["WETH", "ETH", "METH", "CBETH", "WSTETH"].includes(s)) return balance * 2350;
|
|
9156
|
-
return balance * nativePrice;
|
|
9157
|
-
}
|
|
9158
|
-
function decodeU2563(data, offset = 0) {
|
|
9159
|
-
if (!data || data.length < 2 + (offset + 32) * 2) return 0n;
|
|
9160
|
-
const hex = data.slice(2 + offset * 64, 2 + offset * 64 + 64);
|
|
9161
|
-
return BigInt("0x" + hex);
|
|
9162
|
-
}
|
|
9163
|
-
async function scanSingleChain(chainName, rpc, user, tokens, lendingPools, oracleAddr, wrappedNative) {
|
|
9164
|
-
const calls = [];
|
|
9165
|
-
const callTypes = [];
|
|
9166
|
-
for (const token of tokens) {
|
|
9167
|
-
if (token.address !== "0x0000000000000000000000000000000000000000") {
|
|
9168
|
-
callTypes.push({ kind: "token", symbol: token.symbol, decimals: token.decimals });
|
|
9169
|
-
calls.push([
|
|
9170
|
-
token.address,
|
|
9171
|
-
encodeFunctionData30({ abi: ERC20_ABI6, functionName: "balanceOf", args: [user] })
|
|
9172
|
-
]);
|
|
9173
|
-
}
|
|
9174
|
-
}
|
|
9175
|
-
for (const { name, pool, iface } of lendingPools) {
|
|
9176
|
-
callTypes.push({ kind: "lending", protocol: name, iface });
|
|
9177
|
-
calls.push([
|
|
9178
|
-
pool,
|
|
9179
|
-
encodeFunctionData30({ abi: POOL_ABI5, functionName: "getUserAccountData", args: [user] })
|
|
9180
|
-
]);
|
|
9181
|
-
}
|
|
9182
|
-
if (oracleAddr) {
|
|
9183
|
-
callTypes.push({ kind: "native_price" });
|
|
9184
|
-
calls.push([
|
|
9185
|
-
oracleAddr,
|
|
9186
|
-
encodeFunctionData30({ abi: ORACLE_ABI6, functionName: "getAssetPrice", args: [wrappedNative] })
|
|
9187
|
-
]);
|
|
9188
|
-
}
|
|
9189
|
-
if (calls.length === 0) return null;
|
|
9190
|
-
let results;
|
|
9191
|
-
try {
|
|
9192
|
-
results = await multicallRead(rpc, calls);
|
|
9193
|
-
} catch {
|
|
9194
|
-
return null;
|
|
9195
|
-
}
|
|
9196
|
-
const nativePrice = oracleAddr ? Number(decodeU2563(results[results.length - 1])) / 1e8 : 0;
|
|
9197
|
-
const tokenBalances = [];
|
|
9198
|
-
const lendingPositions = [];
|
|
9199
|
-
let chainValue = 0;
|
|
9200
|
-
let totalColl = 0;
|
|
9201
|
-
let totalDebt = 0;
|
|
9202
|
-
for (let i = 0; i < callTypes.length; i++) {
|
|
9203
|
-
const ct = callTypes[i];
|
|
9204
|
-
const data = results[i] ?? null;
|
|
9205
|
-
if (ct.kind === "token") {
|
|
9206
|
-
const balance = decodeU2563(data);
|
|
9207
|
-
if (balance > 0n) {
|
|
9208
|
-
const balF64 = Number(balance) / 10 ** ct.decimals;
|
|
9209
|
-
const valueUsd = estimateTokenValue(ct.symbol, balF64, nativePrice);
|
|
9210
|
-
if (valueUsd > 0.01) {
|
|
9211
|
-
chainValue += valueUsd;
|
|
9212
|
-
tokenBalances.push({
|
|
9213
|
-
symbol: ct.symbol,
|
|
9214
|
-
balance: round42(balF64),
|
|
9215
|
-
value_usd: round22(valueUsd)
|
|
9216
|
-
});
|
|
9217
|
-
}
|
|
9218
|
-
}
|
|
9219
|
-
} else if (ct.kind === "lending") {
|
|
9220
|
-
if (data && data.length >= 2 + 192 * 2) {
|
|
9221
|
-
const priceDecimals = ct.iface === "aave_v2" ? 18 : 8;
|
|
9222
|
-
const divisor = 10 ** priceDecimals;
|
|
9223
|
-
const collateral = Number(decodeU2563(data, 0)) / divisor;
|
|
9224
|
-
const debt = Number(decodeU2563(data, 1)) / divisor;
|
|
9225
|
-
const hfRaw = decodeU2563(data, 5);
|
|
9226
|
-
let hf = null;
|
|
9227
|
-
if (hfRaw <= BigInt("0xffffffffffffffffffffffffffffffff")) {
|
|
9228
|
-
const v = Number(hfRaw) / 1e18;
|
|
9229
|
-
hf = v > 1e10 ? null : round22(v);
|
|
9230
|
-
}
|
|
9231
|
-
if (collateral > 0.01 || debt > 0.01) {
|
|
9232
|
-
const net = collateral - debt;
|
|
9233
|
-
chainValue += net;
|
|
9234
|
-
totalColl += collateral;
|
|
9235
|
-
totalDebt += debt;
|
|
9236
|
-
lendingPositions.push({
|
|
9237
|
-
protocol: ct.protocol,
|
|
9238
|
-
collateral_usd: round22(collateral),
|
|
9239
|
-
debt_usd: round22(debt),
|
|
9240
|
-
net_usd: round22(net),
|
|
9241
|
-
health_factor: hf
|
|
9242
|
-
});
|
|
9243
|
-
}
|
|
9244
|
-
}
|
|
9245
|
-
}
|
|
9246
|
-
}
|
|
9247
|
-
if (tokenBalances.length === 0 && lendingPositions.length === 0) return null;
|
|
9248
|
-
return {
|
|
9249
|
-
chain_name: chainName,
|
|
9250
|
-
native_price: nativePrice,
|
|
9251
|
-
chain_value: chainValue,
|
|
9252
|
-
collateral: totalColl,
|
|
9253
|
-
debt: totalDebt,
|
|
9254
|
-
token_balances: tokenBalances,
|
|
9255
|
-
lending_positions: lendingPositions
|
|
9256
|
-
};
|
|
9257
|
-
}
|
|
9258
|
-
function registerPositions(parent, getOpts) {
|
|
9259
|
-
parent.command("positions").description("Cross-chain position scanner: find all your positions everywhere").requiredOption("--address <address>", "Wallet address to scan").option("--chains <chains>", "Comma-separated chain names (omit for all)").action(async (opts) => {
|
|
9260
|
-
const mode = getOpts();
|
|
9261
|
-
const registry = Registry.loadEmbedded();
|
|
9262
|
-
const user = opts.address;
|
|
9263
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(user)) {
|
|
9264
|
-
printOutput({ error: `Invalid address: ${opts.address}` }, mode);
|
|
9265
|
-
return;
|
|
9266
|
-
}
|
|
9267
|
-
const chainFilter = opts.chains ? opts.chains.split(",").map((s) => s.trim().toLowerCase()) : null;
|
|
9268
|
-
const chainKeys = chainFilter ?? Array.from(registry.chains.keys());
|
|
9269
|
-
const start = Date.now();
|
|
9270
|
-
const scanParams = [];
|
|
9271
|
-
for (const chainKey of chainKeys) {
|
|
9272
|
-
let chain;
|
|
9273
|
-
try {
|
|
9274
|
-
chain = registry.getChain(chainKey);
|
|
9275
|
-
} catch {
|
|
9276
|
-
continue;
|
|
9277
|
-
}
|
|
9278
|
-
const rpc = chain.effectiveRpcUrl();
|
|
9279
|
-
const rawTokens = registry.tokens.get(chainKey) ?? [];
|
|
9280
|
-
const tokens = rawTokens.map((t) => ({
|
|
9281
|
-
address: t.address,
|
|
9282
|
-
symbol: t.symbol,
|
|
9283
|
-
decimals: t.decimals
|
|
9284
|
-
}));
|
|
9285
|
-
const chainProtocols = registry.getProtocolsForChain(chainKey);
|
|
9286
|
-
const lendingPools = chainProtocols.filter(
|
|
9287
|
-
(p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2")
|
|
9288
|
-
).filter((p) => p.contracts?.["pool"]).map((p) => ({
|
|
9289
|
-
name: p.name,
|
|
9290
|
-
pool: p.contracts["pool"],
|
|
9291
|
-
iface: p.interface
|
|
9292
|
-
}));
|
|
9293
|
-
const oracleEntry = chainProtocols.find(
|
|
9294
|
-
(p) => p.interface === "aave_v3" && p.contracts?.["oracle"]
|
|
9295
|
-
);
|
|
9296
|
-
const oracleAddr = oracleEntry?.contracts?.["oracle"];
|
|
9297
|
-
const wrappedNative = chain.wrapped_native ?? "0x5555555555555555555555555555555555555555";
|
|
9298
|
-
scanParams.push({ chainName: chain.name, rpc, tokens, lendingPools, oracleAddr, wrappedNative });
|
|
9299
|
-
}
|
|
9300
|
-
const chainResultsRaw = await Promise.all(
|
|
9301
|
-
scanParams.map(
|
|
9302
|
-
(p) => scanSingleChain(p.chainName, p.rpc, user, p.tokens, p.lendingPools, p.oracleAddr, p.wrappedNative)
|
|
9303
|
-
)
|
|
9304
|
-
);
|
|
9305
|
-
let grandTotalUsd = 0;
|
|
9306
|
-
let totalCollateralUsd = 0;
|
|
9307
|
-
let totalDebtUsd = 0;
|
|
9308
|
-
const chainResults = chainResultsRaw.filter((r) => r !== null).map((r) => {
|
|
9309
|
-
grandTotalUsd += r.chain_value;
|
|
9310
|
-
totalCollateralUsd += r.collateral;
|
|
9311
|
-
totalDebtUsd += r.debt;
|
|
9312
|
-
return {
|
|
9313
|
-
chain: r.chain_name,
|
|
9314
|
-
native_price_usd: round22(r.native_price),
|
|
9315
|
-
chain_total_usd: round22(r.chain_value),
|
|
9316
|
-
token_balances: r.token_balances,
|
|
9317
|
-
lending_positions: r.lending_positions
|
|
9318
|
-
};
|
|
9319
|
-
}).sort((a, b) => b.chain_total_usd - a.chain_total_usd);
|
|
9320
|
-
const scanMs = Date.now() - start;
|
|
9321
|
-
printOutput(
|
|
9322
|
-
{
|
|
9323
|
-
address: user,
|
|
9324
|
-
scan_duration_ms: scanMs,
|
|
9325
|
-
chains_scanned: chainKeys.length,
|
|
9326
|
-
chains_with_positions: chainResults.length,
|
|
9327
|
-
summary: {
|
|
9328
|
-
total_value_usd: round22(grandTotalUsd),
|
|
9329
|
-
total_collateral_usd: round22(totalCollateralUsd),
|
|
9330
|
-
total_debt_usd: round22(totalDebtUsd),
|
|
9331
|
-
net_lending_usd: round22(totalCollateralUsd - totalDebtUsd)
|
|
9332
|
-
},
|
|
9333
|
-
chains: chainResults
|
|
9334
|
-
},
|
|
9335
|
-
mode
|
|
9336
|
-
);
|
|
9337
|
-
});
|
|
9338
|
-
}
|
|
9339
|
-
|
|
9340
7787
|
// src/commands/price.ts
|
|
9341
|
-
function
|
|
7788
|
+
function round2(x) {
|
|
9342
7789
|
return Math.round(x * 100) / 100;
|
|
9343
7790
|
}
|
|
9344
7791
|
function resolveAsset2(registry, chain, asset) {
|
|
@@ -9466,10 +7913,10 @@ function registerPrice(parent, getOpts) {
|
|
|
9466
7913
|
prices: allPrices.map((p) => ({
|
|
9467
7914
|
source: p.source,
|
|
9468
7915
|
source_type: p.source_type,
|
|
9469
|
-
price:
|
|
7916
|
+
price: round2(p.price_f64)
|
|
9470
7917
|
})),
|
|
9471
|
-
max_spread_pct:
|
|
9472
|
-
oracle_vs_dex_spread_pct:
|
|
7918
|
+
max_spread_pct: round2(maxSpreadPct),
|
|
7919
|
+
oracle_vs_dex_spread_pct: round2(oracleVsDexSpreadPct)
|
|
9473
7920
|
};
|
|
9474
7921
|
printOutput(report, mode);
|
|
9475
7922
|
});
|
|
@@ -9557,192 +8004,6 @@ function registerToken(parent, getOpts, makeExecutor2) {
|
|
|
9557
8004
|
});
|
|
9558
8005
|
}
|
|
9559
8006
|
|
|
9560
|
-
// src/commands/whales.ts
|
|
9561
|
-
import { encodeFunctionData as encodeFunctionData31, parseAbi as parseAbi35 } from "viem";
|
|
9562
|
-
var POOL_ABI6 = parseAbi35([
|
|
9563
|
-
"function getUserAccountData(address user) external view returns (uint256 totalCollateralBase, uint256 totalDebtBase, uint256 availableBorrowsBase, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)"
|
|
9564
|
-
]);
|
|
9565
|
-
function round24(x) {
|
|
9566
|
-
return Math.round(x * 100) / 100;
|
|
9567
|
-
}
|
|
9568
|
-
function round43(x) {
|
|
9569
|
-
return Math.round(x * 1e4) / 1e4;
|
|
9570
|
-
}
|
|
9571
|
-
function decodeU2564(data, wordOffset = 0) {
|
|
9572
|
-
if (!data || data.length < 2 + (wordOffset + 1) * 64) return 0n;
|
|
9573
|
-
const hex = data.slice(2 + wordOffset * 64, 2 + wordOffset * 64 + 64);
|
|
9574
|
-
return BigInt("0x" + hex);
|
|
9575
|
-
}
|
|
9576
|
-
function getExplorerApi(chainId, explorerUrl) {
|
|
9577
|
-
const routescanChains = [1, 43114, 10, 5e3];
|
|
9578
|
-
if (routescanChains.includes(chainId)) {
|
|
9579
|
-
return {
|
|
9580
|
-
base: `https://api.routescan.io/v2/network/mainnet/evm/${chainId}/etherscan/api`
|
|
9581
|
-
};
|
|
9582
|
-
}
|
|
9583
|
-
const apiKey = process.env["ETHERSCAN_API_KEY"];
|
|
9584
|
-
if (apiKey) {
|
|
9585
|
-
return {
|
|
9586
|
-
base: `https://api.etherscan.io/v2/api?chainid=${chainId}`,
|
|
9587
|
-
apiKey
|
|
9588
|
-
};
|
|
9589
|
-
}
|
|
9590
|
-
return null;
|
|
9591
|
-
}
|
|
9592
|
-
function registerWhales(parent, getOpts) {
|
|
9593
|
-
parent.command("whales").description("Find top token holders (whales) and their positions").requiredOption("--token <token>", "Token symbol or address").option("--top <n>", "Number of top holders to show", "10").option("--positions", "Also scan each whale's lending positions").action(async (opts) => {
|
|
9594
|
-
const mode = getOpts();
|
|
9595
|
-
const registry = Registry.loadEmbedded();
|
|
9596
|
-
const chainName = (parent.opts().chain ?? "hyperevm").toLowerCase();
|
|
9597
|
-
let chain;
|
|
9598
|
-
try {
|
|
9599
|
-
chain = registry.getChain(chainName);
|
|
9600
|
-
} catch {
|
|
9601
|
-
printOutput({ error: `Chain not found: ${chainName}` }, mode);
|
|
9602
|
-
return;
|
|
9603
|
-
}
|
|
9604
|
-
const rpc = chain.effectiveRpcUrl();
|
|
9605
|
-
const top = parseInt(opts.top, 10) || 10;
|
|
9606
|
-
let token;
|
|
9607
|
-
try {
|
|
9608
|
-
token = registry.resolveToken(chainName, opts.token);
|
|
9609
|
-
} catch {
|
|
9610
|
-
printOutput({ error: `Token not found: ${opts.token}` }, mode);
|
|
9611
|
-
return;
|
|
9612
|
-
}
|
|
9613
|
-
const explorerApi = getExplorerApi(chain.chain_id, chain.explorer_url);
|
|
9614
|
-
if (!explorerApi) {
|
|
9615
|
-
printOutput(
|
|
9616
|
-
{
|
|
9617
|
-
error: `No explorer API available for ${chain.name} (chain_id: ${chain.chain_id}). Set ETHERSCAN_API_KEY to enable.`
|
|
9618
|
-
},
|
|
9619
|
-
mode
|
|
9620
|
-
);
|
|
9621
|
-
return;
|
|
9622
|
-
}
|
|
9623
|
-
const tokenAddr = token.address;
|
|
9624
|
-
let url = `${explorerApi.base}?module=token&action=tokenholderlist&contractaddress=${tokenAddr}&page=1&offset=${top}`;
|
|
9625
|
-
if (explorerApi.apiKey) {
|
|
9626
|
-
url += `&apikey=${explorerApi.apiKey}`;
|
|
9627
|
-
}
|
|
9628
|
-
let body;
|
|
9629
|
-
try {
|
|
9630
|
-
const resp = await fetch(url);
|
|
9631
|
-
body = await resp.json();
|
|
9632
|
-
} catch (e) {
|
|
9633
|
-
printOutput({ error: `Explorer API request failed: ${e instanceof Error ? e.message : String(e)}` }, mode);
|
|
9634
|
-
return;
|
|
9635
|
-
}
|
|
9636
|
-
if (body.status !== "1") {
|
|
9637
|
-
const msg = typeof body.result === "string" ? body.result : "Unknown error";
|
|
9638
|
-
if (msg.includes("API Key") || msg.includes("apikey")) {
|
|
9639
|
-
printOutput(
|
|
9640
|
-
{ error: "Explorer API requires API key. Set ETHERSCAN_API_KEY environment variable." },
|
|
9641
|
-
mode
|
|
9642
|
-
);
|
|
9643
|
-
return;
|
|
9644
|
-
}
|
|
9645
|
-
printOutput({ error: `Explorer API error: ${msg}` }, mode);
|
|
9646
|
-
return;
|
|
9647
|
-
}
|
|
9648
|
-
const holders = Array.isArray(body.result) ? body.result : [];
|
|
9649
|
-
const whaleList = [];
|
|
9650
|
-
for (const h of holders) {
|
|
9651
|
-
const addrStr = h["TokenHolderAddress"] ?? "";
|
|
9652
|
-
const qtyStr = h["TokenHolderQuantity"] ?? "0";
|
|
9653
|
-
if (/^0x[0-9a-fA-F]{40}$/.test(addrStr)) {
|
|
9654
|
-
const raw = BigInt(qtyStr || "0");
|
|
9655
|
-
const balance = Number(raw) / 10 ** token.decimals;
|
|
9656
|
-
whaleList.push({ address: addrStr, balance });
|
|
9657
|
-
}
|
|
9658
|
-
}
|
|
9659
|
-
const whaleData = [];
|
|
9660
|
-
if (opts.positions && whaleList.length > 0) {
|
|
9661
|
-
const lendingPools = registry.getProtocolsForChain(chainName).filter(
|
|
9662
|
-
(p) => p.category === ProtocolCategory.Lending && (p.interface === "aave_v3" || p.interface === "aave_v2")
|
|
9663
|
-
).filter((p) => p.contracts?.["pool"]).map((p) => ({
|
|
9664
|
-
name: p.name,
|
|
9665
|
-
pool: p.contracts["pool"],
|
|
9666
|
-
iface: p.interface
|
|
9667
|
-
}));
|
|
9668
|
-
const calls = [];
|
|
9669
|
-
for (const whale of whaleList) {
|
|
9670
|
-
for (const { pool } of lendingPools) {
|
|
9671
|
-
calls.push([
|
|
9672
|
-
pool,
|
|
9673
|
-
encodeFunctionData31({ abi: POOL_ABI6, functionName: "getUserAccountData", args: [whale.address] })
|
|
9674
|
-
]);
|
|
9675
|
-
}
|
|
9676
|
-
}
|
|
9677
|
-
let results = [];
|
|
9678
|
-
if (calls.length > 0) {
|
|
9679
|
-
try {
|
|
9680
|
-
results = await multicallRead(rpc, calls);
|
|
9681
|
-
} catch {
|
|
9682
|
-
results = [];
|
|
9683
|
-
}
|
|
9684
|
-
}
|
|
9685
|
-
const poolsPerWhale = lendingPools.length;
|
|
9686
|
-
for (let wi = 0; wi < whaleList.length; wi++) {
|
|
9687
|
-
const whale = whaleList[wi];
|
|
9688
|
-
const positions = [];
|
|
9689
|
-
for (let pi = 0; pi < lendingPools.length; pi++) {
|
|
9690
|
-
const { name: protoName, iface } = lendingPools[pi];
|
|
9691
|
-
const idx = wi * poolsPerWhale + pi;
|
|
9692
|
-
const data = results[idx] ?? null;
|
|
9693
|
-
if (data && data.length >= 2 + 192 * 2) {
|
|
9694
|
-
const dec = iface === "aave_v2" ? 18 : 8;
|
|
9695
|
-
const divisor = 10 ** dec;
|
|
9696
|
-
const collateral = Number(decodeU2564(data, 0)) / divisor;
|
|
9697
|
-
const debt = Number(decodeU2564(data, 1)) / divisor;
|
|
9698
|
-
const hfRaw = decodeU2564(data, 5);
|
|
9699
|
-
let hf = null;
|
|
9700
|
-
if (hfRaw <= BigInt("0xffffffffffffffffffffffffffffffff")) {
|
|
9701
|
-
const v = Number(hfRaw) / 1e18;
|
|
9702
|
-
hf = v > 1e10 ? null : round24(v);
|
|
9703
|
-
}
|
|
9704
|
-
if (collateral > 0.01 || debt > 0.01) {
|
|
9705
|
-
positions.push({
|
|
9706
|
-
protocol: protoName,
|
|
9707
|
-
collateral_usd: round24(collateral),
|
|
9708
|
-
debt_usd: round24(debt),
|
|
9709
|
-
health_factor: hf
|
|
9710
|
-
});
|
|
9711
|
-
}
|
|
9712
|
-
}
|
|
9713
|
-
}
|
|
9714
|
-
whaleData.push({
|
|
9715
|
-
rank: wi + 1,
|
|
9716
|
-
address: whale.address,
|
|
9717
|
-
balance: round43(whale.balance),
|
|
9718
|
-
positions
|
|
9719
|
-
});
|
|
9720
|
-
}
|
|
9721
|
-
} else {
|
|
9722
|
-
for (let wi = 0; wi < whaleList.length; wi++) {
|
|
9723
|
-
const whale = whaleList[wi];
|
|
9724
|
-
whaleData.push({
|
|
9725
|
-
rank: wi + 1,
|
|
9726
|
-
address: whale.address,
|
|
9727
|
-
balance: round43(whale.balance)
|
|
9728
|
-
});
|
|
9729
|
-
}
|
|
9730
|
-
}
|
|
9731
|
-
printOutput(
|
|
9732
|
-
{
|
|
9733
|
-
chain: chain.name,
|
|
9734
|
-
token: opts.token,
|
|
9735
|
-
token_address: tokenAddr,
|
|
9736
|
-
decimals: token.decimals,
|
|
9737
|
-
top,
|
|
9738
|
-
holders: whaleData,
|
|
9739
|
-
explorer: chain.explorer_url ?? ""
|
|
9740
|
-
},
|
|
9741
|
-
mode
|
|
9742
|
-
);
|
|
9743
|
-
});
|
|
9744
|
-
}
|
|
9745
|
-
|
|
9746
8007
|
// src/commands/bridge.ts
|
|
9747
8008
|
var LIFI_API = "https://li.quest/v1";
|
|
9748
8009
|
var DLN_API = "https://dln.debridge.finance/v1.0/dln/order";
|
|
@@ -9897,11 +8158,11 @@ function registerBridge(parent, getOpts) {
|
|
|
9897
8158
|
const amountUsdc = Number(BigInt(opts.amount)) / 1e6;
|
|
9898
8159
|
const { fee, maxFeeSubunits } = await getCctpFeeEstimate(srcDomain, dstDomain, amountUsdc);
|
|
9899
8160
|
const recipientPadded = `0x${"0".repeat(24)}${recipient.replace("0x", "").toLowerCase()}`;
|
|
9900
|
-
const { encodeFunctionData:
|
|
9901
|
-
const tokenMessengerAbi =
|
|
8161
|
+
const { encodeFunctionData: encodeFunctionData29, parseAbi: parseAbi33 } = await import("viem");
|
|
8162
|
+
const tokenMessengerAbi = parseAbi33([
|
|
9902
8163
|
"function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) external returns (uint64 nonce)"
|
|
9903
8164
|
]);
|
|
9904
|
-
const data =
|
|
8165
|
+
const data = encodeFunctionData29({
|
|
9905
8166
|
abi: tokenMessengerAbi,
|
|
9906
8167
|
functionName: "depositForBurn",
|
|
9907
8168
|
args: [
|
|
@@ -10174,16 +8435,16 @@ function registerSwap(parent, getOpts, makeExecutor2) {
|
|
|
10174
8435
|
// src/commands/setup.ts
|
|
10175
8436
|
import pc2 from "picocolors";
|
|
10176
8437
|
import { createInterface } from "readline";
|
|
10177
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as
|
|
10178
|
-
import { resolve as
|
|
10179
|
-
var DEFI_DIR =
|
|
10180
|
-
var ENV_FILE =
|
|
8438
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
8439
|
+
import { resolve as resolve4 } from "path";
|
|
8440
|
+
var DEFI_DIR = resolve4(process.env.HOME || "~", ".defi");
|
|
8441
|
+
var ENV_FILE = resolve4(DEFI_DIR, ".env");
|
|
10181
8442
|
function ensureDefiDir() {
|
|
10182
8443
|
if (!existsSync3(DEFI_DIR)) mkdirSync2(DEFI_DIR, { recursive: true, mode: 448 });
|
|
10183
8444
|
}
|
|
10184
8445
|
function loadEnvFile() {
|
|
10185
8446
|
if (!existsSync3(ENV_FILE)) return {};
|
|
10186
|
-
const lines =
|
|
8447
|
+
const lines = readFileSync4(ENV_FILE, "utf-8").split("\n");
|
|
10187
8448
|
const env = {};
|
|
10188
8449
|
for (const line of lines) {
|
|
10189
8450
|
const trimmed = line.trim();
|
|
@@ -10328,8 +8589,8 @@ var BANNER = `
|
|
|
10328
8589
|
|
|
10329
8590
|
2 chains \xB7 21 protocols \xB7 by HypurrQuant
|
|
10330
8591
|
|
|
10331
|
-
Lending, LP
|
|
10332
|
-
|
|
8592
|
+
Lending, LP farming, DEX swap, yield comparison
|
|
8593
|
+
\u2014 all from your terminal.
|
|
10333
8594
|
`;
|
|
10334
8595
|
var program = new Command().name("defi").description("DeFi CLI \u2014 Multi-chain DeFi toolkit").version(_pkg.version).addHelpText("before", BANNER).option("--json", "Output as JSON").option("--ndjson", "Output as newline-delimited JSON").option("--fields <fields>", "Select specific output fields (comma-separated)").option("--chain <chain>", "Target chain", "hyperevm").option("--dry-run", "Dry-run mode (default, no broadcast)", true).option("--broadcast", "Actually broadcast the transaction");
|
|
10335
8596
|
function getOutputMode() {
|
|
@@ -10346,61 +8607,15 @@ registerStatus(program, getOutputMode);
|
|
|
10346
8607
|
registerSchema(program, getOutputMode);
|
|
10347
8608
|
registerLP(program, getOutputMode, makeExecutor);
|
|
10348
8609
|
registerLending(program, getOutputMode, makeExecutor);
|
|
10349
|
-
registerCdp(program, getOutputMode, makeExecutor);
|
|
10350
|
-
registerVault(program, getOutputMode, makeExecutor);
|
|
10351
8610
|
registerYield(program, getOutputMode, makeExecutor);
|
|
10352
8611
|
registerPortfolio(program, getOutputMode);
|
|
10353
|
-
registerMonitor(program, getOutputMode);
|
|
10354
|
-
registerAlert(program, getOutputMode);
|
|
10355
|
-
registerScan(program, getOutputMode);
|
|
10356
|
-
registerPositions(program, getOutputMode);
|
|
10357
8612
|
registerPrice(program, getOutputMode);
|
|
10358
8613
|
registerWallet(program, getOutputMode);
|
|
10359
8614
|
registerToken(program, getOutputMode, makeExecutor);
|
|
10360
|
-
registerWhales(program, getOutputMode);
|
|
10361
8615
|
registerBridge(program, getOutputMode);
|
|
10362
8616
|
registerSwap(program, getOutputMode, makeExecutor);
|
|
10363
8617
|
registerSetup(program);
|
|
10364
8618
|
export {
|
|
10365
8619
|
program
|
|
10366
8620
|
};
|
|
10367
|
-
/*! Bundled license information:
|
|
10368
|
-
|
|
10369
|
-
smol-toml/dist/error.js:
|
|
10370
|
-
smol-toml/dist/util.js:
|
|
10371
|
-
smol-toml/dist/date.js:
|
|
10372
|
-
smol-toml/dist/primitive.js:
|
|
10373
|
-
smol-toml/dist/extract.js:
|
|
10374
|
-
smol-toml/dist/struct.js:
|
|
10375
|
-
smol-toml/dist/parse.js:
|
|
10376
|
-
smol-toml/dist/stringify.js:
|
|
10377
|
-
smol-toml/dist/index.js:
|
|
10378
|
-
(*!
|
|
10379
|
-
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
10380
|
-
* SPDX-License-Identifier: BSD-3-Clause
|
|
10381
|
-
*
|
|
10382
|
-
* Redistribution and use in source and binary forms, with or without
|
|
10383
|
-
* modification, are permitted provided that the following conditions are met:
|
|
10384
|
-
*
|
|
10385
|
-
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
10386
|
-
* list of conditions and the following disclaimer.
|
|
10387
|
-
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10388
|
-
* this list of conditions and the following disclaimer in the
|
|
10389
|
-
* documentation and/or other materials provided with the distribution.
|
|
10390
|
-
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
10391
|
-
* may be used to endorse or promote products derived from this software without
|
|
10392
|
-
* specific prior written permission.
|
|
10393
|
-
*
|
|
10394
|
-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
10395
|
-
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
10396
|
-
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
10397
|
-
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
10398
|
-
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
10399
|
-
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
10400
|
-
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
10401
|
-
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
10402
|
-
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
10403
|
-
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
10404
|
-
*)
|
|
10405
|
-
*/
|
|
10406
8621
|
//# sourceMappingURL=index.js.map
|