@vultisig/cli 0.5.0 → 0.8.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/CHANGELOG.md +44 -0
- package/README.md +39 -0
- package/dist/index.js +3090 -259
- package/package.json +18 -17
package/dist/index.js
CHANGED
|
@@ -11,9 +11,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
11
11
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
12
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
13
|
});
|
|
14
|
+
var __esm = (fn, res) => function __init() {
|
|
15
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
16
|
+
};
|
|
14
17
|
var __commonJS = (cb, mod) => function __require2() {
|
|
15
18
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
19
|
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
|
+
};
|
|
17
24
|
var __copyProps = (to, from, except, desc) => {
|
|
18
25
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
26
|
for (let key of __getOwnPropNames(from))
|
|
@@ -1101,11 +1108,333 @@ var require_main = __commonJS({
|
|
|
1101
1108
|
}
|
|
1102
1109
|
});
|
|
1103
1110
|
|
|
1111
|
+
// ../../node_modules/@noble/hashes/esm/_u64.js
|
|
1112
|
+
function fromBig(n, le = false) {
|
|
1113
|
+
if (le)
|
|
1114
|
+
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
|
1115
|
+
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
1116
|
+
}
|
|
1117
|
+
function split(lst, le = false) {
|
|
1118
|
+
const len = lst.length;
|
|
1119
|
+
let Ah = new Uint32Array(len);
|
|
1120
|
+
let Al = new Uint32Array(len);
|
|
1121
|
+
for (let i = 0; i < len; i++) {
|
|
1122
|
+
const { h, l } = fromBig(lst[i], le);
|
|
1123
|
+
[Ah[i], Al[i]] = [h, l];
|
|
1124
|
+
}
|
|
1125
|
+
return [Ah, Al];
|
|
1126
|
+
}
|
|
1127
|
+
var U32_MASK64, _32n, rotlSH, rotlSL, rotlBH, rotlBL;
|
|
1128
|
+
var init_u64 = __esm({
|
|
1129
|
+
"../../node_modules/@noble/hashes/esm/_u64.js"() {
|
|
1130
|
+
U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
1131
|
+
_32n = /* @__PURE__ */ BigInt(32);
|
|
1132
|
+
rotlSH = (h, l, s) => h << s | l >>> 32 - s;
|
|
1133
|
+
rotlSL = (h, l, s) => l << s | h >>> 32 - s;
|
|
1134
|
+
rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s;
|
|
1135
|
+
rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s;
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
// ../../node_modules/@noble/hashes/esm/utils.js
|
|
1140
|
+
function isBytes(a) {
|
|
1141
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
1142
|
+
}
|
|
1143
|
+
function anumber(n) {
|
|
1144
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
1145
|
+
throw new Error("positive integer expected, got " + n);
|
|
1146
|
+
}
|
|
1147
|
+
function abytes(b, ...lengths) {
|
|
1148
|
+
if (!isBytes(b))
|
|
1149
|
+
throw new Error("Uint8Array expected");
|
|
1150
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
1151
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
1152
|
+
}
|
|
1153
|
+
function aexists(instance, checkFinished = true) {
|
|
1154
|
+
if (instance.destroyed)
|
|
1155
|
+
throw new Error("Hash instance has been destroyed");
|
|
1156
|
+
if (checkFinished && instance.finished)
|
|
1157
|
+
throw new Error("Hash#digest() has already been called");
|
|
1158
|
+
}
|
|
1159
|
+
function aoutput(out, instance) {
|
|
1160
|
+
abytes(out);
|
|
1161
|
+
const min = instance.outputLen;
|
|
1162
|
+
if (out.length < min) {
|
|
1163
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
function u32(arr) {
|
|
1167
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
1168
|
+
}
|
|
1169
|
+
function clean(...arrays) {
|
|
1170
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
1171
|
+
arrays[i].fill(0);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
function byteSwap(word) {
|
|
1175
|
+
return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
|
|
1176
|
+
}
|
|
1177
|
+
function byteSwap32(arr) {
|
|
1178
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1179
|
+
arr[i] = byteSwap(arr[i]);
|
|
1180
|
+
}
|
|
1181
|
+
return arr;
|
|
1182
|
+
}
|
|
1183
|
+
function utf8ToBytes(str) {
|
|
1184
|
+
if (typeof str !== "string")
|
|
1185
|
+
throw new Error("string expected");
|
|
1186
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
1187
|
+
}
|
|
1188
|
+
function toBytes(data) {
|
|
1189
|
+
if (typeof data === "string")
|
|
1190
|
+
data = utf8ToBytes(data);
|
|
1191
|
+
abytes(data);
|
|
1192
|
+
return data;
|
|
1193
|
+
}
|
|
1194
|
+
function createHasher(hashCons) {
|
|
1195
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
1196
|
+
const tmp = hashCons();
|
|
1197
|
+
hashC.outputLen = tmp.outputLen;
|
|
1198
|
+
hashC.blockLen = tmp.blockLen;
|
|
1199
|
+
hashC.create = () => hashCons();
|
|
1200
|
+
return hashC;
|
|
1201
|
+
}
|
|
1202
|
+
function createXOFer(hashCons) {
|
|
1203
|
+
const hashC = (msg, opts) => hashCons(opts).update(toBytes(msg)).digest();
|
|
1204
|
+
const tmp = hashCons({});
|
|
1205
|
+
hashC.outputLen = tmp.outputLen;
|
|
1206
|
+
hashC.blockLen = tmp.blockLen;
|
|
1207
|
+
hashC.create = (opts) => hashCons(opts);
|
|
1208
|
+
return hashC;
|
|
1209
|
+
}
|
|
1210
|
+
var isLE, swap32IfBE, Hash;
|
|
1211
|
+
var init_utils = __esm({
|
|
1212
|
+
"../../node_modules/@noble/hashes/esm/utils.js"() {
|
|
1213
|
+
isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
1214
|
+
swap32IfBE = isLE ? (u) => u : byteSwap32;
|
|
1215
|
+
Hash = class {
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// ../../node_modules/@noble/hashes/esm/sha3.js
|
|
1221
|
+
var sha3_exports = {};
|
|
1222
|
+
__export(sha3_exports, {
|
|
1223
|
+
Keccak: () => Keccak,
|
|
1224
|
+
keccakP: () => keccakP,
|
|
1225
|
+
keccak_224: () => keccak_224,
|
|
1226
|
+
keccak_256: () => keccak_256,
|
|
1227
|
+
keccak_384: () => keccak_384,
|
|
1228
|
+
keccak_512: () => keccak_512,
|
|
1229
|
+
sha3_224: () => sha3_224,
|
|
1230
|
+
sha3_256: () => sha3_256,
|
|
1231
|
+
sha3_384: () => sha3_384,
|
|
1232
|
+
sha3_512: () => sha3_512,
|
|
1233
|
+
shake128: () => shake128,
|
|
1234
|
+
shake256: () => shake256
|
|
1235
|
+
});
|
|
1236
|
+
function keccakP(s, rounds = 24) {
|
|
1237
|
+
const B = new Uint32Array(5 * 2);
|
|
1238
|
+
for (let round = 24 - rounds; round < 24; round++) {
|
|
1239
|
+
for (let x = 0; x < 10; x++)
|
|
1240
|
+
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
|
|
1241
|
+
for (let x = 0; x < 10; x += 2) {
|
|
1242
|
+
const idx1 = (x + 8) % 10;
|
|
1243
|
+
const idx0 = (x + 2) % 10;
|
|
1244
|
+
const B0 = B[idx0];
|
|
1245
|
+
const B1 = B[idx0 + 1];
|
|
1246
|
+
const Th = rotlH(B0, B1, 1) ^ B[idx1];
|
|
1247
|
+
const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
|
|
1248
|
+
for (let y = 0; y < 50; y += 10) {
|
|
1249
|
+
s[x + y] ^= Th;
|
|
1250
|
+
s[x + y + 1] ^= Tl;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
let curH = s[2];
|
|
1254
|
+
let curL = s[3];
|
|
1255
|
+
for (let t = 0; t < 24; t++) {
|
|
1256
|
+
const shift = SHA3_ROTL[t];
|
|
1257
|
+
const Th = rotlH(curH, curL, shift);
|
|
1258
|
+
const Tl = rotlL(curH, curL, shift);
|
|
1259
|
+
const PI = SHA3_PI[t];
|
|
1260
|
+
curH = s[PI];
|
|
1261
|
+
curL = s[PI + 1];
|
|
1262
|
+
s[PI] = Th;
|
|
1263
|
+
s[PI + 1] = Tl;
|
|
1264
|
+
}
|
|
1265
|
+
for (let y = 0; y < 50; y += 10) {
|
|
1266
|
+
for (let x = 0; x < 10; x++)
|
|
1267
|
+
B[x] = s[y + x];
|
|
1268
|
+
for (let x = 0; x < 10; x++)
|
|
1269
|
+
s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
|
|
1270
|
+
}
|
|
1271
|
+
s[0] ^= SHA3_IOTA_H[round];
|
|
1272
|
+
s[1] ^= SHA3_IOTA_L[round];
|
|
1273
|
+
}
|
|
1274
|
+
clean(B);
|
|
1275
|
+
}
|
|
1276
|
+
var _0n, _1n, _2n, _7n, _256n, _0x71n, SHA3_PI, SHA3_ROTL, _SHA3_IOTA, IOTAS, SHA3_IOTA_H, SHA3_IOTA_L, rotlH, rotlL, Keccak, gen, sha3_224, sha3_256, sha3_384, sha3_512, keccak_224, keccak_256, keccak_384, keccak_512, genShake, shake128, shake256;
|
|
1277
|
+
var init_sha3 = __esm({
|
|
1278
|
+
"../../node_modules/@noble/hashes/esm/sha3.js"() {
|
|
1279
|
+
init_u64();
|
|
1280
|
+
init_utils();
|
|
1281
|
+
_0n = BigInt(0);
|
|
1282
|
+
_1n = BigInt(1);
|
|
1283
|
+
_2n = BigInt(2);
|
|
1284
|
+
_7n = BigInt(7);
|
|
1285
|
+
_256n = BigInt(256);
|
|
1286
|
+
_0x71n = BigInt(113);
|
|
1287
|
+
SHA3_PI = [];
|
|
1288
|
+
SHA3_ROTL = [];
|
|
1289
|
+
_SHA3_IOTA = [];
|
|
1290
|
+
for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
|
|
1291
|
+
[x, y] = [y, (2 * x + 3 * y) % 5];
|
|
1292
|
+
SHA3_PI.push(2 * (5 * y + x));
|
|
1293
|
+
SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
|
|
1294
|
+
let t = _0n;
|
|
1295
|
+
for (let j = 0; j < 7; j++) {
|
|
1296
|
+
R = (R << _1n ^ (R >> _7n) * _0x71n) % _256n;
|
|
1297
|
+
if (R & _2n)
|
|
1298
|
+
t ^= _1n << (_1n << /* @__PURE__ */ BigInt(j)) - _1n;
|
|
1299
|
+
}
|
|
1300
|
+
_SHA3_IOTA.push(t);
|
|
1301
|
+
}
|
|
1302
|
+
IOTAS = split(_SHA3_IOTA, true);
|
|
1303
|
+
SHA3_IOTA_H = IOTAS[0];
|
|
1304
|
+
SHA3_IOTA_L = IOTAS[1];
|
|
1305
|
+
rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s);
|
|
1306
|
+
rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s);
|
|
1307
|
+
Keccak = class _Keccak extends Hash {
|
|
1308
|
+
// NOTE: we accept arguments in bytes instead of bits here.
|
|
1309
|
+
constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
|
|
1310
|
+
super();
|
|
1311
|
+
this.pos = 0;
|
|
1312
|
+
this.posOut = 0;
|
|
1313
|
+
this.finished = false;
|
|
1314
|
+
this.destroyed = false;
|
|
1315
|
+
this.enableXOF = false;
|
|
1316
|
+
this.blockLen = blockLen;
|
|
1317
|
+
this.suffix = suffix;
|
|
1318
|
+
this.outputLen = outputLen;
|
|
1319
|
+
this.enableXOF = enableXOF;
|
|
1320
|
+
this.rounds = rounds;
|
|
1321
|
+
anumber(outputLen);
|
|
1322
|
+
if (!(0 < blockLen && blockLen < 200))
|
|
1323
|
+
throw new Error("only keccak-f1600 function is supported");
|
|
1324
|
+
this.state = new Uint8Array(200);
|
|
1325
|
+
this.state32 = u32(this.state);
|
|
1326
|
+
}
|
|
1327
|
+
clone() {
|
|
1328
|
+
return this._cloneInto();
|
|
1329
|
+
}
|
|
1330
|
+
keccak() {
|
|
1331
|
+
swap32IfBE(this.state32);
|
|
1332
|
+
keccakP(this.state32, this.rounds);
|
|
1333
|
+
swap32IfBE(this.state32);
|
|
1334
|
+
this.posOut = 0;
|
|
1335
|
+
this.pos = 0;
|
|
1336
|
+
}
|
|
1337
|
+
update(data) {
|
|
1338
|
+
aexists(this);
|
|
1339
|
+
data = toBytes(data);
|
|
1340
|
+
abytes(data);
|
|
1341
|
+
const { blockLen, state } = this;
|
|
1342
|
+
const len = data.length;
|
|
1343
|
+
for (let pos = 0; pos < len; ) {
|
|
1344
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
1345
|
+
for (let i = 0; i < take; i++)
|
|
1346
|
+
state[this.pos++] ^= data[pos++];
|
|
1347
|
+
if (this.pos === blockLen)
|
|
1348
|
+
this.keccak();
|
|
1349
|
+
}
|
|
1350
|
+
return this;
|
|
1351
|
+
}
|
|
1352
|
+
finish() {
|
|
1353
|
+
if (this.finished)
|
|
1354
|
+
return;
|
|
1355
|
+
this.finished = true;
|
|
1356
|
+
const { state, suffix, pos, blockLen } = this;
|
|
1357
|
+
state[pos] ^= suffix;
|
|
1358
|
+
if ((suffix & 128) !== 0 && pos === blockLen - 1)
|
|
1359
|
+
this.keccak();
|
|
1360
|
+
state[blockLen - 1] ^= 128;
|
|
1361
|
+
this.keccak();
|
|
1362
|
+
}
|
|
1363
|
+
writeInto(out) {
|
|
1364
|
+
aexists(this, false);
|
|
1365
|
+
abytes(out);
|
|
1366
|
+
this.finish();
|
|
1367
|
+
const bufferOut = this.state;
|
|
1368
|
+
const { blockLen } = this;
|
|
1369
|
+
for (let pos = 0, len = out.length; pos < len; ) {
|
|
1370
|
+
if (this.posOut >= blockLen)
|
|
1371
|
+
this.keccak();
|
|
1372
|
+
const take = Math.min(blockLen - this.posOut, len - pos);
|
|
1373
|
+
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
|
|
1374
|
+
this.posOut += take;
|
|
1375
|
+
pos += take;
|
|
1376
|
+
}
|
|
1377
|
+
return out;
|
|
1378
|
+
}
|
|
1379
|
+
xofInto(out) {
|
|
1380
|
+
if (!this.enableXOF)
|
|
1381
|
+
throw new Error("XOF is not possible for this instance");
|
|
1382
|
+
return this.writeInto(out);
|
|
1383
|
+
}
|
|
1384
|
+
xof(bytes) {
|
|
1385
|
+
anumber(bytes);
|
|
1386
|
+
return this.xofInto(new Uint8Array(bytes));
|
|
1387
|
+
}
|
|
1388
|
+
digestInto(out) {
|
|
1389
|
+
aoutput(out, this);
|
|
1390
|
+
if (this.finished)
|
|
1391
|
+
throw new Error("digest() was already called");
|
|
1392
|
+
this.writeInto(out);
|
|
1393
|
+
this.destroy();
|
|
1394
|
+
return out;
|
|
1395
|
+
}
|
|
1396
|
+
digest() {
|
|
1397
|
+
return this.digestInto(new Uint8Array(this.outputLen));
|
|
1398
|
+
}
|
|
1399
|
+
destroy() {
|
|
1400
|
+
this.destroyed = true;
|
|
1401
|
+
clean(this.state);
|
|
1402
|
+
}
|
|
1403
|
+
_cloneInto(to) {
|
|
1404
|
+
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
|
|
1405
|
+
to || (to = new _Keccak(blockLen, suffix, outputLen, enableXOF, rounds));
|
|
1406
|
+
to.state32.set(this.state32);
|
|
1407
|
+
to.pos = this.pos;
|
|
1408
|
+
to.posOut = this.posOut;
|
|
1409
|
+
to.finished = this.finished;
|
|
1410
|
+
to.rounds = rounds;
|
|
1411
|
+
to.suffix = suffix;
|
|
1412
|
+
to.outputLen = outputLen;
|
|
1413
|
+
to.enableXOF = enableXOF;
|
|
1414
|
+
to.destroyed = this.destroyed;
|
|
1415
|
+
return to;
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
gen = (suffix, blockLen, outputLen) => createHasher(() => new Keccak(blockLen, suffix, outputLen));
|
|
1419
|
+
sha3_224 = /* @__PURE__ */ (() => gen(6, 144, 224 / 8))();
|
|
1420
|
+
sha3_256 = /* @__PURE__ */ (() => gen(6, 136, 256 / 8))();
|
|
1421
|
+
sha3_384 = /* @__PURE__ */ (() => gen(6, 104, 384 / 8))();
|
|
1422
|
+
sha3_512 = /* @__PURE__ */ (() => gen(6, 72, 512 / 8))();
|
|
1423
|
+
keccak_224 = /* @__PURE__ */ (() => gen(1, 144, 224 / 8))();
|
|
1424
|
+
keccak_256 = /* @__PURE__ */ (() => gen(1, 136, 256 / 8))();
|
|
1425
|
+
keccak_384 = /* @__PURE__ */ (() => gen(1, 104, 384 / 8))();
|
|
1426
|
+
keccak_512 = /* @__PURE__ */ (() => gen(1, 72, 512 / 8))();
|
|
1427
|
+
genShake = (suffix, blockLen, outputLen) => createXOFer((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === void 0 ? outputLen : opts.dkLen, true));
|
|
1428
|
+
shake128 = /* @__PURE__ */ (() => genShake(31, 168, 128 / 8))();
|
|
1429
|
+
shake256 = /* @__PURE__ */ (() => genShake(31, 136, 256 / 8))();
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1104
1433
|
// src/index.ts
|
|
1105
1434
|
import "dotenv/config";
|
|
1106
1435
|
import { promises as fs3 } from "node:fs";
|
|
1107
|
-
import { parseKeygenQR, Vultisig as
|
|
1108
|
-
import
|
|
1436
|
+
import { parseKeygenQR, Vultisig as Vultisig7 } from "@vultisig/sdk";
|
|
1437
|
+
import chalk15 from "chalk";
|
|
1109
1438
|
import { program } from "commander";
|
|
1110
1439
|
import inquirer8 from "inquirer";
|
|
1111
1440
|
|
|
@@ -2327,6 +2656,66 @@ async function executeBroadcast(ctx2, params) {
|
|
|
2327
2656
|
}
|
|
2328
2657
|
}
|
|
2329
2658
|
|
|
2659
|
+
// src/commands/tx-status.ts
|
|
2660
|
+
import { Chain as Chain5, Vultisig as Vultisig5 } from "@vultisig/sdk";
|
|
2661
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
2662
|
+
async function executeTxStatus(ctx2, params) {
|
|
2663
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2664
|
+
if (!Object.values(Chain5).includes(params.chain)) {
|
|
2665
|
+
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2666
|
+
}
|
|
2667
|
+
const spinner = createSpinner("Checking transaction status...");
|
|
2668
|
+
try {
|
|
2669
|
+
let result = await vault.getTxStatus({ chain: params.chain, txHash: params.txHash });
|
|
2670
|
+
if (!params.noWait) {
|
|
2671
|
+
let polls = 1;
|
|
2672
|
+
while (result.status === "pending") {
|
|
2673
|
+
spinner.text = `Transaction pending... (${polls * 5}s)`;
|
|
2674
|
+
await sleep(POLL_INTERVAL_MS);
|
|
2675
|
+
result = await vault.getTxStatus({ chain: params.chain, txHash: params.txHash });
|
|
2676
|
+
polls++;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
spinner.succeed(`Transaction status: ${result.status}`);
|
|
2680
|
+
displayResult(params.chain, params.txHash, result);
|
|
2681
|
+
return result;
|
|
2682
|
+
} catch (error2) {
|
|
2683
|
+
spinner.fail("Failed to check transaction status");
|
|
2684
|
+
throw error2;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
function displayResult(chain, txHash, result) {
|
|
2688
|
+
if (isJsonOutput()) {
|
|
2689
|
+
outputJson({
|
|
2690
|
+
chain,
|
|
2691
|
+
txHash,
|
|
2692
|
+
status: result.status,
|
|
2693
|
+
receipt: result.receipt ? {
|
|
2694
|
+
feeAmount: result.receipt.feeAmount.toString(),
|
|
2695
|
+
feeDecimals: result.receipt.feeDecimals,
|
|
2696
|
+
feeTicker: result.receipt.feeTicker
|
|
2697
|
+
} : void 0,
|
|
2698
|
+
explorerUrl: Vultisig5.getTxExplorerUrl(chain, txHash)
|
|
2699
|
+
});
|
|
2700
|
+
} else {
|
|
2701
|
+
printResult(`Status: ${result.status}`);
|
|
2702
|
+
if (result.receipt) {
|
|
2703
|
+
const fee = formatFee(result.receipt.feeAmount, result.receipt.feeDecimals);
|
|
2704
|
+
printResult(`Fee: ${fee} ${result.receipt.feeTicker}`);
|
|
2705
|
+
}
|
|
2706
|
+
printResult(`Explorer: ${Vultisig5.getTxExplorerUrl(chain, txHash)}`);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
function formatFee(amount, decimals) {
|
|
2710
|
+
const str = amount.toString().padStart(decimals + 1, "0");
|
|
2711
|
+
const whole = str.slice(0, -decimals) || "0";
|
|
2712
|
+
const frac = str.slice(-decimals).replace(/0+$/, "");
|
|
2713
|
+
return frac ? `${whole}.${frac}` : whole;
|
|
2714
|
+
}
|
|
2715
|
+
function sleep(ms) {
|
|
2716
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2330
2719
|
// src/commands/vault-management.ts
|
|
2331
2720
|
var import_qrcode_terminal4 = __toESM(require_main(), 1);
|
|
2332
2721
|
import chalk5 from "chalk";
|
|
@@ -2344,13 +2733,14 @@ function withAbortSignal(promise, signal) {
|
|
|
2344
2733
|
]);
|
|
2345
2734
|
}
|
|
2346
2735
|
async function executeCreateFast(ctx2, options) {
|
|
2347
|
-
const { name, password, email, signal } = options;
|
|
2736
|
+
const { name, password, email, signal, twoStep } = options;
|
|
2348
2737
|
const spinner = createSpinner("Creating vault...");
|
|
2349
2738
|
const vaultId = await withAbortSignal(
|
|
2350
2739
|
ctx2.sdk.createFastVault({
|
|
2351
2740
|
name,
|
|
2352
2741
|
password,
|
|
2353
2742
|
email,
|
|
2743
|
+
persistPending: twoStep,
|
|
2354
2744
|
onProgress: (step) => {
|
|
2355
2745
|
spinner.text = `${step.message} (${step.progress}%)`;
|
|
2356
2746
|
}
|
|
@@ -2358,6 +2748,17 @@ async function executeCreateFast(ctx2, options) {
|
|
|
2358
2748
|
signal
|
|
2359
2749
|
);
|
|
2360
2750
|
spinner.succeed(`Vault keys generated: ${name}`);
|
|
2751
|
+
if (twoStep) {
|
|
2752
|
+
success("\n+ Vault created and saved to disk (pending verification)");
|
|
2753
|
+
info(`
|
|
2754
|
+
Vault ID: ${vaultId}`);
|
|
2755
|
+
info("\nA verification code has been sent to your email.");
|
|
2756
|
+
info("To complete setup, run:");
|
|
2757
|
+
printResult(chalk5.cyan(` vultisig verify ${vaultId} --code <OTP>`));
|
|
2758
|
+
info("\nOr to resend the verification email:");
|
|
2759
|
+
printResult(chalk5.cyan(` vultisig verify ${vaultId} --resend --email ${email} --password <password>`));
|
|
2760
|
+
return void 0;
|
|
2761
|
+
}
|
|
2361
2762
|
warn("\nA verification code has been sent to your email.");
|
|
2362
2763
|
info("Please check your inbox and enter the code.");
|
|
2363
2764
|
const MAX_VERIFY_ATTEMPTS = 5;
|
|
@@ -3268,7 +3669,7 @@ async function executeSwap(ctx2, options) {
|
|
|
3268
3669
|
}
|
|
3269
3670
|
|
|
3270
3671
|
// src/commands/settings.ts
|
|
3271
|
-
import { Chain as
|
|
3672
|
+
import { Chain as Chain6, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
3272
3673
|
import chalk6 from "chalk";
|
|
3273
3674
|
import inquirer5 from "inquirer";
|
|
3274
3675
|
async function executeCurrency(ctx2, newCurrency) {
|
|
@@ -3336,7 +3737,7 @@ async function executeAddressBook(ctx2, options = {}) {
|
|
|
3336
3737
|
type: "list",
|
|
3337
3738
|
name: "chain",
|
|
3338
3739
|
message: "Select chain:",
|
|
3339
|
-
choices: Object.values(
|
|
3740
|
+
choices: Object.values(Chain6)
|
|
3340
3741
|
});
|
|
3341
3742
|
}
|
|
3342
3743
|
if (!address) {
|
|
@@ -3696,127 +4097,2458 @@ function displayDiscountTier(tierInfo) {
|
|
|
3696
4097
|
printResult("");
|
|
3697
4098
|
}
|
|
3698
4099
|
|
|
3699
|
-
// src/
|
|
3700
|
-
import
|
|
3701
|
-
import
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
"
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
// Swap operations
|
|
3723
|
-
"swap-chains",
|
|
3724
|
-
"swap-quote",
|
|
3725
|
-
"swap",
|
|
3726
|
-
// Session commands (shell-only)
|
|
3727
|
-
"lock",
|
|
3728
|
-
"unlock",
|
|
3729
|
-
"status",
|
|
3730
|
-
// Settings
|
|
3731
|
-
"currency",
|
|
3732
|
-
"server",
|
|
3733
|
-
"address-book",
|
|
3734
|
-
// Help
|
|
3735
|
-
"help",
|
|
3736
|
-
"?",
|
|
3737
|
-
// REPL commands
|
|
3738
|
-
".help",
|
|
3739
|
-
".clear",
|
|
3740
|
-
".exit"
|
|
3741
|
-
];
|
|
3742
|
-
function createCompleter(ctx2) {
|
|
3743
|
-
return function completer(line) {
|
|
4100
|
+
// src/commands/agent.ts
|
|
4101
|
+
import chalk9 from "chalk";
|
|
4102
|
+
import Table from "cli-table3";
|
|
4103
|
+
|
|
4104
|
+
// src/agent/auth.ts
|
|
4105
|
+
init_sha3();
|
|
4106
|
+
import { randomBytes } from "node:crypto";
|
|
4107
|
+
import { Chain as Chain7 } from "@vultisig/sdk";
|
|
4108
|
+
async function authenticateVault(client, vault, password, maxAttempts = 3) {
|
|
4109
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
4110
|
+
const chainCode = vault.hexChainCode;
|
|
4111
|
+
const ethAddress = await vault.address(Chain7.Ethereum);
|
|
4112
|
+
const nonce = "0x" + randomBytes(16).toString("hex");
|
|
4113
|
+
const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
4114
|
+
const authMessage = JSON.stringify({
|
|
4115
|
+
message: "Sign into Vultisig Plugin Marketplace",
|
|
4116
|
+
nonce,
|
|
4117
|
+
expiresAt,
|
|
4118
|
+
address: ethAddress
|
|
4119
|
+
});
|
|
4120
|
+
const messageHash = computePersonalSignHash(authMessage);
|
|
4121
|
+
let lastError = null;
|
|
4122
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3744
4123
|
try {
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
const partial = parts.slice(1).join(" ");
|
|
3749
|
-
return completeFilePath(partial, command === "import");
|
|
4124
|
+
if (attempt > 1) {
|
|
4125
|
+
process.stderr.write(` Retry ${attempt}/${maxAttempts}...
|
|
4126
|
+
`);
|
|
3750
4127
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
4128
|
+
const signature = await vault.signBytes(
|
|
4129
|
+
{ data: Buffer.from(messageHash), chain: Chain7.Ethereum },
|
|
4130
|
+
{}
|
|
4131
|
+
);
|
|
4132
|
+
const sigHex = formatSignature65(signature.signature, signature.recovery ?? 0);
|
|
4133
|
+
const authResponse = await client.authenticate({
|
|
4134
|
+
public_key: publicKey,
|
|
4135
|
+
chain_code_hex: chainCode,
|
|
4136
|
+
message: authMessage,
|
|
4137
|
+
signature: sigHex
|
|
4138
|
+
});
|
|
4139
|
+
return {
|
|
4140
|
+
token: authResponse.token,
|
|
4141
|
+
expiresAt: authResponse.expires_at
|
|
4142
|
+
};
|
|
4143
|
+
} catch (err) {
|
|
4144
|
+
lastError = err;
|
|
4145
|
+
if (attempt < maxAttempts && err.message?.includes("timeout")) {
|
|
4146
|
+
continue;
|
|
3754
4147
|
}
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
4148
|
+
throw err;
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
throw lastError || new Error("Authentication failed after all attempts");
|
|
4152
|
+
}
|
|
4153
|
+
function computePersonalSignHash(message) {
|
|
4154
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
4155
|
+
const prefix = `Ethereum Signed Message:
|
|
4156
|
+
${messageBytes.length}`;
|
|
4157
|
+
const prefixBytes = new TextEncoder().encode(prefix);
|
|
4158
|
+
const combined = new Uint8Array(prefixBytes.length + messageBytes.length);
|
|
4159
|
+
combined.set(prefixBytes);
|
|
4160
|
+
combined.set(messageBytes, prefixBytes.length);
|
|
4161
|
+
return keccak_256(combined);
|
|
4162
|
+
}
|
|
4163
|
+
function formatSignature65(sigHex, recovery) {
|
|
4164
|
+
const hex = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex;
|
|
4165
|
+
const bytes = Buffer.from(hex, "hex");
|
|
4166
|
+
if (bytes[0] === 48) {
|
|
4167
|
+
const { r, s } = decodeDERSignature(bytes);
|
|
4168
|
+
const v = (recovery + 27).toString(16).padStart(2, "0");
|
|
4169
|
+
return r + s + v;
|
|
4170
|
+
}
|
|
4171
|
+
if (hex.length >= 128) {
|
|
4172
|
+
const rs = hex.slice(0, 128);
|
|
4173
|
+
const v = (recovery + 27).toString(16).padStart(2, "0");
|
|
4174
|
+
return rs + v;
|
|
4175
|
+
}
|
|
4176
|
+
throw new Error(`Cannot format signature: unrecognized format (${hex.length} hex chars)`);
|
|
4177
|
+
}
|
|
4178
|
+
function decodeDERSignature(der) {
|
|
4179
|
+
let offset = 0;
|
|
4180
|
+
if (der[offset++] !== 48) throw new Error("Invalid DER: expected SEQUENCE");
|
|
4181
|
+
offset++;
|
|
4182
|
+
if (der[offset++] !== 2) throw new Error("Invalid DER: expected INTEGER for r");
|
|
4183
|
+
const rLen = der[offset++];
|
|
4184
|
+
const rBytes = der.subarray(offset, offset + rLen);
|
|
4185
|
+
offset += rLen;
|
|
4186
|
+
if (der[offset++] !== 2) throw new Error("Invalid DER: expected INTEGER for s");
|
|
4187
|
+
const sLen = der[offset++];
|
|
4188
|
+
const sBytes = der.subarray(offset, offset + sLen);
|
|
4189
|
+
const r = padTo32Bytes(stripLeadingZeros(rBytes));
|
|
4190
|
+
const s = padTo32Bytes(stripLeadingZeros(sBytes));
|
|
4191
|
+
return { r, s };
|
|
4192
|
+
}
|
|
4193
|
+
function stripLeadingZeros(buf) {
|
|
4194
|
+
let start = 0;
|
|
4195
|
+
while (start < buf.length - 1 && buf[start] === 0) start++;
|
|
4196
|
+
return Buffer.from(buf.subarray(start));
|
|
4197
|
+
}
|
|
4198
|
+
function padTo32Bytes(buf) {
|
|
4199
|
+
if (buf.length > 32) {
|
|
4200
|
+
return buf.subarray(buf.length - 32).toString("hex");
|
|
4201
|
+
}
|
|
4202
|
+
return buf.toString("hex").padStart(64, "0");
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
// src/agent/client.ts
|
|
4206
|
+
var AgentClient = class {
|
|
4207
|
+
baseUrl;
|
|
4208
|
+
authToken = null;
|
|
4209
|
+
verbose = false;
|
|
4210
|
+
constructor(baseUrl) {
|
|
4211
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
4212
|
+
}
|
|
4213
|
+
setAuthToken(token) {
|
|
4214
|
+
this.authToken = token;
|
|
4215
|
+
}
|
|
4216
|
+
// ============================================================================
|
|
4217
|
+
// Authentication
|
|
4218
|
+
// ============================================================================
|
|
4219
|
+
async authenticate(req) {
|
|
4220
|
+
const res = await fetch(`${this.baseUrl}/auth/token`, {
|
|
4221
|
+
method: "POST",
|
|
4222
|
+
headers: { "Content-Type": "application/json" },
|
|
4223
|
+
body: JSON.stringify(req)
|
|
4224
|
+
});
|
|
4225
|
+
if (!res.ok) {
|
|
4226
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
4227
|
+
throw new Error(`Auth failed (${res.status}): ${body.error || res.statusText}`);
|
|
4228
|
+
}
|
|
4229
|
+
const data = await res.json();
|
|
4230
|
+
this.authToken = data.token;
|
|
4231
|
+
return data;
|
|
4232
|
+
}
|
|
4233
|
+
// ============================================================================
|
|
4234
|
+
// Health
|
|
4235
|
+
// ============================================================================
|
|
4236
|
+
async healthCheck() {
|
|
4237
|
+
try {
|
|
4238
|
+
const res = await fetch(`${this.baseUrl}/healthz`);
|
|
4239
|
+
return res.ok;
|
|
4240
|
+
} catch {
|
|
4241
|
+
return false;
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
// ============================================================================
|
|
4245
|
+
// Conversations
|
|
4246
|
+
// ============================================================================
|
|
4247
|
+
async createConversation(publicKey) {
|
|
4248
|
+
const req = { public_key: publicKey };
|
|
4249
|
+
return this.post("/agent/conversations", req);
|
|
4250
|
+
}
|
|
4251
|
+
async listConversations(publicKey, skip = 0, take = 20) {
|
|
4252
|
+
const req = { public_key: publicKey, skip, take };
|
|
4253
|
+
return this.post("/agent/conversations/list", req);
|
|
4254
|
+
}
|
|
4255
|
+
async getConversation(conversationId, publicKey) {
|
|
4256
|
+
const req = { public_key: publicKey };
|
|
4257
|
+
return this.post(`/agent/conversations/${conversationId}`, req);
|
|
4258
|
+
}
|
|
4259
|
+
async deleteConversation(conversationId, publicKey) {
|
|
4260
|
+
await this.delete(`/agent/conversations/${conversationId}`, { public_key: publicKey });
|
|
4261
|
+
}
|
|
4262
|
+
// ============================================================================
|
|
4263
|
+
// Messages - JSON mode
|
|
4264
|
+
// ============================================================================
|
|
4265
|
+
async sendMessage(conversationId, req) {
|
|
4266
|
+
return this.post(`/agent/conversations/${conversationId}/messages`, req);
|
|
4267
|
+
}
|
|
4268
|
+
// ============================================================================
|
|
4269
|
+
// Messages - SSE Streaming mode
|
|
4270
|
+
// ============================================================================
|
|
4271
|
+
async sendMessageStream(conversationId, req, callbacks, signal) {
|
|
4272
|
+
const res = await fetch(`${this.baseUrl}/agent/conversations/${conversationId}/messages`, {
|
|
4273
|
+
method: "POST",
|
|
4274
|
+
headers: {
|
|
4275
|
+
"Content-Type": "application/json",
|
|
4276
|
+
Accept: "text/event-stream",
|
|
4277
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
4278
|
+
},
|
|
4279
|
+
body: JSON.stringify(req),
|
|
4280
|
+
signal
|
|
4281
|
+
});
|
|
4282
|
+
if (!res.ok) {
|
|
4283
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
4284
|
+
throw new Error(`Message failed (${res.status}): ${body.error || res.statusText}`);
|
|
4285
|
+
}
|
|
4286
|
+
if (!res.body) {
|
|
4287
|
+
throw new Error("No response body for SSE stream");
|
|
4288
|
+
}
|
|
4289
|
+
const result = {
|
|
4290
|
+
fullText: "",
|
|
4291
|
+
actions: [],
|
|
4292
|
+
suggestions: [],
|
|
4293
|
+
transactions: [],
|
|
4294
|
+
message: null
|
|
4295
|
+
};
|
|
4296
|
+
const reader = res.body.getReader();
|
|
4297
|
+
const decoder = new TextDecoder();
|
|
4298
|
+
let buffer = "";
|
|
4299
|
+
try {
|
|
4300
|
+
while (true) {
|
|
4301
|
+
const { done, value } = await reader.read();
|
|
4302
|
+
if (done) break;
|
|
4303
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4304
|
+
const lines = buffer.split("\n");
|
|
4305
|
+
buffer = lines.pop() || "";
|
|
4306
|
+
let currentEvent = "";
|
|
4307
|
+
let currentData = "";
|
|
4308
|
+
for (const line of lines) {
|
|
4309
|
+
if (line.startsWith("event: ")) {
|
|
4310
|
+
currentEvent = line.slice(7).trim();
|
|
4311
|
+
} else if (line.startsWith("data: ")) {
|
|
4312
|
+
currentData += (currentData ? "\n" : "") + line.slice(6);
|
|
4313
|
+
} else if (line === "") {
|
|
4314
|
+
if (currentEvent && currentData) {
|
|
4315
|
+
this.handleSSEEvent(currentEvent, currentData, result, callbacks);
|
|
4316
|
+
}
|
|
4317
|
+
currentEvent = "";
|
|
4318
|
+
currentData = "";
|
|
4319
|
+
} else if (line.startsWith(": ")) {
|
|
4320
|
+
}
|
|
3762
4321
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
return completeChainName(lastPart);
|
|
4322
|
+
if (currentEvent && currentData) {
|
|
4323
|
+
this.handleSSEEvent(currentEvent, currentData, result, callbacks);
|
|
3766
4324
|
}
|
|
3767
4325
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
4326
|
+
} finally {
|
|
4327
|
+
reader.releaseLock();
|
|
4328
|
+
}
|
|
4329
|
+
return result;
|
|
4330
|
+
}
|
|
4331
|
+
handleSSEEvent(event, data, result, callbacks) {
|
|
4332
|
+
try {
|
|
4333
|
+
const parsed = JSON.parse(data);
|
|
4334
|
+
switch (event) {
|
|
4335
|
+
case "text_delta":
|
|
4336
|
+
result.fullText += parsed.delta;
|
|
4337
|
+
callbacks.onTextDelta?.(parsed.delta);
|
|
4338
|
+
break;
|
|
4339
|
+
case "tool_progress":
|
|
4340
|
+
if (this.verbose) process.stderr.write(`[SSE:tool_progress] raw: ${data.slice(0, 1e3)}
|
|
4341
|
+
`);
|
|
4342
|
+
callbacks.onToolProgress?.(parsed.tool, parsed.status, parsed.label);
|
|
4343
|
+
break;
|
|
4344
|
+
case "title":
|
|
4345
|
+
callbacks.onTitle?.(parsed.title);
|
|
4346
|
+
break;
|
|
4347
|
+
case "actions":
|
|
4348
|
+
if (this.verbose) process.stderr.write(`[SSE:actions] raw: ${data.slice(0, 1e3)}
|
|
4349
|
+
`);
|
|
4350
|
+
result.actions.push(...parsed.actions || []);
|
|
4351
|
+
callbacks.onActions?.(parsed.actions || []);
|
|
4352
|
+
break;
|
|
4353
|
+
case "suggestions":
|
|
4354
|
+
result.suggestions.push(...parsed.suggestions || []);
|
|
4355
|
+
callbacks.onSuggestions?.(parsed.suggestions || []);
|
|
4356
|
+
break;
|
|
4357
|
+
case "tx_ready":
|
|
4358
|
+
if (this.verbose) process.stderr.write(`[SSE:tx_ready] raw: ${data.slice(0, 2e3)}
|
|
4359
|
+
`);
|
|
4360
|
+
result.transactions.push(parsed);
|
|
4361
|
+
callbacks.onTxReady?.(parsed);
|
|
4362
|
+
break;
|
|
4363
|
+
case "message":
|
|
4364
|
+
result.message = parsed.message || parsed;
|
|
4365
|
+
callbacks.onMessage?.(result.message);
|
|
4366
|
+
break;
|
|
4367
|
+
case "error":
|
|
4368
|
+
callbacks.onError?.(parsed.error);
|
|
4369
|
+
break;
|
|
4370
|
+
case "done":
|
|
4371
|
+
break;
|
|
3785
4372
|
}
|
|
3786
|
-
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
3787
|
-
const show = hits.length ? hits : COMMANDS;
|
|
3788
|
-
return [show, line];
|
|
3789
4373
|
} catch {
|
|
3790
|
-
return [[], line];
|
|
3791
4374
|
}
|
|
4375
|
+
}
|
|
4376
|
+
// ============================================================================
|
|
4377
|
+
// Private helpers
|
|
4378
|
+
// ============================================================================
|
|
4379
|
+
async post(path3, body) {
|
|
4380
|
+
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
4381
|
+
method: "POST",
|
|
4382
|
+
headers: {
|
|
4383
|
+
"Content-Type": "application/json",
|
|
4384
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
4385
|
+
},
|
|
4386
|
+
body: JSON.stringify(body)
|
|
4387
|
+
});
|
|
4388
|
+
if (!res.ok) {
|
|
4389
|
+
const errorBody = await res.json().catch(() => ({ error: res.statusText }));
|
|
4390
|
+
throw new Error(`Request failed (${res.status}): ${errorBody.error || res.statusText}`);
|
|
4391
|
+
}
|
|
4392
|
+
return await res.json();
|
|
4393
|
+
}
|
|
4394
|
+
async delete(path3, body) {
|
|
4395
|
+
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
4396
|
+
method: "DELETE",
|
|
4397
|
+
headers: {
|
|
4398
|
+
"Content-Type": "application/json",
|
|
4399
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
4400
|
+
},
|
|
4401
|
+
body: JSON.stringify(body)
|
|
4402
|
+
});
|
|
4403
|
+
if (!res.ok) {
|
|
4404
|
+
const errorBody = await res.json().catch(() => ({ error: res.statusText }));
|
|
4405
|
+
throw new Error(`Delete failed (${res.status}): ${errorBody.error || res.statusText}`);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
};
|
|
4409
|
+
|
|
4410
|
+
// src/agent/context.ts
|
|
4411
|
+
import { Chain as Chain8 } from "@vultisig/sdk";
|
|
4412
|
+
async function buildMessageContext(vault) {
|
|
4413
|
+
const context = {
|
|
4414
|
+
vault_address: vault.publicKeys.ecdsa,
|
|
4415
|
+
vault_name: vault.name
|
|
3792
4416
|
};
|
|
3793
|
-
}
|
|
3794
|
-
function completeFilePath(partial, filterVult) {
|
|
3795
4417
|
try {
|
|
3796
|
-
const
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
if (
|
|
3806
|
-
|
|
3807
|
-
basename = "";
|
|
4418
|
+
const chains = vault.chains;
|
|
4419
|
+
const addressEntries = await Promise.allSettled(
|
|
4420
|
+
chains.map(async (chain) => ({
|
|
4421
|
+
chain: chain.toString(),
|
|
4422
|
+
address: await vault.address(chain)
|
|
4423
|
+
}))
|
|
4424
|
+
);
|
|
4425
|
+
const addresses = {};
|
|
4426
|
+
for (const result of addressEntries) {
|
|
4427
|
+
if (result.status === "fulfilled") {
|
|
4428
|
+
addresses[result.value.chain] = result.value.address;
|
|
3808
4429
|
}
|
|
3809
4430
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
4431
|
+
context.addresses = addresses;
|
|
4432
|
+
} catch {
|
|
4433
|
+
}
|
|
4434
|
+
try {
|
|
4435
|
+
const balanceRecord = await vault.balances();
|
|
4436
|
+
const balanceInfos = [];
|
|
4437
|
+
for (const [key, balance] of Object.entries(balanceRecord)) {
|
|
4438
|
+
balanceInfos.push({
|
|
4439
|
+
chain: balance.chainId || key.split(":")[0] || "",
|
|
4440
|
+
asset: balance.symbol || "",
|
|
4441
|
+
symbol: balance.symbol || "",
|
|
4442
|
+
amount: balance.formattedAmount || balance.amount?.toString() || "0",
|
|
4443
|
+
decimals: balance.decimals || 18
|
|
4444
|
+
});
|
|
3813
4445
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4446
|
+
context.balances = balanceInfos;
|
|
4447
|
+
} catch {
|
|
4448
|
+
}
|
|
4449
|
+
try {
|
|
4450
|
+
const coins = [];
|
|
4451
|
+
const chains = vault.chains;
|
|
4452
|
+
for (const chain of chains) {
|
|
4453
|
+
coins.push({
|
|
4454
|
+
chain: chain.toString(),
|
|
4455
|
+
ticker: getNativeTokenTicker(chain),
|
|
4456
|
+
is_native_token: true,
|
|
4457
|
+
decimals: getNativeTokenDecimals(chain)
|
|
4458
|
+
});
|
|
4459
|
+
const tokens = vault.tokens[chain] || [];
|
|
4460
|
+
for (const token of tokens) {
|
|
4461
|
+
coins.push({
|
|
4462
|
+
chain: chain.toString(),
|
|
4463
|
+
ticker: token.symbol || "",
|
|
4464
|
+
contract_address: token.contractAddress || token.id,
|
|
4465
|
+
is_native_token: false,
|
|
4466
|
+
decimals: token.decimals || 18
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
context.coins = coins;
|
|
4471
|
+
} catch {
|
|
4472
|
+
}
|
|
4473
|
+
return context;
|
|
4474
|
+
}
|
|
4475
|
+
function getNativeTokenTicker(chain) {
|
|
4476
|
+
const tickers = {
|
|
4477
|
+
[Chain8.Ethereum]: "ETH",
|
|
4478
|
+
[Chain8.Bitcoin]: "BTC",
|
|
4479
|
+
[Chain8.Solana]: "SOL",
|
|
4480
|
+
[Chain8.THORChain]: "RUNE",
|
|
4481
|
+
[Chain8.Cosmos]: "ATOM",
|
|
4482
|
+
[Chain8.Avalanche]: "AVAX",
|
|
4483
|
+
[Chain8.BSC]: "BNB",
|
|
4484
|
+
[Chain8.Polygon]: "MATIC",
|
|
4485
|
+
[Chain8.Arbitrum]: "ETH",
|
|
4486
|
+
[Chain8.Optimism]: "ETH",
|
|
4487
|
+
[Chain8.Base]: "ETH",
|
|
4488
|
+
[Chain8.Blast]: "ETH",
|
|
4489
|
+
[Chain8.Litecoin]: "LTC",
|
|
4490
|
+
[Chain8.Dogecoin]: "DOGE",
|
|
4491
|
+
[Chain8.Dash]: "DASH",
|
|
4492
|
+
[Chain8.MayaChain]: "CACAO",
|
|
4493
|
+
[Chain8.Polkadot]: "DOT",
|
|
4494
|
+
[Chain8.Sui]: "SUI",
|
|
4495
|
+
[Chain8.Ton]: "TON",
|
|
4496
|
+
[Chain8.Tron]: "TRX",
|
|
4497
|
+
[Chain8.Ripple]: "XRP",
|
|
4498
|
+
[Chain8.Dydx]: "DYDX",
|
|
4499
|
+
[Chain8.Osmosis]: "OSMO",
|
|
4500
|
+
[Chain8.Terra]: "LUNA",
|
|
4501
|
+
[Chain8.Noble]: "USDC",
|
|
4502
|
+
[Chain8.Kujira]: "KUJI",
|
|
4503
|
+
[Chain8.Zksync]: "ETH",
|
|
4504
|
+
[Chain8.CronosChain]: "CRO"
|
|
4505
|
+
};
|
|
4506
|
+
return tickers[chain] || chain.toString();
|
|
4507
|
+
}
|
|
4508
|
+
function getNativeTokenDecimals(chain) {
|
|
4509
|
+
const decimals = {
|
|
4510
|
+
[Chain8.Bitcoin]: 8,
|
|
4511
|
+
[Chain8.Litecoin]: 8,
|
|
4512
|
+
[Chain8.Dogecoin]: 8,
|
|
4513
|
+
[Chain8.Dash]: 8,
|
|
4514
|
+
[Chain8.Solana]: 9,
|
|
4515
|
+
[Chain8.Sui]: 9,
|
|
4516
|
+
[Chain8.Ton]: 9,
|
|
4517
|
+
[Chain8.Polkadot]: 10,
|
|
4518
|
+
[Chain8.Cosmos]: 6,
|
|
4519
|
+
[Chain8.THORChain]: 8,
|
|
4520
|
+
[Chain8.MayaChain]: 10,
|
|
4521
|
+
[Chain8.Osmosis]: 6,
|
|
4522
|
+
[Chain8.Dydx]: 18,
|
|
4523
|
+
[Chain8.Tron]: 6,
|
|
4524
|
+
[Chain8.Ripple]: 6,
|
|
4525
|
+
[Chain8.Noble]: 6,
|
|
4526
|
+
[Chain8.Kujira]: 6,
|
|
4527
|
+
[Chain8.Terra]: 6
|
|
4528
|
+
};
|
|
4529
|
+
return decimals[chain] || 18;
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
// src/agent/executor.ts
|
|
4533
|
+
import { Chain as Chain9, Vultisig as Vultisig6 } from "@vultisig/sdk";
|
|
4534
|
+
|
|
4535
|
+
// src/agent/types.ts
|
|
4536
|
+
var AUTO_EXECUTE_ACTIONS = /* @__PURE__ */ new Set([
|
|
4537
|
+
"add_chain",
|
|
4538
|
+
"add_coin",
|
|
4539
|
+
"remove_coin",
|
|
4540
|
+
"remove_chain",
|
|
4541
|
+
"address_book_add",
|
|
4542
|
+
"address_book_remove",
|
|
4543
|
+
"get_address_book",
|
|
4544
|
+
"get_market_price",
|
|
4545
|
+
"get_balances",
|
|
4546
|
+
"get_portfolio",
|
|
4547
|
+
"search_token",
|
|
4548
|
+
"list_vaults",
|
|
4549
|
+
"build_swap_tx",
|
|
4550
|
+
"build_send_tx",
|
|
4551
|
+
"build_custom_tx",
|
|
4552
|
+
"build_tx",
|
|
4553
|
+
"sign_tx",
|
|
4554
|
+
"sign_typed_data",
|
|
4555
|
+
"read_evm_contract",
|
|
4556
|
+
"scan_tx",
|
|
4557
|
+
"thorchain_query"
|
|
4558
|
+
]);
|
|
4559
|
+
var PASSWORD_REQUIRED_ACTIONS = /* @__PURE__ */ new Set(["sign_tx", "sign_typed_data", "build_custom_tx"]);
|
|
4560
|
+
|
|
4561
|
+
// src/agent/executor.ts
|
|
4562
|
+
var AgentExecutor = class {
|
|
4563
|
+
vault;
|
|
4564
|
+
pendingPayloads = /* @__PURE__ */ new Map();
|
|
4565
|
+
password = null;
|
|
4566
|
+
verbose;
|
|
4567
|
+
constructor(vault, verbose = false) {
|
|
4568
|
+
this.vault = vault;
|
|
4569
|
+
this.verbose = verbose;
|
|
4570
|
+
}
|
|
4571
|
+
setPassword(password) {
|
|
4572
|
+
this.password = password;
|
|
4573
|
+
}
|
|
4574
|
+
/**
|
|
4575
|
+
* Store a server-built transaction (from tx_ready SSE event).
|
|
4576
|
+
* This allows sign_tx to find and sign it when the backend requests signing.
|
|
4577
|
+
*/
|
|
4578
|
+
storeServerTransaction(txReadyData) {
|
|
4579
|
+
if (this.verbose) process.stderr.write(`[executor] storeServerTransaction called, keys: ${Object.keys(txReadyData || {}).join(",")}
|
|
4580
|
+
`);
|
|
4581
|
+
const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
|
|
4582
|
+
if (!swapTx) {
|
|
4583
|
+
if (this.verbose) process.stderr.write(`[executor] storeServerTransaction: no swap_tx/send_tx/tx found in data
|
|
4584
|
+
`);
|
|
4585
|
+
return;
|
|
4586
|
+
}
|
|
4587
|
+
const chain = resolveChainFromTxReady(txReadyData) || Chain9.Ethereum;
|
|
4588
|
+
this.pendingPayloads.clear();
|
|
4589
|
+
this.pendingPayloads.set("latest", {
|
|
4590
|
+
payload: { __serverTx: true, ...txReadyData },
|
|
4591
|
+
coin: { chain, address: "", decimals: 18, ticker: "" },
|
|
4592
|
+
chain,
|
|
4593
|
+
timestamp: Date.now()
|
|
4594
|
+
});
|
|
4595
|
+
if (this.verbose) process.stderr.write(`[executor] Stored server tx for chain ${chain}, pendingPayloads size=${this.pendingPayloads.size}
|
|
4596
|
+
`);
|
|
4597
|
+
}
|
|
4598
|
+
hasPendingTransaction() {
|
|
4599
|
+
return this.pendingPayloads.has("latest");
|
|
4600
|
+
}
|
|
4601
|
+
shouldAutoExecute(action) {
|
|
4602
|
+
return action.auto_execute === true || AUTO_EXECUTE_ACTIONS.has(action.type);
|
|
4603
|
+
}
|
|
4604
|
+
requiresPassword(action) {
|
|
4605
|
+
return PASSWORD_REQUIRED_ACTIONS.has(action.type);
|
|
4606
|
+
}
|
|
4607
|
+
/**
|
|
4608
|
+
* Execute a single action and return the result.
|
|
4609
|
+
*/
|
|
4610
|
+
async executeAction(action) {
|
|
4611
|
+
try {
|
|
4612
|
+
const data = await this.dispatch(action);
|
|
4613
|
+
return {
|
|
4614
|
+
action: action.type,
|
|
4615
|
+
action_id: action.id,
|
|
4616
|
+
success: true,
|
|
4617
|
+
data
|
|
4618
|
+
};
|
|
4619
|
+
} catch (err) {
|
|
4620
|
+
return {
|
|
4621
|
+
action: action.type,
|
|
4622
|
+
action_id: action.id,
|
|
4623
|
+
success: false,
|
|
4624
|
+
error: err.message || String(err)
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
async dispatch(action) {
|
|
4629
|
+
if (this.verbose) process.stderr.write(`[dispatch] action.type=${action.type} action.id=${action.id}
|
|
4630
|
+
`);
|
|
4631
|
+
const params = action.params || {};
|
|
4632
|
+
switch (action.type) {
|
|
4633
|
+
case "get_balances":
|
|
4634
|
+
return this.getBalances(params);
|
|
4635
|
+
case "get_portfolio":
|
|
4636
|
+
return this.getPortfolio(params);
|
|
4637
|
+
case "add_chain":
|
|
4638
|
+
return this.addChain(params);
|
|
4639
|
+
case "remove_chain":
|
|
4640
|
+
return this.removeChain(params);
|
|
4641
|
+
case "add_coin":
|
|
4642
|
+
return this.addCoin(params);
|
|
4643
|
+
case "remove_coin":
|
|
4644
|
+
return this.removeCoin(params);
|
|
4645
|
+
case "build_send_tx":
|
|
4646
|
+
return this.buildSendTx(params);
|
|
4647
|
+
case "build_swap_tx":
|
|
4648
|
+
return this.buildSwapTx(params);
|
|
4649
|
+
case "build_tx":
|
|
4650
|
+
case "build_custom_tx":
|
|
4651
|
+
return this.buildTx(params);
|
|
4652
|
+
case "sign_tx":
|
|
4653
|
+
return this.signTx(params);
|
|
4654
|
+
case "get_address_book":
|
|
4655
|
+
return this.getAddressBook();
|
|
4656
|
+
case "address_book_add":
|
|
4657
|
+
return this.addAddressBookEntry(params);
|
|
4658
|
+
case "address_book_remove":
|
|
4659
|
+
return this.removeAddressBookEntry(params);
|
|
4660
|
+
case "search_token":
|
|
4661
|
+
return this.searchToken(params);
|
|
4662
|
+
case "list_vaults":
|
|
4663
|
+
return this.listVaults();
|
|
4664
|
+
case "sign_typed_data":
|
|
4665
|
+
return this.signTypedData(params);
|
|
4666
|
+
case "scan_tx":
|
|
4667
|
+
return this.scanTx(params);
|
|
4668
|
+
case "read_evm_contract":
|
|
4669
|
+
return this.readEvmContract(params);
|
|
4670
|
+
default:
|
|
4671
|
+
return { message: `Action type '${action.type}' not implemented locally` };
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
// ============================================================================
|
|
4675
|
+
// Balance & Portfolio
|
|
4676
|
+
// ============================================================================
|
|
4677
|
+
async getBalances(params) {
|
|
4678
|
+
const chainFilter = params.chain;
|
|
4679
|
+
const tickerFilter = params.ticker;
|
|
4680
|
+
const balanceRecord = await this.vault.balances();
|
|
4681
|
+
let entries = Object.entries(balanceRecord).map(([key, b]) => ({
|
|
4682
|
+
chain: b.chainId || key.split(":")[0] || "",
|
|
4683
|
+
symbol: b.symbol || "",
|
|
4684
|
+
amount: b.formattedAmount || b.amount?.toString() || "0",
|
|
4685
|
+
decimals: b.decimals || 18,
|
|
4686
|
+
raw_amount: b.amount?.toString()
|
|
4687
|
+
}));
|
|
4688
|
+
if (chainFilter) {
|
|
4689
|
+
const chain = resolveChain(chainFilter);
|
|
4690
|
+
if (chain) {
|
|
4691
|
+
entries = entries.filter((b) => b.chain.toLowerCase() === chain.toLowerCase());
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
if (tickerFilter) {
|
|
4695
|
+
entries = entries.filter((b) => b.symbol.toLowerCase() === tickerFilter.toLowerCase());
|
|
4696
|
+
}
|
|
4697
|
+
return { balances: entries };
|
|
4698
|
+
}
|
|
4699
|
+
async getPortfolio(_params) {
|
|
4700
|
+
const balanceRecord = await this.vault.balances();
|
|
4701
|
+
const entries = Object.entries(balanceRecord).map(([key, b]) => ({
|
|
4702
|
+
chain: b.chainId || key.split(":")[0] || "",
|
|
4703
|
+
symbol: b.symbol || "",
|
|
4704
|
+
amount: b.formattedAmount || b.amount?.toString() || "0",
|
|
4705
|
+
decimals: b.decimals || 18
|
|
4706
|
+
}));
|
|
4707
|
+
return { balances: entries };
|
|
4708
|
+
}
|
|
4709
|
+
// ============================================================================
|
|
4710
|
+
// Chain & Token Management
|
|
4711
|
+
// ============================================================================
|
|
4712
|
+
async addChain(params) {
|
|
4713
|
+
const chains = params.chains;
|
|
4714
|
+
if (chains && Array.isArray(chains)) {
|
|
4715
|
+
const results = [];
|
|
4716
|
+
for (const c of chains) {
|
|
4717
|
+
const name = typeof c === "string" ? c : c.chain;
|
|
4718
|
+
const chain2 = resolveChain(name);
|
|
4719
|
+
if (!chain2) throw new Error(`Unknown chain: ${name}`);
|
|
4720
|
+
await this.vault.addChain(chain2);
|
|
4721
|
+
const address2 = await this.vault.address(chain2);
|
|
4722
|
+
results.push({ chain: chain2.toString(), address: address2 });
|
|
4723
|
+
}
|
|
4724
|
+
return { added: results };
|
|
4725
|
+
}
|
|
4726
|
+
const chainName = params.chain;
|
|
4727
|
+
const chain = resolveChain(chainName);
|
|
4728
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4729
|
+
await this.vault.addChain(chain);
|
|
4730
|
+
const address = await this.vault.address(chain);
|
|
4731
|
+
return { chain: chain.toString(), address, added: true };
|
|
4732
|
+
}
|
|
4733
|
+
async removeChain(params) {
|
|
4734
|
+
const chainName = params.chain;
|
|
4735
|
+
const chain = resolveChain(chainName);
|
|
4736
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4737
|
+
await this.vault.removeChain(chain);
|
|
4738
|
+
return { chain: chain.toString(), removed: true };
|
|
4739
|
+
}
|
|
4740
|
+
async addCoin(params) {
|
|
4741
|
+
const tokens = params.tokens;
|
|
4742
|
+
if (tokens && Array.isArray(tokens)) {
|
|
4743
|
+
const results = [];
|
|
4744
|
+
for (const t of tokens) {
|
|
4745
|
+
const chain2 = resolveChain(t.chain);
|
|
4746
|
+
if (!chain2) throw new Error(`Unknown chain: ${t.chain}`);
|
|
4747
|
+
const symbol2 = t.symbol || t.ticker || "";
|
|
4748
|
+
await this.vault.addToken(chain2, {
|
|
4749
|
+
id: t.contract_address || t.contractAddress || "",
|
|
4750
|
+
symbol: symbol2,
|
|
4751
|
+
name: t.name || symbol2,
|
|
4752
|
+
decimals: t.decimals || 18,
|
|
4753
|
+
contractAddress: t.contract_address || t.contractAddress,
|
|
4754
|
+
chainId: chain2.toString()
|
|
4755
|
+
});
|
|
4756
|
+
results.push({ chain: chain2.toString(), symbol: symbol2 });
|
|
4757
|
+
}
|
|
4758
|
+
return { added: results };
|
|
4759
|
+
}
|
|
4760
|
+
const chainName = params.chain;
|
|
4761
|
+
const chain = resolveChain(chainName);
|
|
4762
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4763
|
+
const symbol = params.symbol || params.ticker;
|
|
4764
|
+
await this.vault.addToken(chain, {
|
|
4765
|
+
id: params.contract_address || params.contractAddress || "",
|
|
4766
|
+
symbol,
|
|
4767
|
+
name: params.name || symbol,
|
|
4768
|
+
decimals: params.decimals || 18,
|
|
4769
|
+
contractAddress: params.contract_address || params.contractAddress,
|
|
4770
|
+
chainId: chain.toString()
|
|
4771
|
+
});
|
|
4772
|
+
return { chain: chain.toString(), symbol, added: true };
|
|
4773
|
+
}
|
|
4774
|
+
async removeCoin(params) {
|
|
4775
|
+
const chainName = params.chain;
|
|
4776
|
+
const chain = resolveChain(chainName);
|
|
4777
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4778
|
+
const tokenId = params.token_id || params.id || params.contract_address;
|
|
4779
|
+
await this.vault.removeToken(chain, tokenId);
|
|
4780
|
+
return { chain: chain.toString(), removed: true };
|
|
4781
|
+
}
|
|
4782
|
+
// ============================================================================
|
|
4783
|
+
// Transaction Building
|
|
4784
|
+
// ============================================================================
|
|
4785
|
+
async buildSendTx(params) {
|
|
4786
|
+
const chainName = params.chain || params.from_chain;
|
|
4787
|
+
const chain = resolveChain(chainName);
|
|
4788
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4789
|
+
const symbol = params.symbol || params.ticker;
|
|
4790
|
+
const toAddress = params.address || params.to || params.destination;
|
|
4791
|
+
const amountStr = params.amount;
|
|
4792
|
+
if (!toAddress) throw new Error("Destination address is required");
|
|
4793
|
+
if (!amountStr) throw new Error("Amount is required");
|
|
4794
|
+
const address = await this.vault.address(chain);
|
|
4795
|
+
const balance = await this.vault.balance(chain, params.token_id);
|
|
4796
|
+
const coin = {
|
|
4797
|
+
chain,
|
|
4798
|
+
address,
|
|
4799
|
+
decimals: balance.decimals,
|
|
4800
|
+
ticker: symbol || balance.symbol,
|
|
4801
|
+
id: params.token_id
|
|
4802
|
+
};
|
|
4803
|
+
const amount = parseAmount(amountStr, balance.decimals);
|
|
4804
|
+
const memo = params.memo;
|
|
4805
|
+
const payload = await this.vault.prepareSendTx({ coin, receiver: toAddress, amount, memo });
|
|
4806
|
+
this.pendingPayloads.clear();
|
|
4807
|
+
const payloadId = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
4808
|
+
this.pendingPayloads.set(payloadId, { payload, coin, chain, timestamp: Date.now() });
|
|
4809
|
+
this.pendingPayloads.set("latest", { payload, coin, chain, timestamp: Date.now() });
|
|
4810
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
4811
|
+
return {
|
|
4812
|
+
keysign_payload: payloadId,
|
|
4813
|
+
from_chain: chain.toString(),
|
|
4814
|
+
from_symbol: coin.ticker,
|
|
4815
|
+
amount: amountStr,
|
|
4816
|
+
sender: address,
|
|
4817
|
+
destination: toAddress,
|
|
4818
|
+
memo: memo || void 0,
|
|
4819
|
+
message_hashes: messageHashes,
|
|
4820
|
+
tx_details: {
|
|
4821
|
+
chain: chain.toString(),
|
|
4822
|
+
from: address,
|
|
4823
|
+
to: toAddress,
|
|
4824
|
+
amount: amountStr,
|
|
4825
|
+
symbol: coin.ticker
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
async buildSwapTx(params) {
|
|
4830
|
+
if (this.verbose) process.stderr.write(`[build_swap_tx] called with params: ${JSON.stringify(params).slice(0, 500)}
|
|
4831
|
+
`);
|
|
4832
|
+
const fromChainName = params.from_chain || params.chain;
|
|
4833
|
+
const toChainName = params.to_chain;
|
|
4834
|
+
const fromChain = resolveChain(fromChainName);
|
|
4835
|
+
const toChain = toChainName ? resolveChain(toChainName) : null;
|
|
4836
|
+
if (!fromChain) throw new Error(`Unknown from_chain: ${fromChainName}`);
|
|
4837
|
+
const amountStr = params.amount;
|
|
4838
|
+
const fromSymbol = params.from_symbol || params.from_token || "";
|
|
4839
|
+
const toSymbol = params.to_symbol || params.to_token || "";
|
|
4840
|
+
const fromToken = params.from_contract || params.from_token_id;
|
|
4841
|
+
const toToken = params.to_contract || params.to_token_id;
|
|
4842
|
+
const fromCoin = { chain: fromChain, token: fromToken || void 0 };
|
|
4843
|
+
const toCoin = { chain: toChain || fromChain, token: toToken || void 0 };
|
|
4844
|
+
const quote = await this.vault.getSwapQuote({
|
|
4845
|
+
fromCoin,
|
|
4846
|
+
toCoin,
|
|
4847
|
+
amount: parseFloat(amountStr)
|
|
4848
|
+
});
|
|
4849
|
+
const swapResult = await this.vault.prepareSwapTx({
|
|
4850
|
+
fromCoin,
|
|
4851
|
+
toCoin,
|
|
4852
|
+
amount: parseFloat(amountStr),
|
|
4853
|
+
swapQuote: quote,
|
|
4854
|
+
autoApprove: true
|
|
4855
|
+
});
|
|
4856
|
+
const chain = fromChain;
|
|
4857
|
+
const payload = swapResult.keysignPayload;
|
|
4858
|
+
this.pendingPayloads.clear();
|
|
4859
|
+
const payloadId = `swap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
4860
|
+
this.pendingPayloads.set(payloadId, { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
|
|
4861
|
+
this.pendingPayloads.set("latest", { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
|
|
4862
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
4863
|
+
return {
|
|
4864
|
+
keysign_payload: payloadId,
|
|
4865
|
+
from_chain: fromChain.toString(),
|
|
4866
|
+
to_chain: (toChain || fromChain).toString(),
|
|
4867
|
+
from_symbol: fromSymbol,
|
|
4868
|
+
to_symbol: toSymbol,
|
|
4869
|
+
amount: amountStr,
|
|
4870
|
+
estimated_output: quote.estimatedOutput?.toString(),
|
|
4871
|
+
provider: quote.provider,
|
|
4872
|
+
message_hashes: messageHashes
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4875
|
+
async buildTx(params) {
|
|
4876
|
+
if (params.function_name && params.contract_address) {
|
|
4877
|
+
return this.buildContractCallTx(params);
|
|
4878
|
+
}
|
|
4879
|
+
if (params.data || params.calldata || params.hex_payload) {
|
|
4880
|
+
const txData = {
|
|
4881
|
+
to: params.to || params.address || params.contract,
|
|
4882
|
+
value: params.value || "0",
|
|
4883
|
+
data: params.data || params.calldata || params.hex_payload,
|
|
4884
|
+
chain: params.chain,
|
|
4885
|
+
chain_id: params.chain_id
|
|
4886
|
+
};
|
|
4887
|
+
this.storeServerTransaction({
|
|
4888
|
+
tx: txData,
|
|
4889
|
+
chain: params.chain,
|
|
4890
|
+
from_chain: params.chain
|
|
4891
|
+
});
|
|
4892
|
+
const chain = resolveChain(params.chain) || Chain9.Ethereum;
|
|
4893
|
+
const address = await this.vault.address(chain);
|
|
4894
|
+
return {
|
|
4895
|
+
status: "ready",
|
|
4896
|
+
chain: chain.toString(),
|
|
4897
|
+
from: address,
|
|
4898
|
+
to: txData.to,
|
|
4899
|
+
value: txData.value,
|
|
4900
|
+
has_calldata: true,
|
|
4901
|
+
message: "Transaction built. Ready to sign."
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4904
|
+
return this.buildSendTx(params);
|
|
4905
|
+
}
|
|
4906
|
+
/**
|
|
4907
|
+
* Build, sign, and broadcast an EVM contract call transaction from structured params.
|
|
4908
|
+
* Encodes function_name + typed params into ABI calldata, then signs via signServerTx.
|
|
4909
|
+
*/
|
|
4910
|
+
async buildContractCallTx(params) {
|
|
4911
|
+
const chainName = params.chain || "Ethereum";
|
|
4912
|
+
const chain = resolveChain(chainName);
|
|
4913
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4914
|
+
const contractAddress = params.contract_address;
|
|
4915
|
+
const functionName = params.function_name;
|
|
4916
|
+
const typedParams = params.params;
|
|
4917
|
+
const value = params.value || "0";
|
|
4918
|
+
const calldata = await encodeContractCall(functionName, typedParams || []);
|
|
4919
|
+
if (this.verbose) process.stderr.write(`[build_contract_tx] ${functionName}(${(typedParams || []).map((p) => p.type).join(",")}) on ${contractAddress} chain=${chain}
|
|
4920
|
+
`);
|
|
4921
|
+
const serverTxData = {
|
|
4922
|
+
__serverTx: true,
|
|
4923
|
+
tx: {
|
|
4924
|
+
to: contractAddress,
|
|
4925
|
+
value,
|
|
4926
|
+
data: calldata
|
|
4927
|
+
},
|
|
4928
|
+
chain: chainName,
|
|
4929
|
+
from_chain: chainName
|
|
4930
|
+
};
|
|
4931
|
+
this.pendingPayloads.set("latest", {
|
|
4932
|
+
payload: serverTxData,
|
|
4933
|
+
coin: { chain, address: "", decimals: 18, ticker: "" },
|
|
4934
|
+
chain,
|
|
4935
|
+
timestamp: Date.now()
|
|
4936
|
+
});
|
|
4937
|
+
return this.signServerTx(serverTxData, chain, { chain: chainName });
|
|
4938
|
+
}
|
|
4939
|
+
// ============================================================================
|
|
4940
|
+
// Transaction Signing
|
|
4941
|
+
// ============================================================================
|
|
4942
|
+
async signTx(params) {
|
|
4943
|
+
if (this.verbose) process.stderr.write(`[sign_tx] params: ${JSON.stringify(params).slice(0, 500)}
|
|
4944
|
+
`);
|
|
4945
|
+
if (this.verbose) process.stderr.write(`[sign_tx] pendingPayloads keys: ${[...this.pendingPayloads.keys()].join(", ")}
|
|
4946
|
+
`);
|
|
4947
|
+
const payloadId = params.keysign_payload || params.payload_id || "latest";
|
|
4948
|
+
const stored = this.pendingPayloads.get(payloadId);
|
|
4949
|
+
if (!stored) {
|
|
4950
|
+
throw new Error("No pending transaction to sign. Build a transaction first.");
|
|
4951
|
+
}
|
|
4952
|
+
const { payload, chain } = stored;
|
|
4953
|
+
if (payload.__serverTx) {
|
|
4954
|
+
return this.signServerTx(payload, chain, params);
|
|
4955
|
+
}
|
|
4956
|
+
return this.signSdkTx(payload, chain, payloadId);
|
|
4957
|
+
}
|
|
4958
|
+
/**
|
|
4959
|
+
* Sign and broadcast an SDK-built transaction (keysign payload from local build methods).
|
|
4960
|
+
*/
|
|
4961
|
+
async signSdkTx(payload, chain, _payloadId) {
|
|
4962
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
4963
|
+
if (this.password) {
|
|
4964
|
+
await this.vault.unlock?.(this.password);
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
4968
|
+
const signature = await this.vault.sign(
|
|
4969
|
+
{
|
|
4970
|
+
transaction: payload,
|
|
4971
|
+
chain: payload.coin?.chain || chain,
|
|
4972
|
+
messageHashes
|
|
4973
|
+
},
|
|
4974
|
+
{}
|
|
4975
|
+
);
|
|
4976
|
+
const txHash = await this.vault.broadcastTx({
|
|
4977
|
+
chain,
|
|
4978
|
+
keysignPayload: payload,
|
|
4979
|
+
signature
|
|
4980
|
+
});
|
|
4981
|
+
this.pendingPayloads.clear();
|
|
4982
|
+
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
4983
|
+
return {
|
|
4984
|
+
tx_hash: txHash,
|
|
4985
|
+
chain: chain.toString(),
|
|
4986
|
+
status: "pending",
|
|
4987
|
+
explorer_url: explorerUrl
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
/**
|
|
4991
|
+
* Sign and broadcast a server-built transaction (raw EVM tx from tx_ready SSE).
|
|
4992
|
+
* Uses vault.prepareSendTx with memo field to carry the calldata.
|
|
4993
|
+
*/
|
|
4994
|
+
async signServerTx(serverTxData, defaultChain, params) {
|
|
4995
|
+
const swapTx = serverTxData.swap_tx || serverTxData.send_tx || serverTxData.tx;
|
|
4996
|
+
if (!swapTx?.to) {
|
|
4997
|
+
throw new Error("Server transaction missing required fields (to)");
|
|
4998
|
+
}
|
|
4999
|
+
const chainName = params.chain || serverTxData.chain || serverTxData.from_chain;
|
|
5000
|
+
const chainId = serverTxData.chain_id || swapTx.chainId;
|
|
5001
|
+
let chain = defaultChain;
|
|
5002
|
+
if (chainName) {
|
|
5003
|
+
chain = resolveChain(chainName) || defaultChain;
|
|
5004
|
+
} else if (chainId) {
|
|
5005
|
+
chain = resolveChainId(chainId) || defaultChain;
|
|
5006
|
+
}
|
|
5007
|
+
const address = await this.vault.address(chain);
|
|
5008
|
+
const balance = await this.vault.balance(chain);
|
|
5009
|
+
const coin = {
|
|
5010
|
+
chain,
|
|
5011
|
+
address,
|
|
5012
|
+
decimals: balance.decimals || 18,
|
|
5013
|
+
ticker: balance.symbol || chain.toString()
|
|
5014
|
+
};
|
|
5015
|
+
const amount = BigInt(swapTx.value || "0");
|
|
5016
|
+
const hasCalldata = !!(swapTx.data && swapTx.data !== "0x");
|
|
5017
|
+
if (this.verbose) process.stderr.write(`[sign_server_tx] chain=${chain}, to=${swapTx.to}, value=${swapTx.value}, amount=${amount}, hasCalldata=${hasCalldata}
|
|
5018
|
+
`);
|
|
5019
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5020
|
+
if (this.password) {
|
|
5021
|
+
await this.vault.unlock?.(this.password);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
const buildAmount = amount === 0n && hasCalldata ? 1n : amount;
|
|
5025
|
+
const keysignPayload = await this.vault.prepareSendTx({
|
|
5026
|
+
coin,
|
|
5027
|
+
receiver: swapTx.to,
|
|
5028
|
+
amount: buildAmount,
|
|
5029
|
+
memo: swapTx.data
|
|
5030
|
+
});
|
|
5031
|
+
if (amount === 0n && hasCalldata) {
|
|
5032
|
+
;
|
|
5033
|
+
keysignPayload.toAmount = "0";
|
|
5034
|
+
}
|
|
5035
|
+
const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
|
|
5036
|
+
const signature = await this.vault.sign(
|
|
5037
|
+
{
|
|
5038
|
+
transaction: keysignPayload,
|
|
5039
|
+
chain,
|
|
5040
|
+
messageHashes
|
|
5041
|
+
},
|
|
5042
|
+
{}
|
|
5043
|
+
);
|
|
5044
|
+
const txHash = await this.vault.broadcastTx({
|
|
5045
|
+
chain,
|
|
5046
|
+
keysignPayload,
|
|
5047
|
+
signature
|
|
5048
|
+
});
|
|
5049
|
+
this.pendingPayloads.clear();
|
|
5050
|
+
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
5051
|
+
return {
|
|
5052
|
+
tx_hash: txHash,
|
|
5053
|
+
chain: chain.toString(),
|
|
5054
|
+
status: "pending",
|
|
5055
|
+
explorer_url: explorerUrl
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
// ============================================================================
|
|
5059
|
+
// EIP-712 Typed Data Signing
|
|
5060
|
+
// ============================================================================
|
|
5061
|
+
/**
|
|
5062
|
+
* Sign EIP-712 typed data. Computes the EIP-712 hash and signs with vault.signBytes().
|
|
5063
|
+
* Supports two formats:
|
|
5064
|
+
* - Flat: { domain, types, message, primaryType } — single typed data
|
|
5065
|
+
* - Payloads array: { payloads: [{id, domain, types, message, primaryType, chain}, ...] }
|
|
5066
|
+
* Used by Polymarket which requires signing both an Order and a ClobAuth.
|
|
5067
|
+
*/
|
|
5068
|
+
async signTypedData(params) {
|
|
5069
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5070
|
+
if (this.password) {
|
|
5071
|
+
await this.vault.unlock?.(this.password);
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
const payloads = params.payloads;
|
|
5075
|
+
if (payloads && Array.isArray(payloads)) {
|
|
5076
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] payloads mode, ${payloads.length} items
|
|
5077
|
+
`);
|
|
5078
|
+
const signatures = [];
|
|
5079
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
5080
|
+
const payload = payloads[i];
|
|
5081
|
+
const id = payload.id || payload.name || "default";
|
|
5082
|
+
if (i > 0) {
|
|
5083
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] waiting 5s between MPC sessions...
|
|
5084
|
+
`);
|
|
5085
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
5086
|
+
}
|
|
5087
|
+
const sig = await this.signSingleTypedData(payload);
|
|
5088
|
+
signatures.push({ id, ...sig });
|
|
5089
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] signed payload "${id}"
|
|
5090
|
+
`);
|
|
5091
|
+
}
|
|
5092
|
+
return {
|
|
5093
|
+
signatures,
|
|
5094
|
+
pm_order_ref: params.pm_order_ref,
|
|
5095
|
+
auto_submit: !!(params.__pm_auto_submit || params.auto_submit)
|
|
5096
|
+
};
|
|
5097
|
+
}
|
|
5098
|
+
return this.signSingleTypedData(params);
|
|
5099
|
+
}
|
|
5100
|
+
/**
|
|
5101
|
+
* Sign a single EIP-712 typed data object.
|
|
5102
|
+
*/
|
|
5103
|
+
async signSingleTypedData(params) {
|
|
5104
|
+
const domain = params.domain;
|
|
5105
|
+
const types = params.types;
|
|
5106
|
+
const message = params.message;
|
|
5107
|
+
const primaryType = params.primaryType || params.primary_type;
|
|
5108
|
+
if (!domain || !types || !message || !primaryType) {
|
|
5109
|
+
throw new Error("sign_typed_data requires domain, types, message, and primaryType");
|
|
5110
|
+
}
|
|
5111
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] primaryType=${primaryType} domain.name=${domain.name}
|
|
5112
|
+
`);
|
|
5113
|
+
const eip712Hash = await computeEIP712Hash(domain, types, primaryType, message);
|
|
5114
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] hash=${eip712Hash}
|
|
5115
|
+
`);
|
|
5116
|
+
const chainName = params.chain;
|
|
5117
|
+
const chainId = domain.chainId;
|
|
5118
|
+
let chain = Chain9.Ethereum;
|
|
5119
|
+
if (chainName) {
|
|
5120
|
+
chain = resolveChain(chainName) || Chain9.Ethereum;
|
|
5121
|
+
} else if (chainId) {
|
|
5122
|
+
chain = resolveChainId(chainId) || Chain9.Ethereum;
|
|
5123
|
+
}
|
|
5124
|
+
const sigResult = await this.vault.signBytes({
|
|
5125
|
+
data: eip712Hash,
|
|
5126
|
+
chain
|
|
5127
|
+
});
|
|
5128
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] signed, format=${sigResult.format}, recovery=${sigResult.recovery}
|
|
5129
|
+
`);
|
|
5130
|
+
const { r, s } = parseDERSignature(sigResult.signature);
|
|
5131
|
+
const v = (sigResult.recovery ?? 0) + 27;
|
|
5132
|
+
const ethSignature = "0x" + r + s + v.toString(16).padStart(2, "0");
|
|
5133
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] r=${r.slice(0, 16)}... s=${s.slice(0, 16)}... v=${v}
|
|
5134
|
+
`);
|
|
5135
|
+
return {
|
|
5136
|
+
signature: ethSignature,
|
|
5137
|
+
r: "0x" + r,
|
|
5138
|
+
s: "0x" + s,
|
|
5139
|
+
v,
|
|
5140
|
+
recovery: sigResult.recovery,
|
|
5141
|
+
hash: eip712Hash
|
|
5142
|
+
};
|
|
5143
|
+
}
|
|
5144
|
+
// ============================================================================
|
|
5145
|
+
// Address Book
|
|
5146
|
+
// ============================================================================
|
|
5147
|
+
async getAddressBook() {
|
|
5148
|
+
return { entries: [], message: "Address book retrieved" };
|
|
5149
|
+
}
|
|
5150
|
+
async addAddressBookEntry(params) {
|
|
5151
|
+
return { added: true, name: params.name, address: params.address, chain: params.chain };
|
|
5152
|
+
}
|
|
5153
|
+
async removeAddressBookEntry(params) {
|
|
5154
|
+
return { removed: true, address: params.address };
|
|
5155
|
+
}
|
|
5156
|
+
// ============================================================================
|
|
5157
|
+
// Token Search & Other
|
|
5158
|
+
// ============================================================================
|
|
5159
|
+
async searchToken(params) {
|
|
5160
|
+
return { message: `Token search for '${params.query || params.symbol}' - delegated to backend` };
|
|
5161
|
+
}
|
|
5162
|
+
async listVaults() {
|
|
5163
|
+
return {
|
|
5164
|
+
vaults: [{
|
|
5165
|
+
name: this.vault.name,
|
|
5166
|
+
id: this.vault.id,
|
|
5167
|
+
type: this.vault.type,
|
|
5168
|
+
chains: this.vault.chains.map((c) => c.toString())
|
|
5169
|
+
}]
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
5172
|
+
async scanTx(params) {
|
|
5173
|
+
return { message: `Transaction scan for ${params.tx_hash || "unknown"} - delegated to backend` };
|
|
5174
|
+
}
|
|
5175
|
+
async readEvmContract(params) {
|
|
5176
|
+
return { message: `EVM contract read for ${params.contract || "unknown"} - delegated to backend` };
|
|
5177
|
+
}
|
|
5178
|
+
};
|
|
5179
|
+
async function encodeContractCall(functionName, params) {
|
|
5180
|
+
const types = params.map((p) => p.type);
|
|
5181
|
+
const sig = `${functionName}(${types.join(",")})`;
|
|
5182
|
+
const selector = await keccak256Selector(sig);
|
|
5183
|
+
let encoded = "";
|
|
5184
|
+
for (const param of params) {
|
|
5185
|
+
encoded += abiEncodeParam(param.type, param.value);
|
|
5186
|
+
}
|
|
5187
|
+
return "0x" + selector + encoded;
|
|
5188
|
+
}
|
|
5189
|
+
async function keccak256Selector(sig) {
|
|
5190
|
+
const { keccak_256: keccak_2562 } = await Promise.resolve().then(() => (init_sha3(), sha3_exports));
|
|
5191
|
+
const hash = keccak_2562(new TextEncoder().encode(sig));
|
|
5192
|
+
return Buffer.from(hash).toString("hex").slice(0, 8);
|
|
5193
|
+
}
|
|
5194
|
+
function abiEncodeParam(type, value) {
|
|
5195
|
+
if (type === "address") {
|
|
5196
|
+
const addr = value.startsWith("0x") ? value.slice(2) : value;
|
|
5197
|
+
return addr.toLowerCase().padStart(64, "0");
|
|
5198
|
+
}
|
|
5199
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
5200
|
+
const n = BigInt(value);
|
|
5201
|
+
const hex = n.toString(16);
|
|
5202
|
+
return hex.padStart(64, "0");
|
|
5203
|
+
}
|
|
5204
|
+
if (type === "bool") {
|
|
5205
|
+
return (value === "true" || value === "1" ? "1" : "0").padStart(64, "0");
|
|
5206
|
+
}
|
|
5207
|
+
if (type === "bytes32") {
|
|
5208
|
+
const b2 = value.startsWith("0x") ? value.slice(2) : value;
|
|
5209
|
+
return b2.padEnd(64, "0");
|
|
5210
|
+
}
|
|
5211
|
+
const b = value.startsWith("0x") ? value.slice(2) : Buffer.from(value).toString("hex");
|
|
5212
|
+
return b.padStart(64, "0");
|
|
5213
|
+
}
|
|
5214
|
+
function resolveChain(name) {
|
|
5215
|
+
if (!name) return null;
|
|
5216
|
+
if (Object.values(Chain9).includes(name)) {
|
|
5217
|
+
return name;
|
|
5218
|
+
}
|
|
5219
|
+
const lower = name.toLowerCase();
|
|
5220
|
+
for (const [, value] of Object.entries(Chain9)) {
|
|
5221
|
+
if (typeof value === "string" && value.toLowerCase() === lower) {
|
|
5222
|
+
return value;
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
const aliases = {
|
|
5226
|
+
eth: "Ethereum",
|
|
5227
|
+
btc: "Bitcoin",
|
|
5228
|
+
sol: "Solana",
|
|
5229
|
+
bnb: "BSC",
|
|
5230
|
+
avax: "Avalanche",
|
|
5231
|
+
matic: "Polygon",
|
|
5232
|
+
arb: "Arbitrum",
|
|
5233
|
+
op: "Optimism",
|
|
5234
|
+
ltc: "Litecoin",
|
|
5235
|
+
doge: "Dogecoin",
|
|
5236
|
+
dot: "Polkadot",
|
|
5237
|
+
atom: "Cosmos",
|
|
5238
|
+
rune: "THORChain",
|
|
5239
|
+
thor: "THORChain",
|
|
5240
|
+
sui: "Sui",
|
|
5241
|
+
ton: "Ton",
|
|
5242
|
+
trx: "Tron",
|
|
5243
|
+
xrp: "Ripple"
|
|
5244
|
+
};
|
|
5245
|
+
const aliased = aliases[lower];
|
|
5246
|
+
if (aliased && Object.values(Chain9).includes(aliased)) {
|
|
5247
|
+
return aliased;
|
|
5248
|
+
}
|
|
5249
|
+
return null;
|
|
5250
|
+
}
|
|
5251
|
+
function parseAmount(amountStr, decimals) {
|
|
5252
|
+
const [whole, frac = ""] = amountStr.split(".");
|
|
5253
|
+
const paddedFrac = frac.slice(0, decimals).padEnd(decimals, "0");
|
|
5254
|
+
return BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(paddedFrac || "0");
|
|
5255
|
+
}
|
|
5256
|
+
function resolveChainFromTxReady(txReadyData) {
|
|
5257
|
+
if (txReadyData.chain) {
|
|
5258
|
+
const chain = resolveChain(txReadyData.chain);
|
|
5259
|
+
if (chain) return chain;
|
|
5260
|
+
}
|
|
5261
|
+
if (txReadyData.from_chain) {
|
|
5262
|
+
const chain = resolveChain(txReadyData.from_chain);
|
|
5263
|
+
if (chain) return chain;
|
|
5264
|
+
}
|
|
5265
|
+
if (txReadyData.chain_id) {
|
|
5266
|
+
const chain = resolveChainId(txReadyData.chain_id);
|
|
5267
|
+
if (chain) return chain;
|
|
5268
|
+
}
|
|
5269
|
+
const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
|
|
5270
|
+
if (swapTx?.chainId) {
|
|
5271
|
+
const chain = resolveChainId(swapTx.chainId);
|
|
5272
|
+
if (chain) return chain;
|
|
5273
|
+
}
|
|
5274
|
+
return null;
|
|
5275
|
+
}
|
|
5276
|
+
function resolveChainId(chainId) {
|
|
5277
|
+
const id = typeof chainId === "string" ? parseInt(chainId, 10) : chainId;
|
|
5278
|
+
if (isNaN(id)) return null;
|
|
5279
|
+
const chainIdMap = {
|
|
5280
|
+
1: Chain9.Ethereum,
|
|
5281
|
+
56: Chain9.BSC,
|
|
5282
|
+
137: Chain9.Polygon,
|
|
5283
|
+
43114: Chain9.Avalanche,
|
|
5284
|
+
42161: Chain9.Arbitrum,
|
|
5285
|
+
10: Chain9.Optimism,
|
|
5286
|
+
8453: Chain9.Base,
|
|
5287
|
+
81457: Chain9.Blast,
|
|
5288
|
+
324: Chain9.Zksync,
|
|
5289
|
+
25: Chain9.CronosChain
|
|
5290
|
+
};
|
|
5291
|
+
return chainIdMap[id] || null;
|
|
5292
|
+
}
|
|
5293
|
+
async function computeEIP712Hash(domain, types, primaryType, message) {
|
|
5294
|
+
const { keccak_256: keccak_2562 } = await Promise.resolve().then(() => (init_sha3(), sha3_exports));
|
|
5295
|
+
const domainSeparator = hashStruct("EIP712Domain", domain, types, keccak_2562);
|
|
5296
|
+
const messageHash = hashStruct(primaryType, message, types, keccak_2562);
|
|
5297
|
+
const prefix = new Uint8Array([25, 1]);
|
|
5298
|
+
const combined = new Uint8Array(2 + 32 + 32);
|
|
5299
|
+
combined.set(prefix, 0);
|
|
5300
|
+
combined.set(domainSeparator, 2);
|
|
5301
|
+
combined.set(messageHash, 34);
|
|
5302
|
+
const finalHash = keccak_2562(combined);
|
|
5303
|
+
return "0x" + Buffer.from(finalHash).toString("hex");
|
|
5304
|
+
}
|
|
5305
|
+
function hashStruct(typeName, data, types, keccak) {
|
|
5306
|
+
const typeHash = hashType(typeName, types, keccak);
|
|
5307
|
+
const encodedData = encodeData(typeName, data, types, keccak);
|
|
5308
|
+
const combined = new Uint8Array(32 + encodedData.length);
|
|
5309
|
+
combined.set(typeHash, 0);
|
|
5310
|
+
combined.set(encodedData, 32);
|
|
5311
|
+
return keccak(combined);
|
|
5312
|
+
}
|
|
5313
|
+
function hashType(typeName, types, keccak) {
|
|
5314
|
+
const encoded = encodeType(typeName, types);
|
|
5315
|
+
return keccak(new TextEncoder().encode(encoded));
|
|
5316
|
+
}
|
|
5317
|
+
function encodeType(typeName, types) {
|
|
5318
|
+
const fields = getTypeFields(typeName, types);
|
|
5319
|
+
if (!fields) return "";
|
|
5320
|
+
const refs = /* @__PURE__ */ new Set();
|
|
5321
|
+
findReferencedTypes(typeName, types, refs);
|
|
5322
|
+
refs.delete(typeName);
|
|
5323
|
+
const sortedRefs = [...refs].sort();
|
|
5324
|
+
let result = `${typeName}(${fields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
5325
|
+
for (const ref of sortedRefs) {
|
|
5326
|
+
const refFields = getTypeFields(ref, types);
|
|
5327
|
+
if (refFields) {
|
|
5328
|
+
result += `${ref}(${refFields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
return result;
|
|
5332
|
+
}
|
|
5333
|
+
function findReferencedTypes(typeName, types, refs) {
|
|
5334
|
+
if (refs.has(typeName)) return;
|
|
5335
|
+
const fields = getTypeFields(typeName, types);
|
|
5336
|
+
if (!fields) return;
|
|
5337
|
+
refs.add(typeName);
|
|
5338
|
+
for (const field of fields) {
|
|
5339
|
+
const baseType = field.type.replace(/\[\d*\]$/, "");
|
|
5340
|
+
if (types[baseType]) {
|
|
5341
|
+
findReferencedTypes(baseType, types, refs);
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
function getTypeFields(typeName, types) {
|
|
5346
|
+
if (types[typeName]) return types[typeName];
|
|
5347
|
+
if (typeName === "EIP712Domain") {
|
|
5348
|
+
return [
|
|
5349
|
+
{ name: "name", type: "string" },
|
|
5350
|
+
{ name: "version", type: "string" },
|
|
5351
|
+
{ name: "chainId", type: "uint256" },
|
|
5352
|
+
{ name: "verifyingContract", type: "address" }
|
|
5353
|
+
];
|
|
5354
|
+
}
|
|
5355
|
+
return void 0;
|
|
5356
|
+
}
|
|
5357
|
+
function encodeData(typeName, data, types, keccak) {
|
|
5358
|
+
const fields = getTypeFields(typeName, types);
|
|
5359
|
+
if (!fields) return new Uint8Array(0);
|
|
5360
|
+
const chunks = [];
|
|
5361
|
+
for (const field of fields) {
|
|
5362
|
+
const value = data[field.name];
|
|
5363
|
+
if (value === void 0 || value === null) continue;
|
|
5364
|
+
chunks.push(encodeField(field.type, value, types, keccak));
|
|
5365
|
+
}
|
|
5366
|
+
const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
5367
|
+
const result = new Uint8Array(totalLen);
|
|
5368
|
+
let offset = 0;
|
|
5369
|
+
for (const chunk of chunks) {
|
|
5370
|
+
result.set(chunk, offset);
|
|
5371
|
+
offset += chunk.length;
|
|
5372
|
+
}
|
|
5373
|
+
return result;
|
|
5374
|
+
}
|
|
5375
|
+
function encodeField(type, value, types, keccak) {
|
|
5376
|
+
if (type === "string") {
|
|
5377
|
+
return keccak(new TextEncoder().encode(value));
|
|
5378
|
+
}
|
|
5379
|
+
if (type === "bytes") {
|
|
5380
|
+
const hex2 = value.startsWith("0x") ? value.slice(2) : value;
|
|
5381
|
+
const bytes2 = hexToBytes(hex2);
|
|
5382
|
+
return keccak(bytes2);
|
|
5383
|
+
}
|
|
5384
|
+
const baseType = type.replace(/\[\d*\]$/, "");
|
|
5385
|
+
if (types[baseType] && !type.endsWith("]")) {
|
|
5386
|
+
return hashStruct(baseType, value, types, keccak);
|
|
5387
|
+
}
|
|
5388
|
+
if (type.endsWith("]")) {
|
|
5389
|
+
const arr = value;
|
|
5390
|
+
const elementType = type.replace(/\[\d*\]$/, "");
|
|
5391
|
+
const encodedElements = arr.map((el) => encodeField(elementType, el, types, keccak));
|
|
5392
|
+
const totalLen = encodedElements.reduce((sum, e) => sum + e.length, 0);
|
|
5393
|
+
const concat = new Uint8Array(totalLen);
|
|
5394
|
+
let off = 0;
|
|
5395
|
+
for (const el of encodedElements) {
|
|
5396
|
+
concat.set(el, off);
|
|
5397
|
+
off += el.length;
|
|
5398
|
+
}
|
|
5399
|
+
return keccak(concat);
|
|
5400
|
+
}
|
|
5401
|
+
const result = new Uint8Array(32);
|
|
5402
|
+
if (type === "address") {
|
|
5403
|
+
const addr = value.startsWith("0x") ? value.slice(2) : value;
|
|
5404
|
+
const bytes2 = hexToBytes(addr.toLowerCase());
|
|
5405
|
+
result.set(bytes2, 32 - bytes2.length);
|
|
5406
|
+
return result;
|
|
5407
|
+
}
|
|
5408
|
+
if (type === "bool") {
|
|
5409
|
+
if (value === true || value === "true" || value === 1 || value === "1") {
|
|
5410
|
+
result[31] = 1;
|
|
5411
|
+
}
|
|
5412
|
+
return result;
|
|
5413
|
+
}
|
|
5414
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
5415
|
+
const n2 = BigInt(value);
|
|
5416
|
+
const hex2 = n2.toString(16).padStart(64, "0");
|
|
5417
|
+
const bytes2 = hexToBytes(hex2);
|
|
5418
|
+
result.set(bytes2, 32 - bytes2.length);
|
|
5419
|
+
return result;
|
|
5420
|
+
}
|
|
5421
|
+
if (type.startsWith("bytes")) {
|
|
5422
|
+
const hex2 = value.startsWith("0x") ? value.slice(2) : value;
|
|
5423
|
+
const bytes2 = hexToBytes(hex2);
|
|
5424
|
+
result.set(bytes2, 0);
|
|
5425
|
+
return result;
|
|
5426
|
+
}
|
|
5427
|
+
const n = BigInt(value);
|
|
5428
|
+
const hex = n.toString(16).padStart(64, "0");
|
|
5429
|
+
const bytes = hexToBytes(hex);
|
|
5430
|
+
result.set(bytes, 32 - bytes.length);
|
|
5431
|
+
return result;
|
|
5432
|
+
}
|
|
5433
|
+
function hexToBytes(hex) {
|
|
5434
|
+
const clean2 = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
5435
|
+
const padded = clean2.length % 2 === 1 ? "0" + clean2 : clean2;
|
|
5436
|
+
const bytes = new Uint8Array(padded.length / 2);
|
|
5437
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5438
|
+
bytes[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
|
|
5439
|
+
}
|
|
5440
|
+
return bytes;
|
|
5441
|
+
}
|
|
5442
|
+
function parseDERSignature(sigHex) {
|
|
5443
|
+
const raw = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex;
|
|
5444
|
+
if (raw.length === 128) {
|
|
5445
|
+
return { r: raw.slice(0, 64), s: raw.slice(64) };
|
|
5446
|
+
}
|
|
5447
|
+
let offset = 0;
|
|
5448
|
+
if (raw.slice(offset, offset + 2) !== "30") {
|
|
5449
|
+
return { r: raw.slice(0, 64).padStart(64, "0"), s: raw.slice(64).padStart(64, "0") };
|
|
5450
|
+
}
|
|
5451
|
+
offset += 2;
|
|
5452
|
+
offset += 2;
|
|
5453
|
+
if (raw.slice(offset, offset + 2) !== "02") throw new Error("Invalid DER: expected 02 for R");
|
|
5454
|
+
offset += 2;
|
|
5455
|
+
const rLen = parseInt(raw.slice(offset, offset + 2), 16);
|
|
5456
|
+
offset += 2;
|
|
5457
|
+
let rHex = raw.slice(offset, offset + rLen * 2);
|
|
5458
|
+
offset += rLen * 2;
|
|
5459
|
+
if (rHex.length > 64 && rHex.startsWith("00")) {
|
|
5460
|
+
rHex = rHex.slice(rHex.length - 64);
|
|
5461
|
+
}
|
|
5462
|
+
rHex = rHex.padStart(64, "0");
|
|
5463
|
+
if (raw.slice(offset, offset + 2) !== "02") throw new Error("Invalid DER: expected 02 for S");
|
|
5464
|
+
offset += 2;
|
|
5465
|
+
const sLen = parseInt(raw.slice(offset, offset + 2), 16);
|
|
5466
|
+
offset += 2;
|
|
5467
|
+
let sHex = raw.slice(offset, offset + sLen * 2);
|
|
5468
|
+
if (sHex.length > 64 && sHex.startsWith("00")) {
|
|
5469
|
+
sHex = sHex.slice(sHex.length - 64);
|
|
5470
|
+
}
|
|
5471
|
+
sHex = sHex.padStart(64, "0");
|
|
5472
|
+
return { r: rHex, s: sHex };
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
// src/agent/pipe.ts
|
|
5476
|
+
import * as readline from "node:readline";
|
|
5477
|
+
var PipeInterface = class {
|
|
5478
|
+
session;
|
|
5479
|
+
rl = null;
|
|
5480
|
+
stopped = false;
|
|
5481
|
+
pendingPasswordResolve = null;
|
|
5482
|
+
pendingConfirmResolve = null;
|
|
5483
|
+
constructor(session) {
|
|
5484
|
+
this.session = session;
|
|
5485
|
+
process.stdin.pause();
|
|
5486
|
+
}
|
|
5487
|
+
/**
|
|
5488
|
+
* Start the pipe interface.
|
|
5489
|
+
*/
|
|
5490
|
+
async start(vaultName, addresses) {
|
|
5491
|
+
this.rl = readline.createInterface({
|
|
5492
|
+
input: process.stdin,
|
|
5493
|
+
output: void 0,
|
|
5494
|
+
// Don't write prompts to stdout
|
|
5495
|
+
terminal: false
|
|
5496
|
+
});
|
|
5497
|
+
this.emit({ type: "ready", vault: vaultName, addresses });
|
|
5498
|
+
const sessionId = this.session.getConversationId();
|
|
5499
|
+
if (sessionId) {
|
|
5500
|
+
this.emit({ type: "session", id: sessionId });
|
|
5501
|
+
}
|
|
5502
|
+
const history = this.session.getHistoryMessages();
|
|
5503
|
+
if (history.length > 0) {
|
|
5504
|
+
this.emit({
|
|
5505
|
+
type: "history",
|
|
5506
|
+
messages: history.filter((m) => m.content_type !== "action_result").map((m) => ({ role: m.role, content: m.content, created_at: m.created_at }))
|
|
5507
|
+
});
|
|
5508
|
+
}
|
|
5509
|
+
const lines = [];
|
|
5510
|
+
let inputDone = false;
|
|
5511
|
+
let processing = false;
|
|
5512
|
+
this.rl.on("line", async (line) => {
|
|
5513
|
+
const trimmed = line.trim();
|
|
5514
|
+
if (!trimmed) return;
|
|
5515
|
+
lines.push(trimmed);
|
|
5516
|
+
if (!processing) {
|
|
5517
|
+
processing = true;
|
|
5518
|
+
while (lines.length > 0) {
|
|
5519
|
+
const nextLine = lines.shift();
|
|
5520
|
+
try {
|
|
5521
|
+
const cmd = JSON.parse(nextLine);
|
|
5522
|
+
await this.handleCommand(cmd);
|
|
5523
|
+
} catch (err) {
|
|
5524
|
+
this.emit({ type: "error", message: `Invalid input: ${err.message}` });
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
processing = false;
|
|
5528
|
+
if (inputDone && lines.length === 0) {
|
|
5529
|
+
this.stop();
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
});
|
|
5533
|
+
this.rl.on("close", () => {
|
|
5534
|
+
inputDone = true;
|
|
5535
|
+
if (!processing && lines.length === 0) {
|
|
5536
|
+
this.stop();
|
|
5537
|
+
}
|
|
5538
|
+
});
|
|
5539
|
+
await new Promise((resolve) => {
|
|
5540
|
+
const check = setInterval(() => {
|
|
5541
|
+
if (this.stopped) {
|
|
5542
|
+
clearInterval(check);
|
|
5543
|
+
resolve();
|
|
5544
|
+
}
|
|
5545
|
+
}, 100);
|
|
5546
|
+
});
|
|
5547
|
+
}
|
|
5548
|
+
stop() {
|
|
5549
|
+
if (this.stopped) return;
|
|
5550
|
+
this.stopped = true;
|
|
5551
|
+
this.rl?.close();
|
|
5552
|
+
this.session.dispose();
|
|
5553
|
+
}
|
|
5554
|
+
/**
|
|
5555
|
+
* Get UI callbacks for the session.
|
|
5556
|
+
*/
|
|
5557
|
+
getCallbacks() {
|
|
5558
|
+
return {
|
|
5559
|
+
onTextDelta: (delta) => {
|
|
5560
|
+
this.emit({ type: "text_delta", delta });
|
|
5561
|
+
},
|
|
5562
|
+
onToolCall: (id, action, params) => {
|
|
5563
|
+
this.emit({ type: "tool_call", id, action, params, status: "running" });
|
|
5564
|
+
},
|
|
5565
|
+
onToolResult: (id, action, success2, data, error2) => {
|
|
5566
|
+
this.emit({ type: "tool_result", id, action, success: success2, data, error: error2 });
|
|
5567
|
+
},
|
|
5568
|
+
onAssistantMessage: (content) => {
|
|
5569
|
+
this.emit({ type: "assistant", content });
|
|
5570
|
+
},
|
|
5571
|
+
onSuggestions: (suggestions) => {
|
|
5572
|
+
this.emit({ type: "suggestions", suggestions });
|
|
5573
|
+
},
|
|
5574
|
+
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
5575
|
+
this.emit({
|
|
5576
|
+
type: "tx_status",
|
|
5577
|
+
tx_hash: txHash,
|
|
5578
|
+
chain,
|
|
5579
|
+
status,
|
|
5580
|
+
explorer_url: explorerUrl
|
|
5581
|
+
});
|
|
5582
|
+
},
|
|
5583
|
+
onError: (message) => {
|
|
5584
|
+
this.emit({ type: "error", message });
|
|
5585
|
+
},
|
|
5586
|
+
onDone: () => {
|
|
5587
|
+
this.emit({ type: "done" });
|
|
5588
|
+
},
|
|
5589
|
+
requestPassword: async () => {
|
|
5590
|
+
return new Promise((resolve) => {
|
|
5591
|
+
this.pendingPasswordResolve = resolve;
|
|
5592
|
+
this.emit({ type: "error", message: "PASSWORD_REQUIRED" });
|
|
5593
|
+
});
|
|
5594
|
+
},
|
|
5595
|
+
requestConfirmation: async (message) => {
|
|
5596
|
+
return new Promise((resolve) => {
|
|
5597
|
+
this.pendingConfirmResolve = resolve;
|
|
5598
|
+
this.emit({ type: "error", message: `CONFIRMATION_REQUIRED: ${message}` });
|
|
5599
|
+
});
|
|
5600
|
+
}
|
|
5601
|
+
};
|
|
5602
|
+
}
|
|
5603
|
+
async handleCommand(cmd) {
|
|
5604
|
+
switch (cmd.type) {
|
|
5605
|
+
case "message": {
|
|
5606
|
+
const callbacks = this.getCallbacks();
|
|
5607
|
+
try {
|
|
5608
|
+
await this.session.sendMessage(cmd.content, callbacks);
|
|
5609
|
+
} catch (err) {
|
|
5610
|
+
this.emit({ type: "error", message: err.message });
|
|
5611
|
+
this.emit({ type: "done" });
|
|
5612
|
+
}
|
|
5613
|
+
break;
|
|
5614
|
+
}
|
|
5615
|
+
case "password": {
|
|
5616
|
+
if (this.pendingPasswordResolve) {
|
|
5617
|
+
this.pendingPasswordResolve(cmd.password);
|
|
5618
|
+
this.pendingPasswordResolve = null;
|
|
5619
|
+
}
|
|
5620
|
+
break;
|
|
5621
|
+
}
|
|
5622
|
+
case "confirm": {
|
|
5623
|
+
if (this.pendingConfirmResolve) {
|
|
5624
|
+
this.pendingConfirmResolve(cmd.confirmed);
|
|
5625
|
+
this.pendingConfirmResolve = null;
|
|
5626
|
+
}
|
|
5627
|
+
break;
|
|
5628
|
+
}
|
|
5629
|
+
default:
|
|
5630
|
+
this.emit({ type: "error", message: `Unknown command type: ${cmd.type}` });
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
emit(event) {
|
|
5634
|
+
process.stdout.write(JSON.stringify(event) + "\n");
|
|
5635
|
+
}
|
|
5636
|
+
};
|
|
5637
|
+
|
|
5638
|
+
// src/agent/session.ts
|
|
5639
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5640
|
+
import { homedir } from "node:os";
|
|
5641
|
+
import { join } from "node:path";
|
|
5642
|
+
var AgentSession = class {
|
|
5643
|
+
client;
|
|
5644
|
+
vault;
|
|
5645
|
+
executor;
|
|
5646
|
+
config;
|
|
5647
|
+
conversationId = null;
|
|
5648
|
+
publicKey;
|
|
5649
|
+
cachedContext = null;
|
|
5650
|
+
abortController = null;
|
|
5651
|
+
historyMessages = [];
|
|
5652
|
+
constructor(vault, config) {
|
|
5653
|
+
this.vault = vault;
|
|
5654
|
+
this.config = config;
|
|
5655
|
+
this.client = new AgentClient(config.backendUrl);
|
|
5656
|
+
this.client.verbose = !!config.verbose;
|
|
5657
|
+
this.executor = new AgentExecutor(vault, !!config.verbose);
|
|
5658
|
+
this.publicKey = vault.publicKeys.ecdsa;
|
|
5659
|
+
if (config.password) {
|
|
5660
|
+
this.executor.setPassword(config.password);
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
/**
|
|
5664
|
+
* Initialize the session: health check, authenticate, create conversation.
|
|
5665
|
+
*/
|
|
5666
|
+
async initialize(ui) {
|
|
5667
|
+
const healthy = await this.client.healthCheck();
|
|
5668
|
+
if (!healthy) {
|
|
5669
|
+
throw new Error(`Agent backend unreachable at ${this.config.backendUrl}`);
|
|
5670
|
+
}
|
|
5671
|
+
try {
|
|
5672
|
+
if (this.vault.isEncrypted) {
|
|
5673
|
+
const password = this.config.password || await ui.requestPassword();
|
|
5674
|
+
await this.vault.unlock?.(password);
|
|
5675
|
+
this.executor.setPassword(password);
|
|
5676
|
+
}
|
|
5677
|
+
const cached = loadCachedToken(this.publicKey);
|
|
5678
|
+
if (cached) {
|
|
5679
|
+
this.client.setAuthToken(cached);
|
|
5680
|
+
} else {
|
|
5681
|
+
const auth = await authenticateVault(this.client, this.vault, this.config.password);
|
|
5682
|
+
this.client.setAuthToken(auth.token);
|
|
5683
|
+
saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
|
|
5684
|
+
}
|
|
5685
|
+
} catch (err) {
|
|
5686
|
+
throw new Error(`Authentication failed: ${err.message}`);
|
|
5687
|
+
}
|
|
5688
|
+
if (this.config.sessionId) {
|
|
5689
|
+
this.conversationId = this.config.sessionId;
|
|
5690
|
+
try {
|
|
5691
|
+
const conv = await this.client.getConversation(this.conversationId, this.publicKey);
|
|
5692
|
+
this.historyMessages = conv.messages || [];
|
|
5693
|
+
} catch (err) {
|
|
5694
|
+
if (err.message?.includes("401") || err.message?.includes("403")) {
|
|
5695
|
+
clearCachedToken(this.publicKey);
|
|
5696
|
+
const auth = await authenticateVault(this.client, this.vault, this.config.password);
|
|
5697
|
+
this.client.setAuthToken(auth.token);
|
|
5698
|
+
saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
|
|
5699
|
+
const conv = await this.client.getConversation(this.conversationId, this.publicKey);
|
|
5700
|
+
this.historyMessages = conv.messages || [];
|
|
5701
|
+
} else {
|
|
5702
|
+
this.conversationId = null;
|
|
5703
|
+
this.historyMessages = [];
|
|
5704
|
+
const conv = await this.client.createConversation(this.publicKey);
|
|
5705
|
+
this.conversationId = conv.id;
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
} else {
|
|
5709
|
+
const conv = await this.client.createConversation(this.publicKey);
|
|
5710
|
+
this.conversationId = conv.id;
|
|
5711
|
+
}
|
|
5712
|
+
this.cachedContext = await buildMessageContext(this.vault);
|
|
5713
|
+
}
|
|
5714
|
+
getConversationId() {
|
|
5715
|
+
return this.conversationId;
|
|
5716
|
+
}
|
|
5717
|
+
getHistoryMessages() {
|
|
5718
|
+
return this.historyMessages;
|
|
5719
|
+
}
|
|
5720
|
+
getVaultAddresses() {
|
|
5721
|
+
return this.cachedContext?.addresses || {};
|
|
5722
|
+
}
|
|
5723
|
+
/**
|
|
5724
|
+
* Send a user message and process the full response cycle.
|
|
5725
|
+
*
|
|
5726
|
+
* Flow:
|
|
5727
|
+
* 1. Send message to backend via SSE stream
|
|
5728
|
+
* 2. Collect text deltas and actions
|
|
5729
|
+
* 3. Execute auto-execute actions locally
|
|
5730
|
+
* 4. Report results back to backend
|
|
5731
|
+
* 5. Repeat if backend sends more actions
|
|
5732
|
+
*/
|
|
5733
|
+
async sendMessage(content, ui) {
|
|
5734
|
+
if (!this.conversationId) {
|
|
5735
|
+
throw new Error("Session not initialized");
|
|
5736
|
+
}
|
|
5737
|
+
this.abortController = new AbortController();
|
|
5738
|
+
try {
|
|
5739
|
+
this.cachedContext = await buildMessageContext(this.vault);
|
|
5740
|
+
} catch {
|
|
5741
|
+
}
|
|
5742
|
+
try {
|
|
5743
|
+
await this.processMessageLoop(content, null, ui);
|
|
5744
|
+
} catch (err) {
|
|
5745
|
+
if (err.message?.includes("401") || err.message?.includes("403")) {
|
|
5746
|
+
clearCachedToken(this.publicKey);
|
|
5747
|
+
const auth = await authenticateVault(this.client, this.vault, this.config.password);
|
|
5748
|
+
this.client.setAuthToken(auth.token);
|
|
5749
|
+
saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
|
|
5750
|
+
await this.processMessageLoop(content, null, ui);
|
|
5751
|
+
} else {
|
|
5752
|
+
throw err;
|
|
5753
|
+
}
|
|
5754
|
+
} finally {
|
|
5755
|
+
this.abortController = null;
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
/**
|
|
5759
|
+
* Core message processing loop.
|
|
5760
|
+
* Sends content or action results, executes returned actions, repeats.
|
|
5761
|
+
*/
|
|
5762
|
+
async processMessageLoop(content, actionResults, ui) {
|
|
5763
|
+
if (!this.conversationId) return;
|
|
5764
|
+
const request = {
|
|
5765
|
+
public_key: this.publicKey,
|
|
5766
|
+
context: this.cachedContext
|
|
5767
|
+
};
|
|
5768
|
+
if (content) {
|
|
5769
|
+
request.content = content;
|
|
5770
|
+
}
|
|
5771
|
+
if (actionResults && actionResults.length > 0) {
|
|
5772
|
+
const result = actionResults[0];
|
|
5773
|
+
request.action_result = {
|
|
5774
|
+
action: result.action,
|
|
5775
|
+
action_id: result.action_id,
|
|
5776
|
+
success: result.success,
|
|
5777
|
+
data: result.data || {},
|
|
5778
|
+
error: result.error || ""
|
|
5779
|
+
};
|
|
5780
|
+
}
|
|
5781
|
+
const streamResult = await this.client.sendMessageStream(
|
|
5782
|
+
this.conversationId,
|
|
5783
|
+
request,
|
|
5784
|
+
{
|
|
5785
|
+
onTextDelta: (delta) => ui.onTextDelta(delta),
|
|
5786
|
+
onToolProgress: (tool, status, label) => {
|
|
5787
|
+
if (status === "running") {
|
|
5788
|
+
ui.onToolCall(`mcp-${tool}`, tool);
|
|
5789
|
+
} else {
|
|
5790
|
+
ui.onToolResult(`mcp-${tool}`, tool, true, { label });
|
|
5791
|
+
}
|
|
5792
|
+
},
|
|
5793
|
+
onTitle: (_title) => {
|
|
5794
|
+
},
|
|
5795
|
+
onActions: (_actions) => {
|
|
5796
|
+
},
|
|
5797
|
+
onSuggestions: (suggestions) => {
|
|
5798
|
+
ui.onSuggestions(suggestions);
|
|
5799
|
+
},
|
|
5800
|
+
onTxReady: (tx) => {
|
|
5801
|
+
this.executor.storeServerTransaction(tx);
|
|
5802
|
+
if (this.config.password) {
|
|
5803
|
+
this.executor.setPassword(this.config.password);
|
|
5804
|
+
}
|
|
5805
|
+
},
|
|
5806
|
+
onMessage: (_msg) => {
|
|
5807
|
+
},
|
|
5808
|
+
onError: (error2) => {
|
|
5809
|
+
ui.onError(error2);
|
|
5810
|
+
}
|
|
5811
|
+
},
|
|
5812
|
+
this.abortController?.signal
|
|
5813
|
+
);
|
|
5814
|
+
const responseText = streamResult.fullText || streamResult.message?.content || "";
|
|
5815
|
+
const inlineActions = parseInlineToolCalls(responseText);
|
|
5816
|
+
if (inlineActions.length > 0) {
|
|
5817
|
+
const cleanText = responseText.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").trim();
|
|
5818
|
+
if (cleanText) {
|
|
5819
|
+
ui.onAssistantMessage(cleanText);
|
|
5820
|
+
}
|
|
5821
|
+
streamResult.actions.push(...inlineActions);
|
|
5822
|
+
} else if (responseText) {
|
|
5823
|
+
ui.onAssistantMessage(responseText);
|
|
5824
|
+
}
|
|
5825
|
+
const actions = streamResult.actions.filter((a) => a.type !== "sign_tx");
|
|
5826
|
+
if (actions.length > 0) {
|
|
5827
|
+
const results = await this.executeActions(actions, ui);
|
|
5828
|
+
const hasBuildSuccess = results.some(
|
|
5829
|
+
(r) => r.success && r.action.startsWith("build_")
|
|
5830
|
+
);
|
|
5831
|
+
if (hasBuildSuccess && this.executor.hasPendingTransaction()) {
|
|
5832
|
+
if (this.config.verbose) process.stderr.write(`[session] build_* action produced pending tx, auto-signing client-side
|
|
5833
|
+
`);
|
|
5834
|
+
const signAction = {
|
|
5835
|
+
id: `tx_sign_${Date.now()}`,
|
|
5836
|
+
type: "sign_tx",
|
|
5837
|
+
title: "Sign transaction",
|
|
5838
|
+
params: {},
|
|
5839
|
+
auto_execute: true
|
|
5840
|
+
};
|
|
5841
|
+
const signResults = await this.executeActions([signAction], ui);
|
|
5842
|
+
const signResult = signResults[0];
|
|
5843
|
+
if (signResult) {
|
|
5844
|
+
await this.processMessageLoop(null, [signResult], ui);
|
|
5845
|
+
return;
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
if (results.length > 0) {
|
|
5849
|
+
for (const result of results) {
|
|
5850
|
+
await this.processMessageLoop(null, [result], ui);
|
|
5851
|
+
}
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
if (streamResult.transactions.length > 0 && this.executor.hasPendingTransaction()) {
|
|
5856
|
+
if (this.config.verbose) process.stderr.write(`[session] ${streamResult.transactions.length} tx_ready events, signing client-side
|
|
5857
|
+
`);
|
|
5858
|
+
const signAction = {
|
|
5859
|
+
id: `tx_sign_${Date.now()}`,
|
|
5860
|
+
type: "sign_tx",
|
|
5861
|
+
title: "Sign transaction",
|
|
5862
|
+
params: {},
|
|
5863
|
+
auto_execute: true
|
|
5864
|
+
};
|
|
5865
|
+
const results = await this.executeActions([signAction], ui);
|
|
5866
|
+
if (results.length > 0) {
|
|
5867
|
+
for (const result of results) {
|
|
5868
|
+
await this.processMessageLoop(null, [result], ui);
|
|
5869
|
+
}
|
|
5870
|
+
return;
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
ui.onDone();
|
|
5874
|
+
}
|
|
5875
|
+
/**
|
|
5876
|
+
* Execute a list of actions, handling password requirements.
|
|
5877
|
+
*/
|
|
5878
|
+
async executeActions(actions, ui) {
|
|
5879
|
+
const results = [];
|
|
5880
|
+
for (const action of actions) {
|
|
5881
|
+
if (!this.executor.shouldAutoExecute(action)) {
|
|
5882
|
+
continue;
|
|
5883
|
+
}
|
|
5884
|
+
if (PASSWORD_REQUIRED_ACTIONS.has(action.type)) {
|
|
5885
|
+
if (!this.config.password) {
|
|
5886
|
+
try {
|
|
5887
|
+
const password = await ui.requestPassword();
|
|
5888
|
+
this.executor.setPassword(password);
|
|
5889
|
+
this.config.password = password;
|
|
5890
|
+
} catch {
|
|
5891
|
+
results.push({
|
|
5892
|
+
action: action.type,
|
|
5893
|
+
action_id: action.id,
|
|
5894
|
+
success: false,
|
|
5895
|
+
error: "Password not provided"
|
|
5896
|
+
});
|
|
5897
|
+
continue;
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
ui.onToolCall(action.id, action.type, action.params);
|
|
5902
|
+
const result = await this.executor.executeAction(action);
|
|
5903
|
+
results.push(result);
|
|
5904
|
+
ui.onToolResult(action.id, action.type, result.success, result.data, result.error);
|
|
5905
|
+
if (action.type === "sign_tx" && result.success && result.data) {
|
|
5906
|
+
const txHash = result.data.tx_hash;
|
|
5907
|
+
const chain = result.data.chain;
|
|
5908
|
+
const explorerUrl = result.data.explorer_url;
|
|
5909
|
+
if (txHash) {
|
|
5910
|
+
ui.onTxStatus(txHash, chain, "pending", explorerUrl);
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
return results;
|
|
5915
|
+
}
|
|
5916
|
+
/**
|
|
5917
|
+
* Cancel the current operation.
|
|
5918
|
+
*/
|
|
5919
|
+
cancel() {
|
|
5920
|
+
this.abortController?.abort();
|
|
5921
|
+
}
|
|
5922
|
+
/**
|
|
5923
|
+
* Clean up session resources.
|
|
5924
|
+
*/
|
|
5925
|
+
dispose() {
|
|
5926
|
+
this.cancel();
|
|
5927
|
+
this.cachedContext = null;
|
|
5928
|
+
this.conversationId = null;
|
|
5929
|
+
this.historyMessages = [];
|
|
5930
|
+
}
|
|
5931
|
+
};
|
|
5932
|
+
function parseInlineToolCalls(text) {
|
|
5933
|
+
const actions = [];
|
|
5934
|
+
const invokeRegex = /<invoke\s+name="([^"]+)">([\s\S]*?)<\/invoke>/g;
|
|
5935
|
+
let match;
|
|
5936
|
+
while ((match = invokeRegex.exec(text)) !== null) {
|
|
5937
|
+
const actionType = match[1];
|
|
5938
|
+
const body = match[2];
|
|
5939
|
+
const params = {};
|
|
5940
|
+
const paramRegex = /<parameter\s+name="([^"]+)">([\s\S]*?)<\/parameter>/g;
|
|
5941
|
+
let paramMatch;
|
|
5942
|
+
while ((paramMatch = paramRegex.exec(body)) !== null) {
|
|
5943
|
+
const key = paramMatch[1];
|
|
5944
|
+
const value = paramMatch[2];
|
|
5945
|
+
try {
|
|
5946
|
+
params[key] = JSON.parse(value);
|
|
5947
|
+
} catch {
|
|
5948
|
+
params[key] = value;
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
actions.push({
|
|
5952
|
+
id: `inline_${actionType}_${Date.now()}`,
|
|
5953
|
+
type: actionType,
|
|
5954
|
+
title: actionType,
|
|
5955
|
+
params,
|
|
5956
|
+
auto_execute: true
|
|
5957
|
+
});
|
|
5958
|
+
}
|
|
5959
|
+
return actions;
|
|
5960
|
+
}
|
|
5961
|
+
function getTokenCachePath() {
|
|
5962
|
+
const dir = process.env.VULTISIG_CONFIG_DIR ?? join(homedir(), ".vultisig");
|
|
5963
|
+
return join(dir, "agent-tokens.json");
|
|
5964
|
+
}
|
|
5965
|
+
function readTokenStore() {
|
|
5966
|
+
try {
|
|
5967
|
+
const path3 = getTokenCachePath();
|
|
5968
|
+
if (!existsSync(path3)) return {};
|
|
5969
|
+
return JSON.parse(readFileSync(path3, "utf-8"));
|
|
5970
|
+
} catch {
|
|
5971
|
+
return {};
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
function writeTokenStore(store) {
|
|
5975
|
+
const path3 = getTokenCachePath();
|
|
5976
|
+
const dir = join(path3, "..");
|
|
5977
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
5978
|
+
writeFileSync(path3, JSON.stringify(store, null, 2), { mode: 384 });
|
|
5979
|
+
}
|
|
5980
|
+
function loadCachedToken(publicKey) {
|
|
5981
|
+
const store = readTokenStore();
|
|
5982
|
+
const entry = store[publicKey];
|
|
5983
|
+
if (!entry) return null;
|
|
5984
|
+
const now = Date.now();
|
|
5985
|
+
const expiresMs = entry.expiresAt * (entry.expiresAt < 1e12 ? 1e3 : 1);
|
|
5986
|
+
if (now >= expiresMs - 6e4) {
|
|
5987
|
+
delete store[publicKey];
|
|
5988
|
+
try {
|
|
5989
|
+
writeTokenStore(store);
|
|
5990
|
+
} catch {
|
|
5991
|
+
}
|
|
5992
|
+
return null;
|
|
5993
|
+
}
|
|
5994
|
+
return entry.token;
|
|
5995
|
+
}
|
|
5996
|
+
function saveCachedToken(publicKey, token, expiresAt) {
|
|
5997
|
+
const store = readTokenStore();
|
|
5998
|
+
store[publicKey] = { token, expiresAt };
|
|
5999
|
+
try {
|
|
6000
|
+
writeTokenStore(store);
|
|
6001
|
+
} catch {
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
function clearCachedToken(publicKey) {
|
|
6005
|
+
const store = readTokenStore();
|
|
6006
|
+
delete store[publicKey];
|
|
6007
|
+
try {
|
|
6008
|
+
writeTokenStore(store);
|
|
6009
|
+
} catch {
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
|
|
6013
|
+
// src/agent/tui.ts
|
|
6014
|
+
import * as readline2 from "node:readline";
|
|
6015
|
+
import chalk8 from "chalk";
|
|
6016
|
+
var ChatTUI = class {
|
|
6017
|
+
rl;
|
|
6018
|
+
session;
|
|
6019
|
+
isStreaming = false;
|
|
6020
|
+
currentStreamText = "";
|
|
6021
|
+
vaultName;
|
|
6022
|
+
stopped = false;
|
|
6023
|
+
verbose;
|
|
6024
|
+
constructor(session, vaultName, verbose = false) {
|
|
6025
|
+
this.session = session;
|
|
6026
|
+
this.vaultName = vaultName;
|
|
6027
|
+
this.verbose = verbose;
|
|
6028
|
+
this.rl = readline2.createInterface({
|
|
6029
|
+
input: process.stdin,
|
|
6030
|
+
output: process.stdout,
|
|
6031
|
+
prompt: "",
|
|
6032
|
+
terminal: true
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
/**
|
|
6036
|
+
* Start the interactive chat loop.
|
|
6037
|
+
*/
|
|
6038
|
+
async start() {
|
|
6039
|
+
this.printHeader();
|
|
6040
|
+
const sessionId = this.session.getConversationId();
|
|
6041
|
+
if (sessionId) {
|
|
6042
|
+
console.log(chalk8.gray(` Session: ${sessionId}`));
|
|
6043
|
+
console.log("");
|
|
6044
|
+
}
|
|
6045
|
+
const history = this.session.getHistoryMessages();
|
|
6046
|
+
if (history.length > 0) {
|
|
6047
|
+
this.printHistory(history);
|
|
6048
|
+
}
|
|
6049
|
+
this.printHelp();
|
|
6050
|
+
this.showPrompt();
|
|
6051
|
+
this.rl.on("line", async (line) => {
|
|
6052
|
+
const input = line.trim();
|
|
6053
|
+
readline2.moveCursor(process.stdout, 0, -1);
|
|
6054
|
+
readline2.clearLine(process.stdout, 0);
|
|
6055
|
+
if (!input) {
|
|
6056
|
+
this.showPrompt();
|
|
6057
|
+
return;
|
|
6058
|
+
}
|
|
6059
|
+
if (input === "/quit" || input === "/exit" || input === "/q") {
|
|
6060
|
+
this.stop();
|
|
6061
|
+
return;
|
|
6062
|
+
}
|
|
6063
|
+
if (input === "/help" || input === "/h") {
|
|
6064
|
+
this.printHelp();
|
|
6065
|
+
this.showPrompt();
|
|
6066
|
+
return;
|
|
6067
|
+
}
|
|
6068
|
+
if (input === "/clear") {
|
|
6069
|
+
console.clear();
|
|
6070
|
+
this.printHeader();
|
|
6071
|
+
this.showPrompt();
|
|
6072
|
+
return;
|
|
6073
|
+
}
|
|
6074
|
+
this.printUserMessage(input);
|
|
6075
|
+
await this.handleMessage(input);
|
|
6076
|
+
this.showPrompt();
|
|
6077
|
+
});
|
|
6078
|
+
this.rl.on("close", () => {
|
|
6079
|
+
this.stop();
|
|
6080
|
+
});
|
|
6081
|
+
process.on("SIGINT", () => {
|
|
6082
|
+
if (this.isStreaming) {
|
|
6083
|
+
this.session.cancel();
|
|
6084
|
+
this.isStreaming = false;
|
|
6085
|
+
console.log(chalk8.yellow("\n [cancelled]"));
|
|
6086
|
+
this.showPrompt();
|
|
6087
|
+
} else {
|
|
6088
|
+
this.stop();
|
|
6089
|
+
}
|
|
6090
|
+
});
|
|
6091
|
+
await new Promise((resolve) => {
|
|
6092
|
+
const check = setInterval(() => {
|
|
6093
|
+
if (this.stopped) {
|
|
6094
|
+
clearInterval(check);
|
|
6095
|
+
resolve();
|
|
6096
|
+
}
|
|
6097
|
+
}, 100);
|
|
6098
|
+
});
|
|
6099
|
+
}
|
|
6100
|
+
stop() {
|
|
6101
|
+
if (this.stopped) return;
|
|
6102
|
+
this.stopped = true;
|
|
6103
|
+
console.log(chalk8.gray("\n Goodbye!\n"));
|
|
6104
|
+
this.rl.close();
|
|
6105
|
+
this.session.dispose();
|
|
6106
|
+
}
|
|
6107
|
+
/**
|
|
6108
|
+
* Get UI callbacks for the session.
|
|
6109
|
+
*/
|
|
6110
|
+
getCallbacks() {
|
|
6111
|
+
return {
|
|
6112
|
+
onTextDelta: (delta) => {
|
|
6113
|
+
if (!this.isStreaming) {
|
|
6114
|
+
this.isStreaming = true;
|
|
6115
|
+
this.currentStreamText = "";
|
|
6116
|
+
const ts = this.timestamp();
|
|
6117
|
+
process.stdout.write(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: `);
|
|
6118
|
+
}
|
|
6119
|
+
this.currentStreamText += delta;
|
|
6120
|
+
},
|
|
6121
|
+
onToolCall: (_id, action, params) => {
|
|
6122
|
+
if (this.isStreaming) {
|
|
6123
|
+
process.stdout.write("\n");
|
|
6124
|
+
this.isStreaming = false;
|
|
6125
|
+
}
|
|
6126
|
+
if (this.verbose) {
|
|
6127
|
+
const paramStr = params ? chalk8.gray(` ${JSON.stringify(params).slice(0, 80)}`) : "";
|
|
6128
|
+
console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)}${paramStr} ${chalk8.gray("...")}`);
|
|
6129
|
+
} else {
|
|
6130
|
+
console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)} ${chalk8.gray("...")}`);
|
|
6131
|
+
}
|
|
6132
|
+
},
|
|
6133
|
+
onToolResult: (_id, action, success2, data, error2) => {
|
|
6134
|
+
if (success2) {
|
|
6135
|
+
if (this.verbose) {
|
|
6136
|
+
const summary = data ? summarizeData(data) : "";
|
|
6137
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}${summary ? chalk8.gray(` \u2192 ${summary}`) : ""}`);
|
|
6138
|
+
} else {
|
|
6139
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}`);
|
|
6140
|
+
}
|
|
6141
|
+
} else {
|
|
6142
|
+
console.log(` ${chalk8.red("\u2717")} ${chalk8.red(action)}: ${chalk8.red(error2 || "failed")}`);
|
|
6143
|
+
}
|
|
6144
|
+
},
|
|
6145
|
+
onAssistantMessage: (content) => {
|
|
6146
|
+
if (this.isStreaming) {
|
|
6147
|
+
process.stdout.write(renderMarkdown(this.currentStreamText) + "\n");
|
|
6148
|
+
this.isStreaming = false;
|
|
6149
|
+
} else if (content && content !== this.currentStreamText) {
|
|
6150
|
+
const ts = this.timestamp();
|
|
6151
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: ${renderMarkdown(content)}`);
|
|
6152
|
+
}
|
|
6153
|
+
this.currentStreamText = "";
|
|
6154
|
+
},
|
|
6155
|
+
onSuggestions: (suggestions) => {
|
|
6156
|
+
if (suggestions.length > 0) {
|
|
6157
|
+
console.log(chalk8.gray(" Suggestions:"));
|
|
6158
|
+
for (const s of suggestions) {
|
|
6159
|
+
console.log(chalk8.gray(` \u2022 ${s.title}`));
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
},
|
|
6163
|
+
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
6164
|
+
const statusIcon = status === "confirmed" ? chalk8.green("\u2713") : status === "failed" ? chalk8.red("\u2717") : chalk8.yellow("\u23F3");
|
|
6165
|
+
console.log(` ${statusIcon} ${chalk8.bold("TX")} [${chain}]: ${txHash.slice(0, 12)}...${txHash.slice(-8)}`);
|
|
6166
|
+
if (explorerUrl) {
|
|
6167
|
+
console.log(` ${chalk8.blue.underline(explorerUrl)}`);
|
|
6168
|
+
}
|
|
6169
|
+
},
|
|
6170
|
+
onError: (message) => {
|
|
6171
|
+
if (this.isStreaming) {
|
|
6172
|
+
process.stdout.write("\n");
|
|
6173
|
+
this.isStreaming = false;
|
|
6174
|
+
}
|
|
6175
|
+
console.log(` ${chalk8.red("Error")}: ${message}`);
|
|
6176
|
+
},
|
|
6177
|
+
onDone: () => {
|
|
6178
|
+
if (this.isStreaming) {
|
|
6179
|
+
process.stdout.write(renderMarkdown(this.currentStreamText) + "\n");
|
|
6180
|
+
this.isStreaming = false;
|
|
6181
|
+
this.currentStreamText = "";
|
|
6182
|
+
}
|
|
6183
|
+
},
|
|
6184
|
+
requestPassword: async () => {
|
|
6185
|
+
return new Promise((resolve, reject) => {
|
|
6186
|
+
const rl2 = readline2.createInterface({
|
|
6187
|
+
input: process.stdin,
|
|
6188
|
+
output: process.stdout,
|
|
6189
|
+
terminal: true
|
|
6190
|
+
});
|
|
6191
|
+
if (process.stdin.isTTY) {
|
|
6192
|
+
process.stdout.write(chalk8.yellow(" \u{1F510} Enter vault password: "));
|
|
6193
|
+
const wasRaw = process.stdin.isRaw;
|
|
6194
|
+
process.stdin.setRawMode(true);
|
|
6195
|
+
let password = "";
|
|
6196
|
+
const onData = (key) => {
|
|
6197
|
+
const ch = key.toString();
|
|
6198
|
+
if (ch === "\r" || ch === "\n") {
|
|
6199
|
+
process.stdin.setRawMode(wasRaw || false);
|
|
6200
|
+
process.stdin.removeListener("data", onData);
|
|
6201
|
+
process.stdout.write("\n");
|
|
6202
|
+
rl2.close();
|
|
6203
|
+
resolve(password);
|
|
6204
|
+
} else if (ch === "") {
|
|
6205
|
+
process.stdin.setRawMode(wasRaw || false);
|
|
6206
|
+
process.stdin.removeListener("data", onData);
|
|
6207
|
+
rl2.close();
|
|
6208
|
+
reject(new Error("Password input cancelled"));
|
|
6209
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
6210
|
+
if (password.length > 0) {
|
|
6211
|
+
password = password.slice(0, -1);
|
|
6212
|
+
process.stdout.write("\b \b");
|
|
6213
|
+
}
|
|
6214
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
6215
|
+
password += ch;
|
|
6216
|
+
process.stdout.write("*");
|
|
6217
|
+
}
|
|
6218
|
+
};
|
|
6219
|
+
process.stdin.on("data", onData);
|
|
6220
|
+
} else {
|
|
6221
|
+
rl2.question("Password: ", (answer) => {
|
|
6222
|
+
rl2.close();
|
|
6223
|
+
resolve(answer.trim());
|
|
6224
|
+
});
|
|
6225
|
+
}
|
|
6226
|
+
});
|
|
6227
|
+
},
|
|
6228
|
+
requestConfirmation: async (message) => {
|
|
6229
|
+
return new Promise((resolve) => {
|
|
6230
|
+
this.rl.question(chalk8.yellow(` ${message} (y/N): `), (answer) => {
|
|
6231
|
+
resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
|
|
6232
|
+
});
|
|
6233
|
+
});
|
|
6234
|
+
}
|
|
6235
|
+
};
|
|
6236
|
+
}
|
|
6237
|
+
async handleMessage(content) {
|
|
6238
|
+
const callbacks = this.getCallbacks();
|
|
6239
|
+
this.isStreaming = false;
|
|
6240
|
+
try {
|
|
6241
|
+
await this.session.sendMessage(content, callbacks);
|
|
6242
|
+
} catch (err) {
|
|
6243
|
+
if (err.name === "AbortError") {
|
|
6244
|
+
console.log(chalk8.yellow(" [cancelled]"));
|
|
6245
|
+
} else {
|
|
6246
|
+
console.log(chalk8.red(` Error: ${err.message}`));
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
printHeader() {
|
|
6251
|
+
console.log("");
|
|
6252
|
+
console.log(chalk8.bold.cyan(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
|
|
6253
|
+
console.log(chalk8.bold.cyan(` \u2551`) + chalk8.bold(` Vultisig Agent - ${this.vaultName}`.padEnd(38).slice(0, 38)) + chalk8.bold.cyan(`\u2551`));
|
|
6254
|
+
console.log(chalk8.bold.cyan(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
|
|
6255
|
+
console.log("");
|
|
6256
|
+
}
|
|
6257
|
+
printHistory(messages) {
|
|
6258
|
+
console.log(chalk8.gray(" \u2500\u2500 Session History \u2500\u2500"));
|
|
6259
|
+
console.log("");
|
|
6260
|
+
for (const msg of messages) {
|
|
6261
|
+
if (msg.content_type === "action_result") continue;
|
|
6262
|
+
const ts = this.formatHistoryTimestamp(msg.created_at);
|
|
6263
|
+
if (msg.role === "user") {
|
|
6264
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.green.bold("You")}: ${msg.content}`);
|
|
6265
|
+
} else if (msg.role === "assistant") {
|
|
6266
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: ${renderMarkdown(msg.content)}`);
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
console.log("");
|
|
6270
|
+
console.log(chalk8.gray(" \u2500\u2500 End of History \u2500\u2500"));
|
|
6271
|
+
console.log("");
|
|
6272
|
+
}
|
|
6273
|
+
formatHistoryTimestamp(iso) {
|
|
6274
|
+
try {
|
|
6275
|
+
const d = new Date(iso);
|
|
6276
|
+
return `[${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}]`;
|
|
6277
|
+
} catch {
|
|
6278
|
+
return "[--:--:--]";
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
printHelp() {
|
|
6282
|
+
console.log(chalk8.gray(" Commands: /help, /clear, /quit"));
|
|
6283
|
+
console.log(chalk8.gray(" Press Ctrl+C to cancel a response, or to exit"));
|
|
6284
|
+
console.log("");
|
|
6285
|
+
}
|
|
6286
|
+
printUserMessage(content) {
|
|
6287
|
+
const ts = this.timestamp();
|
|
6288
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.green.bold("You")}: ${content}`);
|
|
6289
|
+
}
|
|
6290
|
+
showPrompt() {
|
|
6291
|
+
if (this.stopped) return;
|
|
6292
|
+
const prompt = chalk8.gray(`${this.timestamp()} `) + chalk8.green.bold("You") + ": ";
|
|
6293
|
+
this.rl.setPrompt(prompt);
|
|
6294
|
+
this.rl.prompt();
|
|
6295
|
+
}
|
|
6296
|
+
timestamp() {
|
|
6297
|
+
const now = /* @__PURE__ */ new Date();
|
|
6298
|
+
return `[${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}]`;
|
|
6299
|
+
}
|
|
6300
|
+
};
|
|
6301
|
+
function renderMarkdown(text) {
|
|
6302
|
+
return text.replace(/\*\*(.+?)\*\*/g, (_m, p1) => chalk8.bold(p1)).replace(/__(.+?)__/g, (_m, p1) => chalk8.bold(p1)).replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, (_m, p1) => chalk8.italic(p1)).replace(/(?<!\w)_([^_]+?)_(?!\w)/g, (_m, p1) => chalk8.italic(p1)).replace(/`([^`]+?)`/g, (_m, p1) => chalk8.cyan(p1)).replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, p1, p2) => `${p1} ${chalk8.blue.underline(`(${p2})`)}`);
|
|
6303
|
+
}
|
|
6304
|
+
function summarizeData(data) {
|
|
6305
|
+
if (data.balances && Array.isArray(data.balances)) {
|
|
6306
|
+
const balances = data.balances;
|
|
6307
|
+
if (balances.length === 1) {
|
|
6308
|
+
return `${balances[0].amount} ${balances[0].symbol}`;
|
|
6309
|
+
}
|
|
6310
|
+
return `${balances.length} balances`;
|
|
6311
|
+
}
|
|
6312
|
+
if (data.tx_hash) {
|
|
6313
|
+
return `tx: ${data.tx_hash.slice(0, 12)}...`;
|
|
6314
|
+
}
|
|
6315
|
+
if (data.added) return "added";
|
|
6316
|
+
if (data.removed) return "removed";
|
|
6317
|
+
if (data.message) return data.message;
|
|
6318
|
+
return "";
|
|
6319
|
+
}
|
|
6320
|
+
|
|
6321
|
+
// src/commands/agent.ts
|
|
6322
|
+
async function executeAgent(ctx2, options) {
|
|
6323
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6324
|
+
const config = {
|
|
6325
|
+
backendUrl: options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998",
|
|
6326
|
+
vaultName: vault.name,
|
|
6327
|
+
password: options.password,
|
|
6328
|
+
viaAgent: options.viaAgent,
|
|
6329
|
+
sessionId: options.sessionId,
|
|
6330
|
+
verbose: options.verbose
|
|
6331
|
+
};
|
|
6332
|
+
const session = new AgentSession(vault, config);
|
|
6333
|
+
if (options.viaAgent) {
|
|
6334
|
+
const pipe = new PipeInterface(session);
|
|
6335
|
+
const callbacks = pipe.getCallbacks();
|
|
6336
|
+
try {
|
|
6337
|
+
await session.initialize(callbacks);
|
|
6338
|
+
const addresses = session.getVaultAddresses();
|
|
6339
|
+
await pipe.start(vault.name, addresses);
|
|
6340
|
+
} catch (err) {
|
|
6341
|
+
process.stdout.write(JSON.stringify({ type: "error", message: err.message }) + "\n");
|
|
6342
|
+
process.exit(1);
|
|
6343
|
+
}
|
|
6344
|
+
} else {
|
|
6345
|
+
const tui = new ChatTUI(session, vault.name, config.verbose);
|
|
6346
|
+
const callbacks = tui.getCallbacks();
|
|
6347
|
+
try {
|
|
6348
|
+
await session.initialize(callbacks);
|
|
6349
|
+
await tui.start();
|
|
6350
|
+
} catch (err) {
|
|
6351
|
+
console.error(`Agent error: ${err.message}`);
|
|
6352
|
+
process.exit(1);
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
async function executeAgentSessionsList(ctx2, options) {
|
|
6357
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6358
|
+
const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998";
|
|
6359
|
+
const client = await createAuthenticatedClient(backendUrl, vault, options.password);
|
|
6360
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
6361
|
+
const PAGE_SIZE = 100;
|
|
6362
|
+
const allConversations = [];
|
|
6363
|
+
let totalCount = 0;
|
|
6364
|
+
let skip = 0;
|
|
6365
|
+
while (true) {
|
|
6366
|
+
const page = await client.listConversations(publicKey, skip, PAGE_SIZE);
|
|
6367
|
+
totalCount = page.total_count;
|
|
6368
|
+
allConversations.push(...page.conversations);
|
|
6369
|
+
if (allConversations.length >= totalCount || page.conversations.length < PAGE_SIZE) break;
|
|
6370
|
+
skip += PAGE_SIZE;
|
|
6371
|
+
}
|
|
6372
|
+
if (isJsonOutput()) {
|
|
6373
|
+
outputJson({
|
|
6374
|
+
sessions: allConversations.map((c) => ({
|
|
6375
|
+
id: c.id,
|
|
6376
|
+
title: c.title,
|
|
6377
|
+
created_at: c.created_at,
|
|
6378
|
+
updated_at: c.updated_at
|
|
6379
|
+
})),
|
|
6380
|
+
total_count: totalCount
|
|
6381
|
+
});
|
|
6382
|
+
return;
|
|
6383
|
+
}
|
|
6384
|
+
if (allConversations.length === 0) {
|
|
6385
|
+
printResult("No sessions found.");
|
|
6386
|
+
return;
|
|
6387
|
+
}
|
|
6388
|
+
const table = new Table({
|
|
6389
|
+
head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
|
|
6390
|
+
});
|
|
6391
|
+
for (const conv of allConversations) {
|
|
6392
|
+
table.push([
|
|
6393
|
+
conv.id,
|
|
6394
|
+
conv.title || chalk9.gray("(untitled)"),
|
|
6395
|
+
formatDate(conv.created_at),
|
|
6396
|
+
formatDate(conv.updated_at)
|
|
6397
|
+
]);
|
|
6398
|
+
}
|
|
6399
|
+
printResult(table.toString());
|
|
6400
|
+
printResult(chalk9.gray(`
|
|
6401
|
+
${totalCount} session(s) total`));
|
|
6402
|
+
}
|
|
6403
|
+
async function executeAgentSessionsDelete(ctx2, sessionId, options) {
|
|
6404
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6405
|
+
const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998";
|
|
6406
|
+
const client = await createAuthenticatedClient(backendUrl, vault, options.password);
|
|
6407
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
6408
|
+
await client.deleteConversation(sessionId, publicKey);
|
|
6409
|
+
if (isJsonOutput()) {
|
|
6410
|
+
outputJson({ deleted: sessionId });
|
|
6411
|
+
return;
|
|
6412
|
+
}
|
|
6413
|
+
printResult(chalk9.green(`Session ${sessionId} deleted.`));
|
|
6414
|
+
}
|
|
6415
|
+
async function createAuthenticatedClient(backendUrl, vault, password) {
|
|
6416
|
+
const client = new AgentClient(backendUrl);
|
|
6417
|
+
const auth = await authenticateVault(client, vault, password);
|
|
6418
|
+
client.setAuthToken(auth.token);
|
|
6419
|
+
return client;
|
|
6420
|
+
}
|
|
6421
|
+
function formatDate(iso) {
|
|
6422
|
+
try {
|
|
6423
|
+
const d = new Date(iso);
|
|
6424
|
+
return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
6425
|
+
} catch {
|
|
6426
|
+
return iso;
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6429
|
+
|
|
6430
|
+
// src/interactive/completer.ts
|
|
6431
|
+
import { Chain as Chain10 } from "@vultisig/sdk";
|
|
6432
|
+
import fs2 from "fs";
|
|
6433
|
+
import path2 from "path";
|
|
6434
|
+
var COMMANDS = [
|
|
6435
|
+
// Vault management
|
|
6436
|
+
"vaults",
|
|
6437
|
+
"vault",
|
|
6438
|
+
"import",
|
|
6439
|
+
"delete",
|
|
6440
|
+
"create-from-seedphrase",
|
|
6441
|
+
"create",
|
|
6442
|
+
"join",
|
|
6443
|
+
"info",
|
|
6444
|
+
"export",
|
|
6445
|
+
// Wallet operations
|
|
6446
|
+
"balance",
|
|
6447
|
+
"bal",
|
|
6448
|
+
"send",
|
|
6449
|
+
"tx-status",
|
|
6450
|
+
"portfolio",
|
|
6451
|
+
"addresses",
|
|
6452
|
+
"chains",
|
|
6453
|
+
"tokens",
|
|
6454
|
+
// Swap operations
|
|
6455
|
+
"swap-chains",
|
|
6456
|
+
"swap-quote",
|
|
6457
|
+
"swap",
|
|
6458
|
+
// Session commands (shell-only)
|
|
6459
|
+
"lock",
|
|
6460
|
+
"unlock",
|
|
6461
|
+
"status",
|
|
6462
|
+
// Settings
|
|
6463
|
+
"currency",
|
|
6464
|
+
"server",
|
|
6465
|
+
"address-book",
|
|
6466
|
+
// Help
|
|
6467
|
+
"help",
|
|
6468
|
+
"?",
|
|
6469
|
+
// REPL commands
|
|
6470
|
+
".help",
|
|
6471
|
+
".clear",
|
|
6472
|
+
".exit"
|
|
6473
|
+
];
|
|
6474
|
+
function createCompleter(ctx2) {
|
|
6475
|
+
return function completer(line) {
|
|
6476
|
+
try {
|
|
6477
|
+
const parts = line.split(/\s+/);
|
|
6478
|
+
const command = parts[0]?.toLowerCase();
|
|
6479
|
+
if ((command === "import" || command === "export") && parts.length > 1) {
|
|
6480
|
+
const partial = parts.slice(1).join(" ");
|
|
6481
|
+
return completeFilePath(partial, command === "import");
|
|
6482
|
+
}
|
|
6483
|
+
if (command === "vault" && parts.length > 1) {
|
|
6484
|
+
const partial = parts.slice(1).join(" ");
|
|
6485
|
+
return completeVaultName(ctx2, partial);
|
|
6486
|
+
}
|
|
6487
|
+
if (command === "chains" && parts.length >= 2) {
|
|
6488
|
+
const lastPart = parts[parts.length - 1] || "";
|
|
6489
|
+
const lastPartLower = lastPart.toLowerCase();
|
|
6490
|
+
if (lastPartLower.startsWith("-")) {
|
|
6491
|
+
const flags = ["--add", "--add-all", "--remove"];
|
|
6492
|
+
const matches = flags.filter((f) => f.startsWith(lastPartLower));
|
|
6493
|
+
return [matches.length ? matches : flags, lastPart];
|
|
6494
|
+
}
|
|
6495
|
+
const flag = parts[parts.length - 2]?.toLowerCase();
|
|
6496
|
+
if (flag === "--add" || flag === "--remove") {
|
|
6497
|
+
return completeChainName(lastPart);
|
|
6498
|
+
}
|
|
6499
|
+
}
|
|
6500
|
+
if (["balance", "bal", "tokens", "send", "swap", "swap-quote", "tx-status"].includes(command) && parts.length === 2) {
|
|
6501
|
+
const partial = parts[1] || "";
|
|
6502
|
+
return completeChainName(partial);
|
|
6503
|
+
}
|
|
6504
|
+
if ((command === "create" || command === "create-from-seedphrase") && parts.length === 2) {
|
|
6505
|
+
const types = ["fast", "secure"];
|
|
6506
|
+
const partial = parts[1] || "";
|
|
6507
|
+
const partialLower = partial.toLowerCase();
|
|
6508
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
6509
|
+
return [matches.length ? matches : types, partial];
|
|
6510
|
+
}
|
|
6511
|
+
if (command === "join" && parts.length === 2) {
|
|
6512
|
+
const types = ["secure"];
|
|
6513
|
+
const partial = parts[1] || "";
|
|
6514
|
+
const partialLower = partial.toLowerCase();
|
|
6515
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
6516
|
+
return [matches.length ? matches : types, partial];
|
|
6517
|
+
}
|
|
6518
|
+
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
6519
|
+
const show = hits.length ? hits : COMMANDS;
|
|
6520
|
+
return [show, line];
|
|
6521
|
+
} catch {
|
|
6522
|
+
return [[], line];
|
|
6523
|
+
}
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
6526
|
+
function completeFilePath(partial, filterVult) {
|
|
6527
|
+
try {
|
|
6528
|
+
const endsWithSeparator = partial.endsWith("/") || partial.endsWith(path2.sep);
|
|
6529
|
+
let dir;
|
|
6530
|
+
let basename;
|
|
6531
|
+
if (endsWithSeparator) {
|
|
6532
|
+
dir = partial;
|
|
6533
|
+
basename = "";
|
|
6534
|
+
} else {
|
|
6535
|
+
dir = path2.dirname(partial);
|
|
6536
|
+
basename = path2.basename(partial);
|
|
6537
|
+
if (fs2.existsSync(partial) && fs2.statSync(partial).isDirectory()) {
|
|
6538
|
+
dir = partial;
|
|
6539
|
+
basename = "";
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
const resolvedDir = path2.resolve(dir);
|
|
6543
|
+
if (!fs2.existsSync(resolvedDir) || !fs2.statSync(resolvedDir).isDirectory()) {
|
|
6544
|
+
return [[], partial];
|
|
6545
|
+
}
|
|
6546
|
+
const files = fs2.readdirSync(resolvedDir);
|
|
6547
|
+
const matches = files.filter((file) => file.startsWith(basename)).map((file) => {
|
|
6548
|
+
const fullPath = path2.join(dir, file);
|
|
6549
|
+
const stats = fs2.statSync(path2.join(resolvedDir, file));
|
|
6550
|
+
if (stats.isDirectory()) {
|
|
6551
|
+
return fullPath + "/";
|
|
3820
6552
|
}
|
|
3821
6553
|
if (filterVult) {
|
|
3822
6554
|
if (file.endsWith(".vult") || stats.isDirectory()) {
|
|
@@ -3840,7 +6572,7 @@ function completeVaultName(ctx2, partial) {
|
|
|
3840
6572
|
return [show, partial];
|
|
3841
6573
|
}
|
|
3842
6574
|
function completeChainName(partial) {
|
|
3843
|
-
const allChains = Object.values(
|
|
6575
|
+
const allChains = Object.values(Chain10);
|
|
3844
6576
|
const partialLower = partial.toLowerCase();
|
|
3845
6577
|
const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
|
|
3846
6578
|
matches.sort();
|
|
@@ -3848,14 +6580,14 @@ function completeChainName(partial) {
|
|
|
3848
6580
|
return [show, partial];
|
|
3849
6581
|
}
|
|
3850
6582
|
function findChainByName(name) {
|
|
3851
|
-
const allChains = Object.values(
|
|
6583
|
+
const allChains = Object.values(Chain10);
|
|
3852
6584
|
const nameLower = name.toLowerCase();
|
|
3853
6585
|
const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
|
|
3854
6586
|
return found ? found : null;
|
|
3855
6587
|
}
|
|
3856
6588
|
|
|
3857
6589
|
// src/interactive/event-buffer.ts
|
|
3858
|
-
import
|
|
6590
|
+
import chalk10 from "chalk";
|
|
3859
6591
|
var EventBuffer = class {
|
|
3860
6592
|
eventBuffer = [];
|
|
3861
6593
|
isCommandRunning = false;
|
|
@@ -3895,17 +6627,17 @@ var EventBuffer = class {
|
|
|
3895
6627
|
displayEvent(message, type) {
|
|
3896
6628
|
switch (type) {
|
|
3897
6629
|
case "success":
|
|
3898
|
-
console.log(
|
|
6630
|
+
console.log(chalk10.green(message));
|
|
3899
6631
|
break;
|
|
3900
6632
|
case "warning":
|
|
3901
|
-
console.log(
|
|
6633
|
+
console.log(chalk10.yellow(message));
|
|
3902
6634
|
break;
|
|
3903
6635
|
case "error":
|
|
3904
|
-
console.error(
|
|
6636
|
+
console.error(chalk10.red(message));
|
|
3905
6637
|
break;
|
|
3906
6638
|
case "info":
|
|
3907
6639
|
default:
|
|
3908
|
-
console.log(
|
|
6640
|
+
console.log(chalk10.blue(message));
|
|
3909
6641
|
break;
|
|
3910
6642
|
}
|
|
3911
6643
|
}
|
|
@@ -3916,13 +6648,13 @@ var EventBuffer = class {
|
|
|
3916
6648
|
if (this.eventBuffer.length === 0) {
|
|
3917
6649
|
return;
|
|
3918
6650
|
}
|
|
3919
|
-
console.log(
|
|
6651
|
+
console.log(chalk10.gray("\n--- Background Events ---"));
|
|
3920
6652
|
this.eventBuffer.forEach((event) => {
|
|
3921
6653
|
const timeStr = event.timestamp.toLocaleTimeString();
|
|
3922
6654
|
const message = `[${timeStr}] ${event.message}`;
|
|
3923
6655
|
this.displayEvent(message, event.type);
|
|
3924
6656
|
});
|
|
3925
|
-
console.log(
|
|
6657
|
+
console.log(chalk10.gray("--- End Events ---\n"));
|
|
3926
6658
|
}
|
|
3927
6659
|
/**
|
|
3928
6660
|
* Setup all vault event listeners
|
|
@@ -3939,6 +6671,12 @@ var EventBuffer = class {
|
|
|
3939
6671
|
this.handleEvent(`+ Transaction broadcast on ${chain}`, "success");
|
|
3940
6672
|
this.handleEvent(` TX Hash: ${txHash}`, "info");
|
|
3941
6673
|
});
|
|
6674
|
+
vault.on("transactionConfirmed", ({ chain, txHash }) => {
|
|
6675
|
+
this.handleEvent(`+ Transaction confirmed on ${chain}: ${txHash}`, "success");
|
|
6676
|
+
});
|
|
6677
|
+
vault.on("transactionFailed", ({ chain, txHash }) => {
|
|
6678
|
+
this.handleEvent(`x Transaction failed on ${chain}: ${txHash}`, "error");
|
|
6679
|
+
});
|
|
3942
6680
|
vault.on("signingProgress", ({ step }) => {
|
|
3943
6681
|
this.handleEvent(`i Signing: ${step}`, "info");
|
|
3944
6682
|
});
|
|
@@ -4002,6 +6740,8 @@ var EventBuffer = class {
|
|
|
4002
6740
|
vault.removeAllListeners("balanceUpdated");
|
|
4003
6741
|
vault.removeAllListeners("transactionSigned");
|
|
4004
6742
|
vault.removeAllListeners("transactionBroadcast");
|
|
6743
|
+
vault.removeAllListeners("transactionConfirmed");
|
|
6744
|
+
vault.removeAllListeners("transactionFailed");
|
|
4005
6745
|
vault.removeAllListeners("signingProgress");
|
|
4006
6746
|
vault.removeAllListeners("chainAdded");
|
|
4007
6747
|
vault.removeAllListeners("chainRemoved");
|
|
@@ -4024,13 +6764,13 @@ var EventBuffer = class {
|
|
|
4024
6764
|
|
|
4025
6765
|
// src/interactive/session.ts
|
|
4026
6766
|
import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
|
|
4027
|
-
import
|
|
6767
|
+
import chalk12 from "chalk";
|
|
4028
6768
|
import ora3 from "ora";
|
|
4029
|
-
import * as
|
|
6769
|
+
import * as readline3 from "readline";
|
|
4030
6770
|
|
|
4031
6771
|
// src/interactive/shell-commands.ts
|
|
4032
|
-
import
|
|
4033
|
-
import
|
|
6772
|
+
import chalk11 from "chalk";
|
|
6773
|
+
import Table2 from "cli-table3";
|
|
4034
6774
|
import inquirer6 from "inquirer";
|
|
4035
6775
|
import ora2 from "ora";
|
|
4036
6776
|
function formatTimeRemaining(ms) {
|
|
@@ -4046,25 +6786,25 @@ function formatTimeRemaining(ms) {
|
|
|
4046
6786
|
async function executeLock(ctx2) {
|
|
4047
6787
|
const vault = ctx2.getActiveVault();
|
|
4048
6788
|
if (!vault) {
|
|
4049
|
-
console.log(
|
|
4050
|
-
console.log(
|
|
6789
|
+
console.log(chalk11.red("No active vault."));
|
|
6790
|
+
console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
4051
6791
|
return;
|
|
4052
6792
|
}
|
|
4053
6793
|
ctx2.lockVault(vault.id);
|
|
4054
|
-
console.log(
|
|
4055
|
-
console.log(
|
|
6794
|
+
console.log(chalk11.green("\n+ Vault locked"));
|
|
6795
|
+
console.log(chalk11.gray("Password cache cleared. You will need to enter the password again."));
|
|
4056
6796
|
}
|
|
4057
6797
|
async function executeUnlock(ctx2) {
|
|
4058
6798
|
const vault = ctx2.getActiveVault();
|
|
4059
6799
|
if (!vault) {
|
|
4060
|
-
console.log(
|
|
4061
|
-
console.log(
|
|
6800
|
+
console.log(chalk11.red("No active vault."));
|
|
6801
|
+
console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
4062
6802
|
return;
|
|
4063
6803
|
}
|
|
4064
6804
|
if (ctx2.isVaultUnlocked(vault.id)) {
|
|
4065
6805
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
4066
|
-
console.log(
|
|
4067
|
-
console.log(
|
|
6806
|
+
console.log(chalk11.yellow("\nVault is already unlocked."));
|
|
6807
|
+
console.log(chalk11.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
4068
6808
|
return;
|
|
4069
6809
|
}
|
|
4070
6810
|
const { password } = await inquirer6.prompt([
|
|
@@ -4081,19 +6821,19 @@ async function executeUnlock(ctx2) {
|
|
|
4081
6821
|
ctx2.cachePassword(vault.id, password);
|
|
4082
6822
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
4083
6823
|
spinner.succeed("Vault unlocked");
|
|
4084
|
-
console.log(
|
|
6824
|
+
console.log(chalk11.green(`
|
|
4085
6825
|
+ Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
|
|
4086
6826
|
} catch (err) {
|
|
4087
6827
|
spinner.fail("Failed to unlock vault");
|
|
4088
|
-
console.error(
|
|
6828
|
+
console.error(chalk11.red(`
|
|
4089
6829
|
x ${err.message}`));
|
|
4090
6830
|
}
|
|
4091
6831
|
}
|
|
4092
6832
|
async function executeStatus(ctx2) {
|
|
4093
6833
|
const vault = ctx2.getActiveVault();
|
|
4094
6834
|
if (!vault) {
|
|
4095
|
-
console.log(
|
|
4096
|
-
console.log(
|
|
6835
|
+
console.log(chalk11.red("No active vault."));
|
|
6836
|
+
console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
4097
6837
|
return;
|
|
4098
6838
|
}
|
|
4099
6839
|
const isUnlocked = ctx2.isVaultUnlocked(vault.id);
|
|
@@ -4124,30 +6864,30 @@ async function executeStatus(ctx2) {
|
|
|
4124
6864
|
displayStatus(status);
|
|
4125
6865
|
}
|
|
4126
6866
|
function displayStatus(status) {
|
|
4127
|
-
console.log(
|
|
4128
|
-
console.log(
|
|
4129
|
-
console.log(
|
|
4130
|
-
console.log(
|
|
4131
|
-
console.log(` Name: ${
|
|
6867
|
+
console.log(chalk11.cyan("\n+----------------------------------------+"));
|
|
6868
|
+
console.log(chalk11.cyan("| Vault Status |"));
|
|
6869
|
+
console.log(chalk11.cyan("+----------------------------------------+\n"));
|
|
6870
|
+
console.log(chalk11.bold("Vault:"));
|
|
6871
|
+
console.log(` Name: ${chalk11.green(status.name)}`);
|
|
4132
6872
|
console.log(` ID: ${status.id}`);
|
|
4133
|
-
console.log(` Type: ${
|
|
4134
|
-
console.log(
|
|
6873
|
+
console.log(` Type: ${chalk11.yellow(status.type)}`);
|
|
6874
|
+
console.log(chalk11.bold("\nSecurity:"));
|
|
4135
6875
|
if (status.isUnlocked) {
|
|
4136
|
-
console.log(` Status: ${
|
|
6876
|
+
console.log(` Status: ${chalk11.green("Unlocked")} ${chalk11.green("\u{1F513}")}`);
|
|
4137
6877
|
console.log(` Expires: ${status.timeRemainingFormatted}`);
|
|
4138
6878
|
} else {
|
|
4139
|
-
console.log(` Status: ${
|
|
6879
|
+
console.log(` Status: ${chalk11.yellow("Locked")} ${chalk11.yellow("\u{1F512}")}`);
|
|
4140
6880
|
}
|
|
4141
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
4142
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
4143
|
-
console.log(
|
|
6881
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk11.green("Yes") : chalk11.gray("No")}`);
|
|
6882
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk11.green("Yes") : chalk11.yellow("No")}`);
|
|
6883
|
+
console.log(chalk11.bold("\nMPC Configuration:"));
|
|
4144
6884
|
console.log(` Library: ${status.libType}`);
|
|
4145
|
-
console.log(` Threshold: ${
|
|
4146
|
-
console.log(
|
|
6885
|
+
console.log(` Threshold: ${chalk11.cyan(status.threshold)} of ${chalk11.cyan(status.totalSigners)}`);
|
|
6886
|
+
console.log(chalk11.bold("\nSigning Modes:"));
|
|
4147
6887
|
status.availableSigningModes.forEach((mode) => {
|
|
4148
6888
|
console.log(` - ${mode}`);
|
|
4149
6889
|
});
|
|
4150
|
-
console.log(
|
|
6890
|
+
console.log(chalk11.bold("\nDetails:"));
|
|
4151
6891
|
console.log(` Chains: ${status.chains}`);
|
|
4152
6892
|
console.log(` Currency: ${status.currency.toUpperCase()}`);
|
|
4153
6893
|
console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
|
|
@@ -4155,8 +6895,8 @@ function displayStatus(status) {
|
|
|
4155
6895
|
`);
|
|
4156
6896
|
}
|
|
4157
6897
|
function showHelp() {
|
|
4158
|
-
const table = new
|
|
4159
|
-
head: [
|
|
6898
|
+
const table = new Table2({
|
|
6899
|
+
head: [chalk11.bold("Available Commands")],
|
|
4160
6900
|
colWidths: [50],
|
|
4161
6901
|
chars: {
|
|
4162
6902
|
mid: "",
|
|
@@ -4170,7 +6910,7 @@ function showHelp() {
|
|
|
4170
6910
|
}
|
|
4171
6911
|
});
|
|
4172
6912
|
table.push(
|
|
4173
|
-
[
|
|
6913
|
+
[chalk11.bold("Vault Management:")],
|
|
4174
6914
|
[" vaults - List all vaults"],
|
|
4175
6915
|
[" vault <name> - Switch to vault"],
|
|
4176
6916
|
[" import <file> - Import vault from file"],
|
|
@@ -4179,30 +6919,31 @@ function showHelp() {
|
|
|
4179
6919
|
[" info - Show vault details"],
|
|
4180
6920
|
[" export [path] - Export vault to file"],
|
|
4181
6921
|
[""],
|
|
4182
|
-
[
|
|
6922
|
+
[chalk11.bold("Wallet Operations:")],
|
|
4183
6923
|
[" balance [chain] - Show balances"],
|
|
4184
6924
|
[" send <chain> <to> <amount> - Send transaction"],
|
|
6925
|
+
[" tx-status <chain> <txHash> - Check transaction status"],
|
|
4185
6926
|
[" portfolio [-c usd] - Show portfolio value"],
|
|
4186
6927
|
[" addresses - Show all addresses"],
|
|
4187
6928
|
[" chains [--add/--remove/--add-all] - Manage chains"],
|
|
4188
6929
|
[" tokens <chain> - Manage tokens"],
|
|
4189
6930
|
[""],
|
|
4190
|
-
[
|
|
6931
|
+
[chalk11.bold("Swap Operations:")],
|
|
4191
6932
|
[" swap-chains - List swap-enabled chains"],
|
|
4192
6933
|
[" swap-quote <from> <to> <amount> - Get quote"],
|
|
4193
6934
|
[" swap <from> <to> <amount> - Execute swap"],
|
|
4194
6935
|
[""],
|
|
4195
|
-
[
|
|
6936
|
+
[chalk11.bold("Session Commands (shell only):")],
|
|
4196
6937
|
[" lock - Lock vault"],
|
|
4197
6938
|
[" unlock - Unlock vault"],
|
|
4198
6939
|
[" status - Show vault status"],
|
|
4199
6940
|
[""],
|
|
4200
|
-
[
|
|
6941
|
+
[chalk11.bold("Settings:")],
|
|
4201
6942
|
[" currency [code] - View/set currency"],
|
|
4202
6943
|
[" server - Check server status"],
|
|
4203
6944
|
[" address-book - Manage saved addresses"],
|
|
4204
6945
|
[""],
|
|
4205
|
-
[
|
|
6946
|
+
[chalk11.bold("Help & Navigation:")],
|
|
4206
6947
|
[" help, ? - Show this help"],
|
|
4207
6948
|
[" .clear - Clear screen"],
|
|
4208
6949
|
[" .exit - Exit shell"]
|
|
@@ -4340,12 +7081,12 @@ var ShellSession = class {
|
|
|
4340
7081
|
*/
|
|
4341
7082
|
async start() {
|
|
4342
7083
|
console.clear();
|
|
4343
|
-
console.log(
|
|
4344
|
-
console.log(
|
|
4345
|
-
console.log(
|
|
7084
|
+
console.log(chalk12.cyan.bold("\n=============================================="));
|
|
7085
|
+
console.log(chalk12.cyan.bold(" Vultisig Interactive Shell"));
|
|
7086
|
+
console.log(chalk12.cyan.bold("==============================================\n"));
|
|
4346
7087
|
await this.loadAllVaults();
|
|
4347
7088
|
this.displayVaultList();
|
|
4348
|
-
console.log(
|
|
7089
|
+
console.log(chalk12.gray('Type "help" for available commands, "exit" to quit\n'));
|
|
4349
7090
|
this.promptLoop().catch(() => {
|
|
4350
7091
|
});
|
|
4351
7092
|
}
|
|
@@ -4363,7 +7104,7 @@ var ShellSession = class {
|
|
|
4363
7104
|
*/
|
|
4364
7105
|
readLine(prompt) {
|
|
4365
7106
|
return new Promise((resolve) => {
|
|
4366
|
-
const rl =
|
|
7107
|
+
const rl = readline3.createInterface({
|
|
4367
7108
|
input: process.stdin,
|
|
4368
7109
|
output: process.stdout,
|
|
4369
7110
|
completer: (line, cb) => {
|
|
@@ -4379,12 +7120,12 @@ var ShellSession = class {
|
|
|
4379
7120
|
const now = Date.now();
|
|
4380
7121
|
if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
|
|
4381
7122
|
rl.close();
|
|
4382
|
-
console.log(
|
|
7123
|
+
console.log(chalk12.yellow("\nGoodbye!"));
|
|
4383
7124
|
this.ctx.dispose();
|
|
4384
7125
|
process.exit(0);
|
|
4385
7126
|
}
|
|
4386
7127
|
this.lastSigintTime = now;
|
|
4387
|
-
console.log(
|
|
7128
|
+
console.log(chalk12.yellow("\n(Press Ctrl+C again to exit)"));
|
|
4388
7129
|
rl.close();
|
|
4389
7130
|
resolve("");
|
|
4390
7131
|
});
|
|
@@ -4396,7 +7137,7 @@ var ShellSession = class {
|
|
|
4396
7137
|
prompt(message, defaultValue) {
|
|
4397
7138
|
return new Promise((resolve, reject) => {
|
|
4398
7139
|
const displayPrompt = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
4399
|
-
const rl =
|
|
7140
|
+
const rl = readline3.createInterface({
|
|
4400
7141
|
input: process.stdin,
|
|
4401
7142
|
output: process.stdout,
|
|
4402
7143
|
terminal: true
|
|
@@ -4416,7 +7157,7 @@ var ShellSession = class {
|
|
|
4416
7157
|
*/
|
|
4417
7158
|
promptPassword(message) {
|
|
4418
7159
|
return new Promise((resolve, reject) => {
|
|
4419
|
-
const rl =
|
|
7160
|
+
const rl = readline3.createInterface({
|
|
4420
7161
|
input: process.stdin,
|
|
4421
7162
|
output: process.stdout,
|
|
4422
7163
|
terminal: true
|
|
@@ -4479,7 +7220,7 @@ var ShellSession = class {
|
|
|
4479
7220
|
stopAllSpinners();
|
|
4480
7221
|
process.stdout.write("\x1B[?25h");
|
|
4481
7222
|
process.stdout.write("\r\x1B[K");
|
|
4482
|
-
console.log(
|
|
7223
|
+
console.log(chalk12.yellow("\nCancelling operation..."));
|
|
4483
7224
|
};
|
|
4484
7225
|
const cleanup = () => {
|
|
4485
7226
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -4516,10 +7257,10 @@ var ShellSession = class {
|
|
|
4516
7257
|
stopAllSpinners();
|
|
4517
7258
|
process.stdout.write("\x1B[?25h");
|
|
4518
7259
|
process.stdout.write("\r\x1B[K");
|
|
4519
|
-
console.log(
|
|
7260
|
+
console.log(chalk12.yellow("Operation cancelled"));
|
|
4520
7261
|
return;
|
|
4521
7262
|
}
|
|
4522
|
-
console.error(
|
|
7263
|
+
console.error(chalk12.red(`
|
|
4523
7264
|
Error: ${error2.message}`));
|
|
4524
7265
|
}
|
|
4525
7266
|
}
|
|
@@ -4552,7 +7293,7 @@ Error: ${error2.message}`));
|
|
|
4552
7293
|
break;
|
|
4553
7294
|
case "rename":
|
|
4554
7295
|
if (args.length === 0) {
|
|
4555
|
-
console.log(
|
|
7296
|
+
console.log(chalk12.yellow("Usage: rename <newName>"));
|
|
4556
7297
|
return;
|
|
4557
7298
|
}
|
|
4558
7299
|
await executeRename(this.ctx, args.join(" "));
|
|
@@ -4572,6 +7313,9 @@ Error: ${error2.message}`));
|
|
|
4572
7313
|
case "send":
|
|
4573
7314
|
await this.runSend(args);
|
|
4574
7315
|
break;
|
|
7316
|
+
case "tx-status":
|
|
7317
|
+
await this.runTxStatus(args);
|
|
7318
|
+
break;
|
|
4575
7319
|
// Chain management
|
|
4576
7320
|
case "addresses":
|
|
4577
7321
|
await executeAddresses(this.ctx);
|
|
@@ -4625,41 +7369,41 @@ Error: ${error2.message}`));
|
|
|
4625
7369
|
// Exit
|
|
4626
7370
|
case "exit":
|
|
4627
7371
|
case "quit":
|
|
4628
|
-
console.log(
|
|
7372
|
+
console.log(chalk12.yellow("\nGoodbye!"));
|
|
4629
7373
|
this.ctx.dispose();
|
|
4630
7374
|
process.exit(0);
|
|
4631
7375
|
break;
|
|
4632
7376
|
// eslint requires break even after process.exit
|
|
4633
7377
|
default:
|
|
4634
|
-
console.log(
|
|
4635
|
-
console.log(
|
|
7378
|
+
console.log(chalk12.yellow(`Unknown command: ${command}`));
|
|
7379
|
+
console.log(chalk12.gray('Type "help" for available commands'));
|
|
4636
7380
|
break;
|
|
4637
7381
|
}
|
|
4638
7382
|
}
|
|
4639
7383
|
// ===== Command Helpers =====
|
|
4640
7384
|
async switchVault(args) {
|
|
4641
7385
|
if (args.length === 0) {
|
|
4642
|
-
console.log(
|
|
4643
|
-
console.log(
|
|
7386
|
+
console.log(chalk12.yellow("Usage: vault <name>"));
|
|
7387
|
+
console.log(chalk12.gray('Run "vaults" to see available vaults'));
|
|
4644
7388
|
return;
|
|
4645
7389
|
}
|
|
4646
7390
|
const vaultName = args.join(" ");
|
|
4647
7391
|
const vault = this.ctx.findVaultByName(vaultName);
|
|
4648
7392
|
if (!vault) {
|
|
4649
|
-
console.log(
|
|
4650
|
-
console.log(
|
|
7393
|
+
console.log(chalk12.red(`Vault not found: ${vaultName}`));
|
|
7394
|
+
console.log(chalk12.gray('Run "vaults" to see available vaults'));
|
|
4651
7395
|
return;
|
|
4652
7396
|
}
|
|
4653
7397
|
await this.ctx.setActiveVault(vault);
|
|
4654
|
-
console.log(
|
|
7398
|
+
console.log(chalk12.green(`
|
|
4655
7399
|
+ Switched to: ${vault.name}`));
|
|
4656
7400
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4657
|
-
const status = isUnlocked ?
|
|
7401
|
+
const status = isUnlocked ? chalk12.green("Unlocked") : chalk12.yellow("Locked");
|
|
4658
7402
|
console.log(`Status: ${status}`);
|
|
4659
7403
|
}
|
|
4660
7404
|
async importVault(args) {
|
|
4661
7405
|
if (args.length === 0) {
|
|
4662
|
-
console.log(
|
|
7406
|
+
console.log(chalk12.yellow("Usage: import <file>"));
|
|
4663
7407
|
return;
|
|
4664
7408
|
}
|
|
4665
7409
|
const filePath = args.join(" ");
|
|
@@ -4674,45 +7418,45 @@ Error: ${error2.message}`));
|
|
|
4674
7418
|
async createVault(args) {
|
|
4675
7419
|
const type = args[0]?.toLowerCase();
|
|
4676
7420
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4677
|
-
console.log(
|
|
4678
|
-
console.log(
|
|
4679
|
-
console.log(
|
|
7421
|
+
console.log(chalk12.yellow("Usage: create <fast|secure>"));
|
|
7422
|
+
console.log(chalk12.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
7423
|
+
console.log(chalk12.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
4680
7424
|
return;
|
|
4681
7425
|
}
|
|
4682
7426
|
let vault;
|
|
4683
7427
|
if (type === "fast") {
|
|
4684
7428
|
const name = await this.prompt("Vault name");
|
|
4685
7429
|
if (!name) {
|
|
4686
|
-
console.log(
|
|
7430
|
+
console.log(chalk12.red("Name is required"));
|
|
4687
7431
|
return;
|
|
4688
7432
|
}
|
|
4689
7433
|
const password = await this.promptPassword("Vault password");
|
|
4690
7434
|
if (!password) {
|
|
4691
|
-
console.log(
|
|
7435
|
+
console.log(chalk12.red("Password is required"));
|
|
4692
7436
|
return;
|
|
4693
7437
|
}
|
|
4694
7438
|
const email = await this.prompt("Email for verification");
|
|
4695
7439
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4696
|
-
console.log(
|
|
7440
|
+
console.log(chalk12.red("Valid email is required"));
|
|
4697
7441
|
return;
|
|
4698
7442
|
}
|
|
4699
7443
|
vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
|
|
4700
7444
|
} else {
|
|
4701
7445
|
const name = await this.prompt("Vault name");
|
|
4702
7446
|
if (!name) {
|
|
4703
|
-
console.log(
|
|
7447
|
+
console.log(chalk12.red("Name is required"));
|
|
4704
7448
|
return;
|
|
4705
7449
|
}
|
|
4706
7450
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4707
7451
|
const shares = parseInt(sharesStr, 10);
|
|
4708
7452
|
if (isNaN(shares) || shares < 2) {
|
|
4709
|
-
console.log(
|
|
7453
|
+
console.log(chalk12.red("Must have at least 2 shares"));
|
|
4710
7454
|
return;
|
|
4711
7455
|
}
|
|
4712
7456
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4713
7457
|
const threshold = parseInt(thresholdStr, 10);
|
|
4714
7458
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4715
|
-
console.log(
|
|
7459
|
+
console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
|
|
4716
7460
|
return;
|
|
4717
7461
|
}
|
|
4718
7462
|
const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
|
|
@@ -4734,37 +7478,37 @@ Error: ${error2.message}`));
|
|
|
4734
7478
|
async importSeedphrase(args) {
|
|
4735
7479
|
const type = args[0]?.toLowerCase();
|
|
4736
7480
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4737
|
-
console.log(
|
|
4738
|
-
console.log(
|
|
4739
|
-
console.log(
|
|
7481
|
+
console.log(chalk12.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
7482
|
+
console.log(chalk12.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
7483
|
+
console.log(chalk12.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4740
7484
|
return;
|
|
4741
7485
|
}
|
|
4742
|
-
console.log(
|
|
7486
|
+
console.log(chalk12.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
4743
7487
|
const mnemonic = await this.promptPassword("Seedphrase");
|
|
4744
7488
|
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
4745
7489
|
if (!validation.valid) {
|
|
4746
|
-
console.log(
|
|
7490
|
+
console.log(chalk12.red(`Invalid seedphrase: ${validation.error}`));
|
|
4747
7491
|
if (validation.invalidWords?.length) {
|
|
4748
|
-
console.log(
|
|
7492
|
+
console.log(chalk12.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4749
7493
|
}
|
|
4750
7494
|
return;
|
|
4751
7495
|
}
|
|
4752
|
-
console.log(
|
|
7496
|
+
console.log(chalk12.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
4753
7497
|
let vault;
|
|
4754
7498
|
if (type === "fast") {
|
|
4755
7499
|
const name = await this.prompt("Vault name");
|
|
4756
7500
|
if (!name) {
|
|
4757
|
-
console.log(
|
|
7501
|
+
console.log(chalk12.red("Name is required"));
|
|
4758
7502
|
return;
|
|
4759
7503
|
}
|
|
4760
7504
|
const password = await this.promptPassword("Vault password");
|
|
4761
7505
|
if (!password) {
|
|
4762
|
-
console.log(
|
|
7506
|
+
console.log(chalk12.red("Password is required"));
|
|
4763
7507
|
return;
|
|
4764
7508
|
}
|
|
4765
7509
|
const email = await this.prompt("Email for verification");
|
|
4766
7510
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4767
|
-
console.log(
|
|
7511
|
+
console.log(chalk12.red("Valid email is required"));
|
|
4768
7512
|
return;
|
|
4769
7513
|
}
|
|
4770
7514
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
@@ -4782,19 +7526,19 @@ Error: ${error2.message}`));
|
|
|
4782
7526
|
} else {
|
|
4783
7527
|
const name = await this.prompt("Vault name");
|
|
4784
7528
|
if (!name) {
|
|
4785
|
-
console.log(
|
|
7529
|
+
console.log(chalk12.red("Name is required"));
|
|
4786
7530
|
return;
|
|
4787
7531
|
}
|
|
4788
7532
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4789
7533
|
const shares = parseInt(sharesStr, 10);
|
|
4790
7534
|
if (isNaN(shares) || shares < 2) {
|
|
4791
|
-
console.log(
|
|
7535
|
+
console.log(chalk12.red("Must have at least 2 shares"));
|
|
4792
7536
|
return;
|
|
4793
7537
|
}
|
|
4794
7538
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4795
7539
|
const threshold = parseInt(thresholdStr, 10);
|
|
4796
7540
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4797
|
-
console.log(
|
|
7541
|
+
console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
|
|
4798
7542
|
return;
|
|
4799
7543
|
}
|
|
4800
7544
|
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
@@ -4838,8 +7582,8 @@ Error: ${error2.message}`));
|
|
|
4838
7582
|
}
|
|
4839
7583
|
}
|
|
4840
7584
|
if (!fiatCurrencies3.includes(currency)) {
|
|
4841
|
-
console.log(
|
|
4842
|
-
console.log(
|
|
7585
|
+
console.log(chalk12.red(`Invalid currency: ${currency}`));
|
|
7586
|
+
console.log(chalk12.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
|
|
4843
7587
|
return;
|
|
4844
7588
|
}
|
|
4845
7589
|
const raw = args.includes("--raw");
|
|
@@ -4847,7 +7591,7 @@ Error: ${error2.message}`));
|
|
|
4847
7591
|
}
|
|
4848
7592
|
async runSend(args) {
|
|
4849
7593
|
if (args.length < 3) {
|
|
4850
|
-
console.log(
|
|
7594
|
+
console.log(chalk12.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
|
|
4851
7595
|
return;
|
|
4852
7596
|
}
|
|
4853
7597
|
const [chainStr, to, amount, ...rest] = args;
|
|
@@ -4867,12 +7611,22 @@ Error: ${error2.message}`));
|
|
|
4867
7611
|
await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
|
|
4868
7612
|
} catch (err) {
|
|
4869
7613
|
if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4870
|
-
console.log(
|
|
7614
|
+
console.log(chalk12.yellow("\nTransaction cancelled"));
|
|
4871
7615
|
return;
|
|
4872
7616
|
}
|
|
4873
7617
|
throw err;
|
|
4874
7618
|
}
|
|
4875
7619
|
}
|
|
7620
|
+
async runTxStatus(args) {
|
|
7621
|
+
if (args.length < 2) {
|
|
7622
|
+
console.log(chalk12.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
|
|
7623
|
+
return;
|
|
7624
|
+
}
|
|
7625
|
+
const [chainStr, txHash, ...rest] = args;
|
|
7626
|
+
const chain = findChainByName(chainStr) || chainStr;
|
|
7627
|
+
const noWait = rest.includes("--no-wait");
|
|
7628
|
+
await this.withCancellation(() => executeTxStatus(this.ctx, { chain, txHash, noWait }));
|
|
7629
|
+
}
|
|
4876
7630
|
async runChains(args) {
|
|
4877
7631
|
let addChain;
|
|
4878
7632
|
let removeChain;
|
|
@@ -4883,8 +7637,8 @@ Error: ${error2.message}`));
|
|
|
4883
7637
|
} else if (args[i] === "--add" && i + 1 < args.length) {
|
|
4884
7638
|
const chain = findChainByName(args[i + 1]);
|
|
4885
7639
|
if (!chain) {
|
|
4886
|
-
console.log(
|
|
4887
|
-
console.log(
|
|
7640
|
+
console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
|
|
7641
|
+
console.log(chalk12.gray("Use tab completion to see available chains"));
|
|
4888
7642
|
return;
|
|
4889
7643
|
}
|
|
4890
7644
|
addChain = chain;
|
|
@@ -4892,8 +7646,8 @@ Error: ${error2.message}`));
|
|
|
4892
7646
|
} else if (args[i] === "--remove" && i + 1 < args.length) {
|
|
4893
7647
|
const chain = findChainByName(args[i + 1]);
|
|
4894
7648
|
if (!chain) {
|
|
4895
|
-
console.log(
|
|
4896
|
-
console.log(
|
|
7649
|
+
console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
|
|
7650
|
+
console.log(chalk12.gray("Use tab completion to see available chains"));
|
|
4897
7651
|
return;
|
|
4898
7652
|
}
|
|
4899
7653
|
removeChain = chain;
|
|
@@ -4904,7 +7658,7 @@ Error: ${error2.message}`));
|
|
|
4904
7658
|
}
|
|
4905
7659
|
async runTokens(args) {
|
|
4906
7660
|
if (args.length === 0) {
|
|
4907
|
-
console.log(
|
|
7661
|
+
console.log(chalk12.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
|
|
4908
7662
|
return;
|
|
4909
7663
|
}
|
|
4910
7664
|
const chainStr = args[0];
|
|
@@ -4925,7 +7679,7 @@ Error: ${error2.message}`));
|
|
|
4925
7679
|
async runSwapQuote(args) {
|
|
4926
7680
|
if (args.length < 3) {
|
|
4927
7681
|
console.log(
|
|
4928
|
-
|
|
7682
|
+
chalk12.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
|
|
4929
7683
|
);
|
|
4930
7684
|
return;
|
|
4931
7685
|
}
|
|
@@ -4949,7 +7703,7 @@ Error: ${error2.message}`));
|
|
|
4949
7703
|
async runSwap(args) {
|
|
4950
7704
|
if (args.length < 3) {
|
|
4951
7705
|
console.log(
|
|
4952
|
-
|
|
7706
|
+
chalk12.yellow(
|
|
4953
7707
|
"Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
|
|
4954
7708
|
)
|
|
4955
7709
|
);
|
|
@@ -4980,7 +7734,7 @@ Error: ${error2.message}`));
|
|
|
4980
7734
|
);
|
|
4981
7735
|
} catch (err) {
|
|
4982
7736
|
if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4983
|
-
console.log(
|
|
7737
|
+
console.log(chalk12.yellow("\nSwap cancelled"));
|
|
4984
7738
|
return;
|
|
4985
7739
|
}
|
|
4986
7740
|
throw err;
|
|
@@ -5042,24 +7796,24 @@ Error: ${error2.message}`));
|
|
|
5042
7796
|
}
|
|
5043
7797
|
getPrompt() {
|
|
5044
7798
|
const vault = this.ctx.getActiveVault();
|
|
5045
|
-
if (!vault) return
|
|
7799
|
+
if (!vault) return chalk12.cyan("wallet> ");
|
|
5046
7800
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
5047
|
-
const status = isUnlocked ?
|
|
5048
|
-
return
|
|
7801
|
+
const status = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
|
|
7802
|
+
return chalk12.cyan(`wallet[${vault.name}]${status}> `);
|
|
5049
7803
|
}
|
|
5050
7804
|
displayVaultList() {
|
|
5051
7805
|
const vaults = Array.from(this.ctx.getVaults().values());
|
|
5052
7806
|
const activeVault = this.ctx.getActiveVault();
|
|
5053
7807
|
if (vaults.length === 0) {
|
|
5054
|
-
console.log(
|
|
7808
|
+
console.log(chalk12.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
|
|
5055
7809
|
return;
|
|
5056
7810
|
}
|
|
5057
|
-
console.log(
|
|
7811
|
+
console.log(chalk12.cyan("Loaded Vaults:\n"));
|
|
5058
7812
|
vaults.forEach((vault) => {
|
|
5059
7813
|
const isActive = vault.id === activeVault?.id;
|
|
5060
7814
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
5061
|
-
const activeMarker = isActive ?
|
|
5062
|
-
const lockIcon = isUnlocked ?
|
|
7815
|
+
const activeMarker = isActive ? chalk12.green(" (active)") : "";
|
|
7816
|
+
const lockIcon = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
|
|
5063
7817
|
console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
|
|
5064
7818
|
});
|
|
5065
7819
|
console.log();
|
|
@@ -5067,23 +7821,23 @@ Error: ${error2.message}`));
|
|
|
5067
7821
|
};
|
|
5068
7822
|
|
|
5069
7823
|
// src/lib/errors.ts
|
|
5070
|
-
import
|
|
7824
|
+
import chalk13 from "chalk";
|
|
5071
7825
|
|
|
5072
7826
|
// src/lib/version.ts
|
|
5073
|
-
import
|
|
5074
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
5075
|
-
import { homedir } from "os";
|
|
5076
|
-
import { join } from "path";
|
|
7827
|
+
import chalk14 from "chalk";
|
|
7828
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
7829
|
+
import { homedir as homedir2 } from "os";
|
|
7830
|
+
import { join as join2 } from "path";
|
|
5077
7831
|
var cachedVersion = null;
|
|
5078
7832
|
function getVersion() {
|
|
5079
7833
|
if (cachedVersion) return cachedVersion;
|
|
5080
7834
|
if (true) {
|
|
5081
|
-
cachedVersion = "0.
|
|
7835
|
+
cachedVersion = "0.8.0";
|
|
5082
7836
|
return cachedVersion;
|
|
5083
7837
|
}
|
|
5084
7838
|
try {
|
|
5085
7839
|
const packagePath = new URL("../../package.json", import.meta.url);
|
|
5086
|
-
const pkg = JSON.parse(
|
|
7840
|
+
const pkg = JSON.parse(readFileSync2(packagePath, "utf-8"));
|
|
5087
7841
|
cachedVersion = pkg.version;
|
|
5088
7842
|
return cachedVersion;
|
|
5089
7843
|
} catch {
|
|
@@ -5091,13 +7845,13 @@ function getVersion() {
|
|
|
5091
7845
|
return cachedVersion;
|
|
5092
7846
|
}
|
|
5093
7847
|
}
|
|
5094
|
-
var CACHE_DIR =
|
|
5095
|
-
var VERSION_CACHE_FILE =
|
|
7848
|
+
var CACHE_DIR = join2(homedir2(), ".vultisig", "cache");
|
|
7849
|
+
var VERSION_CACHE_FILE = join2(CACHE_DIR, "version-check.json");
|
|
5096
7850
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5097
7851
|
function readVersionCache() {
|
|
5098
7852
|
try {
|
|
5099
|
-
if (!
|
|
5100
|
-
const data =
|
|
7853
|
+
if (!existsSync2(VERSION_CACHE_FILE)) return null;
|
|
7854
|
+
const data = readFileSync2(VERSION_CACHE_FILE, "utf-8");
|
|
5101
7855
|
return JSON.parse(data);
|
|
5102
7856
|
} catch {
|
|
5103
7857
|
return null;
|
|
@@ -5105,10 +7859,10 @@ function readVersionCache() {
|
|
|
5105
7859
|
}
|
|
5106
7860
|
function writeVersionCache(cache) {
|
|
5107
7861
|
try {
|
|
5108
|
-
if (!
|
|
5109
|
-
|
|
7862
|
+
if (!existsSync2(CACHE_DIR)) {
|
|
7863
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
5110
7864
|
}
|
|
5111
|
-
|
|
7865
|
+
writeFileSync2(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
5112
7866
|
} catch {
|
|
5113
7867
|
}
|
|
5114
7868
|
}
|
|
@@ -5132,8 +7886,8 @@ async function fetchLatestVersion() {
|
|
|
5132
7886
|
}
|
|
5133
7887
|
function isNewerVersion(v1, v2) {
|
|
5134
7888
|
const parse = (v) => {
|
|
5135
|
-
const
|
|
5136
|
-
return
|
|
7889
|
+
const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
|
|
7890
|
+
return clean2.split(".").map((n) => parseInt(n, 10) || 0);
|
|
5137
7891
|
};
|
|
5138
7892
|
const p1 = parse(v1);
|
|
5139
7893
|
const p2 = parse(v2);
|
|
@@ -5175,7 +7929,7 @@ function formatVersionShort() {
|
|
|
5175
7929
|
}
|
|
5176
7930
|
function formatVersionDetailed() {
|
|
5177
7931
|
const lines = [];
|
|
5178
|
-
lines.push(
|
|
7932
|
+
lines.push(chalk14.bold(`Vultisig CLI v${getVersion()}`));
|
|
5179
7933
|
lines.push("");
|
|
5180
7934
|
lines.push(` Node.js: ${process.version}`);
|
|
5181
7935
|
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
@@ -5213,9 +7967,9 @@ function getUpdateCommand() {
|
|
|
5213
7967
|
}
|
|
5214
7968
|
|
|
5215
7969
|
// src/lib/completion.ts
|
|
5216
|
-
import { homedir as
|
|
5217
|
-
import { join as
|
|
5218
|
-
import { readFileSync as
|
|
7970
|
+
import { homedir as homedir3 } from "os";
|
|
7971
|
+
import { join as join3 } from "path";
|
|
7972
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
5219
7973
|
var tabtab = null;
|
|
5220
7974
|
async function getTabtab() {
|
|
5221
7975
|
if (!tabtab) {
|
|
@@ -5280,15 +8034,15 @@ var CHAINS = [
|
|
|
5280
8034
|
];
|
|
5281
8035
|
function getVaultNames() {
|
|
5282
8036
|
try {
|
|
5283
|
-
const vaultDir =
|
|
5284
|
-
if (!
|
|
8037
|
+
const vaultDir = join3(homedir3(), ".vultisig", "vaults");
|
|
8038
|
+
if (!existsSync3(vaultDir)) return [];
|
|
5285
8039
|
const { readdirSync } = __require("fs");
|
|
5286
8040
|
const files = readdirSync(vaultDir);
|
|
5287
8041
|
const names = [];
|
|
5288
8042
|
for (const file of files) {
|
|
5289
8043
|
if (file.startsWith("vault:") && file.endsWith(".json")) {
|
|
5290
8044
|
try {
|
|
5291
|
-
const content =
|
|
8045
|
+
const content = readFileSync3(join3(vaultDir, file), "utf-8");
|
|
5292
8046
|
const vault = JSON.parse(content);
|
|
5293
8047
|
if (vault.name) names.push(vault.name);
|
|
5294
8048
|
if (vault.id) names.push(vault.id);
|
|
@@ -5526,7 +8280,26 @@ complete -c vsig -n "__fish_seen_subcommand_from import export" -a "(__fish_comp
|
|
|
5526
8280
|
`.trim();
|
|
5527
8281
|
}
|
|
5528
8282
|
|
|
8283
|
+
// src/lib/user-agent.ts
|
|
8284
|
+
function setupUserAgent() {
|
|
8285
|
+
const userAgent = `vultisig-cli/${getVersion()}`;
|
|
8286
|
+
const originalFetch = globalThis.fetch;
|
|
8287
|
+
globalThis.fetch = (input, init2) => {
|
|
8288
|
+
const headers = new Headers(input instanceof Request ? input.headers : void 0);
|
|
8289
|
+
if (init2?.headers) {
|
|
8290
|
+
new Headers(init2.headers).forEach((value, key) => {
|
|
8291
|
+
headers.set(key, value);
|
|
8292
|
+
});
|
|
8293
|
+
}
|
|
8294
|
+
if (!headers.has("User-Agent")) {
|
|
8295
|
+
headers.set("User-Agent", userAgent);
|
|
8296
|
+
}
|
|
8297
|
+
return originalFetch(input, { ...init2, headers });
|
|
8298
|
+
};
|
|
8299
|
+
}
|
|
8300
|
+
|
|
5529
8301
|
// src/index.ts
|
|
8302
|
+
setupUserAgent();
|
|
5530
8303
|
(async () => {
|
|
5531
8304
|
const handled = await handleCompletion();
|
|
5532
8305
|
if (handled) process.exit(0);
|
|
@@ -5546,14 +8319,15 @@ async function findVaultByNameOrId(sdk, nameOrId) {
|
|
|
5546
8319
|
if (byPartialId) return byPartialId;
|
|
5547
8320
|
return null;
|
|
5548
8321
|
}
|
|
5549
|
-
async function init(vaultOverride, unlockPassword) {
|
|
8322
|
+
async function init(vaultOverride, unlockPassword, passwordTTL) {
|
|
5550
8323
|
if (!ctx) {
|
|
5551
8324
|
const vaultSelector = vaultOverride || process.env.VULTISIG_VAULT;
|
|
5552
8325
|
if (unlockPassword && vaultSelector) {
|
|
5553
8326
|
cachePassword(vaultSelector, unlockPassword);
|
|
5554
8327
|
}
|
|
5555
|
-
const sdk = new
|
|
5556
|
-
onPasswordRequired: createPasswordCallback()
|
|
8328
|
+
const sdk = new Vultisig7({
|
|
8329
|
+
onPasswordRequired: createPasswordCallback(),
|
|
8330
|
+
...passwordTTL !== void 0 ? { passwordCache: { defaultTTL: passwordTTL } } : {}
|
|
5557
8331
|
});
|
|
5558
8332
|
await sdk.initialize();
|
|
5559
8333
|
ctx = new CLIContext(sdk);
|
|
@@ -5574,10 +8348,10 @@ async function init(vaultOverride, unlockPassword) {
|
|
|
5574
8348
|
return ctx;
|
|
5575
8349
|
}
|
|
5576
8350
|
var createCmd = program.command("create").description("Create a vault");
|
|
5577
|
-
createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").action(
|
|
8351
|
+
createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--two-step", 'Create vault without verifying OTP (verify later with "vultisig verify")').action(
|
|
5578
8352
|
withExit(async (options) => {
|
|
5579
8353
|
const context = await init(program.opts().vault);
|
|
5580
|
-
await executeCreateFast(context, options);
|
|
8354
|
+
await executeCreateFast(context, { ...options, twoStep: options.twoStep });
|
|
5581
8355
|
})
|
|
5582
8356
|
);
|
|
5583
8357
|
createCmd.command("secure").description("Create a secure vault (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").action(
|
|
@@ -5823,6 +8597,16 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
|
|
|
5823
8597
|
});
|
|
5824
8598
|
})
|
|
5825
8599
|
);
|
|
8600
|
+
program.command("tx-status").description("Check the status of a transaction (polls until confirmed)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").action(
|
|
8601
|
+
withExit(async (options) => {
|
|
8602
|
+
const context = await init(program.opts().vault);
|
|
8603
|
+
await executeTxStatus(context, {
|
|
8604
|
+
chain: findChainByName(options.chain) || options.chain,
|
|
8605
|
+
txHash: options.txHash,
|
|
8606
|
+
noWait: !options.wait
|
|
8607
|
+
});
|
|
8608
|
+
})
|
|
8609
|
+
);
|
|
5826
8610
|
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
|
|
5827
8611
|
withExit(async (options) => {
|
|
5828
8612
|
const context = await init(program.opts().vault);
|
|
@@ -6051,14 +8835,56 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
|
|
|
6051
8835
|
}
|
|
6052
8836
|
)
|
|
6053
8837
|
);
|
|
8838
|
+
var agentCmd = program.command("agent").description("AI-powered chat interface for wallet operations").option("--via-agent", "Use NDJSON pipe mode for agent-to-agent communication").option("--verbose", "Show detailed tool call parameters and debug output").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for signing operations").option("--password-ttl <ms>", "Password cache TTL in milliseconds (default: 300000, 86400000/24h for --via-agent)").option("--session-id <id>", "Resume an existing session").action(async (options) => {
|
|
8839
|
+
const MAX_TTL = 864e5;
|
|
8840
|
+
let passwordTTL;
|
|
8841
|
+
if (options.passwordTtl) {
|
|
8842
|
+
const parsed = parseInt(options.passwordTtl, 10);
|
|
8843
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
8844
|
+
throw new Error(`Invalid --password-ttl value: "${options.passwordTtl}". Expected a non-negative integer in milliseconds.`);
|
|
8845
|
+
}
|
|
8846
|
+
passwordTTL = parsed;
|
|
8847
|
+
} else if (options.viaAgent) {
|
|
8848
|
+
passwordTTL = MAX_TTL;
|
|
8849
|
+
}
|
|
8850
|
+
const context = await init(program.opts().vault, options.password, passwordTTL);
|
|
8851
|
+
await executeAgent(context, {
|
|
8852
|
+
viaAgent: options.viaAgent,
|
|
8853
|
+
verbose: options.verbose,
|
|
8854
|
+
backendUrl: options.backendUrl,
|
|
8855
|
+
password: options.password,
|
|
8856
|
+
sessionId: options.sessionId
|
|
8857
|
+
});
|
|
8858
|
+
});
|
|
8859
|
+
var sessionsCmd = agentCmd.command("sessions").description("Manage agent chat sessions");
|
|
8860
|
+
sessionsCmd.command("list").description("List chat sessions for the current vault").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for authentication").action(
|
|
8861
|
+
withExit(async (options) => {
|
|
8862
|
+
const parentOpts = agentCmd.opts();
|
|
8863
|
+
const context = await init(program.opts().vault, options.password || parentOpts.password);
|
|
8864
|
+
await executeAgentSessionsList(context, {
|
|
8865
|
+
backendUrl: options.backendUrl || parentOpts.backendUrl,
|
|
8866
|
+
password: options.password || parentOpts.password
|
|
8867
|
+
});
|
|
8868
|
+
})
|
|
8869
|
+
);
|
|
8870
|
+
sessionsCmd.command("delete <id>").description("Delete a chat session").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for authentication").action(
|
|
8871
|
+
withExit(async (id, options) => {
|
|
8872
|
+
const parentOpts = agentCmd.opts();
|
|
8873
|
+
const context = await init(program.opts().vault, options.password || parentOpts.password);
|
|
8874
|
+
await executeAgentSessionsDelete(context, id, {
|
|
8875
|
+
backendUrl: options.backendUrl || parentOpts.backendUrl,
|
|
8876
|
+
password: options.password || parentOpts.password
|
|
8877
|
+
});
|
|
8878
|
+
})
|
|
8879
|
+
);
|
|
6054
8880
|
program.command("version").description("Show detailed version information").action(
|
|
6055
8881
|
withExit(async () => {
|
|
6056
8882
|
printResult(formatVersionDetailed());
|
|
6057
8883
|
const result = await checkForUpdates();
|
|
6058
8884
|
if (result?.updateAvailable && result.latestVersion) {
|
|
6059
8885
|
info("");
|
|
6060
|
-
info(
|
|
6061
|
-
info(
|
|
8886
|
+
info(chalk15.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
8887
|
+
info(chalk15.gray(`Run "${getUpdateCommand()}" to update`));
|
|
6062
8888
|
}
|
|
6063
8889
|
})
|
|
6064
8890
|
);
|
|
@@ -6067,28 +8893,28 @@ program.command("update").description("Check for updates and show update command
|
|
|
6067
8893
|
info("Checking for updates...");
|
|
6068
8894
|
const result = await checkForUpdates();
|
|
6069
8895
|
if (!result) {
|
|
6070
|
-
printResult(
|
|
8896
|
+
printResult(chalk15.gray("Update checking is disabled"));
|
|
6071
8897
|
return;
|
|
6072
8898
|
}
|
|
6073
8899
|
if (result.updateAvailable && result.latestVersion) {
|
|
6074
8900
|
printResult("");
|
|
6075
|
-
printResult(
|
|
8901
|
+
printResult(chalk15.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
6076
8902
|
printResult("");
|
|
6077
8903
|
if (options.check) {
|
|
6078
8904
|
printResult(`Run "${getUpdateCommand()}" to update`);
|
|
6079
8905
|
} else {
|
|
6080
8906
|
const updateCmd = getUpdateCommand();
|
|
6081
8907
|
printResult(`To update, run:`);
|
|
6082
|
-
printResult(
|
|
8908
|
+
printResult(chalk15.cyan(` ${updateCmd}`));
|
|
6083
8909
|
}
|
|
6084
8910
|
} else {
|
|
6085
|
-
printResult(
|
|
8911
|
+
printResult(chalk15.green(`You're on the latest version (${result.currentVersion})`));
|
|
6086
8912
|
}
|
|
6087
8913
|
})
|
|
6088
8914
|
);
|
|
6089
8915
|
setupCompletionCommand(program);
|
|
6090
8916
|
async function startInteractiveMode() {
|
|
6091
|
-
const sdk = new
|
|
8917
|
+
const sdk = new Vultisig7({
|
|
6092
8918
|
onPasswordRequired: createPasswordCallback()
|
|
6093
8919
|
});
|
|
6094
8920
|
await sdk.initialize();
|
|
@@ -6110,3 +8936,8 @@ if (isInteractiveMode) {
|
|
|
6110
8936
|
} else {
|
|
6111
8937
|
program.parse();
|
|
6112
8938
|
}
|
|
8939
|
+
/*! Bundled license information:
|
|
8940
|
+
|
|
8941
|
+
@noble/hashes/esm/utils.js:
|
|
8942
|
+
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
8943
|
+
*/
|