@vultisig/cli 0.5.0 → 0.7.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 +30 -0
- package/README.md +39 -0
- package/dist/index.js +2934 -258
- 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 chalk14 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,130 +4097,2339 @@ function displayDiscountTier(tierInfo) {
|
|
|
3696
4097
|
printResult("");
|
|
3697
4098
|
}
|
|
3698
4099
|
|
|
3699
|
-
// src/
|
|
3700
|
-
|
|
3701
|
-
import
|
|
3702
|
-
import
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
"
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
"portfolio",
|
|
3719
|
-
"addresses",
|
|
3720
|
-
"chains",
|
|
3721
|
-
"tokens",
|
|
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/agent/auth.ts
|
|
4101
|
+
init_sha3();
|
|
4102
|
+
import { randomBytes } from "node:crypto";
|
|
4103
|
+
import { Chain as Chain7 } from "@vultisig/sdk";
|
|
4104
|
+
async function authenticateVault(client, vault, password, maxAttempts = 3) {
|
|
4105
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
4106
|
+
const chainCode = vault.hexChainCode;
|
|
4107
|
+
const ethAddress = await vault.address(Chain7.Ethereum);
|
|
4108
|
+
const nonce = "0x" + randomBytes(16).toString("hex");
|
|
4109
|
+
const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
4110
|
+
const authMessage = JSON.stringify({
|
|
4111
|
+
message: "Sign into Vultisig Plugin Marketplace",
|
|
4112
|
+
nonce,
|
|
4113
|
+
expiresAt,
|
|
4114
|
+
address: ethAddress
|
|
4115
|
+
});
|
|
4116
|
+
const messageHash = computePersonalSignHash(authMessage);
|
|
4117
|
+
let lastError = null;
|
|
4118
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3744
4119
|
try {
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
const partial = parts.slice(1).join(" ");
|
|
3749
|
-
return completeFilePath(partial, command === "import");
|
|
4120
|
+
if (attempt > 1) {
|
|
4121
|
+
process.stderr.write(` Retry ${attempt}/${maxAttempts}...
|
|
4122
|
+
`);
|
|
3750
4123
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
4124
|
+
const signature = await vault.signBytes(
|
|
4125
|
+
{ data: Buffer.from(messageHash), chain: Chain7.Ethereum },
|
|
4126
|
+
{}
|
|
4127
|
+
);
|
|
4128
|
+
const sigHex = formatSignature65(signature.signature, signature.recovery ?? 0);
|
|
4129
|
+
const authResponse = await client.authenticate({
|
|
4130
|
+
public_key: publicKey,
|
|
4131
|
+
chain_code_hex: chainCode,
|
|
4132
|
+
message: authMessage,
|
|
4133
|
+
signature: sigHex
|
|
4134
|
+
});
|
|
4135
|
+
return {
|
|
4136
|
+
token: authResponse.token,
|
|
4137
|
+
expiresAt: authResponse.expires_at
|
|
4138
|
+
};
|
|
4139
|
+
} catch (err) {
|
|
4140
|
+
lastError = err;
|
|
4141
|
+
if (attempt < maxAttempts && err.message?.includes("timeout")) {
|
|
4142
|
+
continue;
|
|
3754
4143
|
}
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
4144
|
+
throw err;
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
throw lastError || new Error("Authentication failed after all attempts");
|
|
4148
|
+
}
|
|
4149
|
+
function computePersonalSignHash(message) {
|
|
4150
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
4151
|
+
const prefix = `Ethereum Signed Message:
|
|
4152
|
+
${messageBytes.length}`;
|
|
4153
|
+
const prefixBytes = new TextEncoder().encode(prefix);
|
|
4154
|
+
const combined = new Uint8Array(prefixBytes.length + messageBytes.length);
|
|
4155
|
+
combined.set(prefixBytes);
|
|
4156
|
+
combined.set(messageBytes, prefixBytes.length);
|
|
4157
|
+
return keccak_256(combined);
|
|
4158
|
+
}
|
|
4159
|
+
function formatSignature65(sigHex, recovery) {
|
|
4160
|
+
const hex = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex;
|
|
4161
|
+
const bytes = Buffer.from(hex, "hex");
|
|
4162
|
+
if (bytes[0] === 48) {
|
|
4163
|
+
const { r, s } = decodeDERSignature(bytes);
|
|
4164
|
+
const v = (recovery + 27).toString(16).padStart(2, "0");
|
|
4165
|
+
return r + s + v;
|
|
4166
|
+
}
|
|
4167
|
+
if (hex.length >= 128) {
|
|
4168
|
+
const rs = hex.slice(0, 128);
|
|
4169
|
+
const v = (recovery + 27).toString(16).padStart(2, "0");
|
|
4170
|
+
return rs + v;
|
|
4171
|
+
}
|
|
4172
|
+
throw new Error(`Cannot format signature: unrecognized format (${hex.length} hex chars)`);
|
|
4173
|
+
}
|
|
4174
|
+
function decodeDERSignature(der) {
|
|
4175
|
+
let offset = 0;
|
|
4176
|
+
if (der[offset++] !== 48) throw new Error("Invalid DER: expected SEQUENCE");
|
|
4177
|
+
offset++;
|
|
4178
|
+
if (der[offset++] !== 2) throw new Error("Invalid DER: expected INTEGER for r");
|
|
4179
|
+
const rLen = der[offset++];
|
|
4180
|
+
const rBytes = der.subarray(offset, offset + rLen);
|
|
4181
|
+
offset += rLen;
|
|
4182
|
+
if (der[offset++] !== 2) throw new Error("Invalid DER: expected INTEGER for s");
|
|
4183
|
+
const sLen = der[offset++];
|
|
4184
|
+
const sBytes = der.subarray(offset, offset + sLen);
|
|
4185
|
+
const r = padTo32Bytes(stripLeadingZeros(rBytes));
|
|
4186
|
+
const s = padTo32Bytes(stripLeadingZeros(sBytes));
|
|
4187
|
+
return { r, s };
|
|
4188
|
+
}
|
|
4189
|
+
function stripLeadingZeros(buf) {
|
|
4190
|
+
let start = 0;
|
|
4191
|
+
while (start < buf.length - 1 && buf[start] === 0) start++;
|
|
4192
|
+
return Buffer.from(buf.subarray(start));
|
|
4193
|
+
}
|
|
4194
|
+
function padTo32Bytes(buf) {
|
|
4195
|
+
if (buf.length > 32) {
|
|
4196
|
+
return buf.subarray(buf.length - 32).toString("hex");
|
|
4197
|
+
}
|
|
4198
|
+
return buf.toString("hex").padStart(64, "0");
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
// src/agent/client.ts
|
|
4202
|
+
var AgentClient = class {
|
|
4203
|
+
baseUrl;
|
|
4204
|
+
authToken = null;
|
|
4205
|
+
verbose = false;
|
|
4206
|
+
constructor(baseUrl) {
|
|
4207
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
4208
|
+
}
|
|
4209
|
+
setAuthToken(token) {
|
|
4210
|
+
this.authToken = token;
|
|
4211
|
+
}
|
|
4212
|
+
// ============================================================================
|
|
4213
|
+
// Authentication
|
|
4214
|
+
// ============================================================================
|
|
4215
|
+
async authenticate(req) {
|
|
4216
|
+
const res = await fetch(`${this.baseUrl}/auth/token`, {
|
|
4217
|
+
method: "POST",
|
|
4218
|
+
headers: { "Content-Type": "application/json" },
|
|
4219
|
+
body: JSON.stringify(req)
|
|
4220
|
+
});
|
|
4221
|
+
if (!res.ok) {
|
|
4222
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
4223
|
+
throw new Error(`Auth failed (${res.status}): ${body.error || res.statusText}`);
|
|
4224
|
+
}
|
|
4225
|
+
const data = await res.json();
|
|
4226
|
+
this.authToken = data.token;
|
|
4227
|
+
return data;
|
|
4228
|
+
}
|
|
4229
|
+
// ============================================================================
|
|
4230
|
+
// Health
|
|
4231
|
+
// ============================================================================
|
|
4232
|
+
async healthCheck() {
|
|
4233
|
+
try {
|
|
4234
|
+
const res = await fetch(`${this.baseUrl}/healthz`);
|
|
4235
|
+
return res.ok;
|
|
4236
|
+
} catch {
|
|
4237
|
+
return false;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
// ============================================================================
|
|
4241
|
+
// Conversations
|
|
4242
|
+
// ============================================================================
|
|
4243
|
+
async createConversation(publicKey) {
|
|
4244
|
+
const req = { public_key: publicKey };
|
|
4245
|
+
return this.post("/agent/conversations", req);
|
|
4246
|
+
}
|
|
4247
|
+
async listConversations(publicKey, skip = 0, take = 20) {
|
|
4248
|
+
const req = { public_key: publicKey, skip, take };
|
|
4249
|
+
return this.post("/agent/conversations/list", req);
|
|
4250
|
+
}
|
|
4251
|
+
async getConversation(conversationId, publicKey) {
|
|
4252
|
+
const req = { public_key: publicKey };
|
|
4253
|
+
return this.post(`/agent/conversations/${conversationId}`, req);
|
|
4254
|
+
}
|
|
4255
|
+
async deleteConversation(conversationId, publicKey) {
|
|
4256
|
+
await this.delete(`/agent/conversations/${conversationId}`, { public_key: publicKey });
|
|
4257
|
+
}
|
|
4258
|
+
// ============================================================================
|
|
4259
|
+
// Messages - JSON mode
|
|
4260
|
+
// ============================================================================
|
|
4261
|
+
async sendMessage(conversationId, req) {
|
|
4262
|
+
return this.post(`/agent/conversations/${conversationId}/messages`, req);
|
|
4263
|
+
}
|
|
4264
|
+
// ============================================================================
|
|
4265
|
+
// Messages - SSE Streaming mode
|
|
4266
|
+
// ============================================================================
|
|
4267
|
+
async sendMessageStream(conversationId, req, callbacks, signal) {
|
|
4268
|
+
const res = await fetch(`${this.baseUrl}/agent/conversations/${conversationId}/messages`, {
|
|
4269
|
+
method: "POST",
|
|
4270
|
+
headers: {
|
|
4271
|
+
"Content-Type": "application/json",
|
|
4272
|
+
Accept: "text/event-stream",
|
|
4273
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
4274
|
+
},
|
|
4275
|
+
body: JSON.stringify(req),
|
|
4276
|
+
signal
|
|
4277
|
+
});
|
|
4278
|
+
if (!res.ok) {
|
|
4279
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
4280
|
+
throw new Error(`Message failed (${res.status}): ${body.error || res.statusText}`);
|
|
4281
|
+
}
|
|
4282
|
+
if (!res.body) {
|
|
4283
|
+
throw new Error("No response body for SSE stream");
|
|
4284
|
+
}
|
|
4285
|
+
const result = {
|
|
4286
|
+
fullText: "",
|
|
4287
|
+
actions: [],
|
|
4288
|
+
suggestions: [],
|
|
4289
|
+
transactions: [],
|
|
4290
|
+
message: null
|
|
4291
|
+
};
|
|
4292
|
+
const reader = res.body.getReader();
|
|
4293
|
+
const decoder = new TextDecoder();
|
|
4294
|
+
let buffer = "";
|
|
4295
|
+
try {
|
|
4296
|
+
while (true) {
|
|
4297
|
+
const { done, value } = await reader.read();
|
|
4298
|
+
if (done) break;
|
|
4299
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4300
|
+
const lines = buffer.split("\n");
|
|
4301
|
+
buffer = lines.pop() || "";
|
|
4302
|
+
let currentEvent = "";
|
|
4303
|
+
let currentData = "";
|
|
4304
|
+
for (const line of lines) {
|
|
4305
|
+
if (line.startsWith("event: ")) {
|
|
4306
|
+
currentEvent = line.slice(7).trim();
|
|
4307
|
+
} else if (line.startsWith("data: ")) {
|
|
4308
|
+
currentData += (currentData ? "\n" : "") + line.slice(6);
|
|
4309
|
+
} else if (line === "") {
|
|
4310
|
+
if (currentEvent && currentData) {
|
|
4311
|
+
this.handleSSEEvent(currentEvent, currentData, result, callbacks);
|
|
4312
|
+
}
|
|
4313
|
+
currentEvent = "";
|
|
4314
|
+
currentData = "";
|
|
4315
|
+
} else if (line.startsWith(": ")) {
|
|
4316
|
+
}
|
|
3762
4317
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
return completeChainName(lastPart);
|
|
4318
|
+
if (currentEvent && currentData) {
|
|
4319
|
+
this.handleSSEEvent(currentEvent, currentData, result, callbacks);
|
|
3766
4320
|
}
|
|
3767
4321
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
4322
|
+
} finally {
|
|
4323
|
+
reader.releaseLock();
|
|
4324
|
+
}
|
|
4325
|
+
return result;
|
|
4326
|
+
}
|
|
4327
|
+
handleSSEEvent(event, data, result, callbacks) {
|
|
4328
|
+
try {
|
|
4329
|
+
const parsed = JSON.parse(data);
|
|
4330
|
+
switch (event) {
|
|
4331
|
+
case "text_delta":
|
|
4332
|
+
result.fullText += parsed.delta;
|
|
4333
|
+
callbacks.onTextDelta?.(parsed.delta);
|
|
4334
|
+
break;
|
|
4335
|
+
case "tool_progress":
|
|
4336
|
+
if (this.verbose) process.stderr.write(`[SSE:tool_progress] raw: ${data.slice(0, 1e3)}
|
|
4337
|
+
`);
|
|
4338
|
+
callbacks.onToolProgress?.(parsed.tool, parsed.status, parsed.label);
|
|
4339
|
+
break;
|
|
4340
|
+
case "title":
|
|
4341
|
+
callbacks.onTitle?.(parsed.title);
|
|
4342
|
+
break;
|
|
4343
|
+
case "actions":
|
|
4344
|
+
if (this.verbose) process.stderr.write(`[SSE:actions] raw: ${data.slice(0, 1e3)}
|
|
4345
|
+
`);
|
|
4346
|
+
result.actions.push(...parsed.actions || []);
|
|
4347
|
+
callbacks.onActions?.(parsed.actions || []);
|
|
4348
|
+
break;
|
|
4349
|
+
case "suggestions":
|
|
4350
|
+
result.suggestions.push(...parsed.suggestions || []);
|
|
4351
|
+
callbacks.onSuggestions?.(parsed.suggestions || []);
|
|
4352
|
+
break;
|
|
4353
|
+
case "tx_ready":
|
|
4354
|
+
if (this.verbose) process.stderr.write(`[SSE:tx_ready] raw: ${data.slice(0, 2e3)}
|
|
4355
|
+
`);
|
|
4356
|
+
result.transactions.push(parsed);
|
|
4357
|
+
callbacks.onTxReady?.(parsed);
|
|
4358
|
+
break;
|
|
4359
|
+
case "message":
|
|
4360
|
+
result.message = parsed.message || parsed;
|
|
4361
|
+
callbacks.onMessage?.(result.message);
|
|
4362
|
+
break;
|
|
4363
|
+
case "error":
|
|
4364
|
+
callbacks.onError?.(parsed.error);
|
|
4365
|
+
break;
|
|
4366
|
+
case "done":
|
|
4367
|
+
break;
|
|
3785
4368
|
}
|
|
3786
|
-
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
3787
|
-
const show = hits.length ? hits : COMMANDS;
|
|
3788
|
-
return [show, line];
|
|
3789
4369
|
} catch {
|
|
3790
|
-
return [[], line];
|
|
3791
4370
|
}
|
|
4371
|
+
}
|
|
4372
|
+
// ============================================================================
|
|
4373
|
+
// Private helpers
|
|
4374
|
+
// ============================================================================
|
|
4375
|
+
async post(path3, body) {
|
|
4376
|
+
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
4377
|
+
method: "POST",
|
|
4378
|
+
headers: {
|
|
4379
|
+
"Content-Type": "application/json",
|
|
4380
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
4381
|
+
},
|
|
4382
|
+
body: JSON.stringify(body)
|
|
4383
|
+
});
|
|
4384
|
+
if (!res.ok) {
|
|
4385
|
+
const errorBody = await res.json().catch(() => ({ error: res.statusText }));
|
|
4386
|
+
throw new Error(`Request failed (${res.status}): ${errorBody.error || res.statusText}`);
|
|
4387
|
+
}
|
|
4388
|
+
return await res.json();
|
|
4389
|
+
}
|
|
4390
|
+
async delete(path3, body) {
|
|
4391
|
+
const res = await fetch(`${this.baseUrl}${path3}`, {
|
|
4392
|
+
method: "DELETE",
|
|
4393
|
+
headers: {
|
|
4394
|
+
"Content-Type": "application/json",
|
|
4395
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
4396
|
+
},
|
|
4397
|
+
body: JSON.stringify(body)
|
|
4398
|
+
});
|
|
4399
|
+
if (!res.ok) {
|
|
4400
|
+
const errorBody = await res.json().catch(() => ({ error: res.statusText }));
|
|
4401
|
+
throw new Error(`Delete failed (${res.status}): ${errorBody.error || res.statusText}`);
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
};
|
|
4405
|
+
|
|
4406
|
+
// src/agent/context.ts
|
|
4407
|
+
import { Chain as Chain8 } from "@vultisig/sdk";
|
|
4408
|
+
async function buildMessageContext(vault) {
|
|
4409
|
+
const context = {
|
|
4410
|
+
vault_address: vault.publicKeys.ecdsa,
|
|
4411
|
+
vault_name: vault.name
|
|
3792
4412
|
};
|
|
3793
|
-
}
|
|
3794
|
-
function completeFilePath(partial, filterVult) {
|
|
3795
4413
|
try {
|
|
3796
|
-
const
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
if (
|
|
3806
|
-
|
|
3807
|
-
basename = "";
|
|
4414
|
+
const chains = vault.chains;
|
|
4415
|
+
const addressEntries = await Promise.allSettled(
|
|
4416
|
+
chains.map(async (chain) => ({
|
|
4417
|
+
chain: chain.toString(),
|
|
4418
|
+
address: await vault.address(chain)
|
|
4419
|
+
}))
|
|
4420
|
+
);
|
|
4421
|
+
const addresses = {};
|
|
4422
|
+
for (const result of addressEntries) {
|
|
4423
|
+
if (result.status === "fulfilled") {
|
|
4424
|
+
addresses[result.value.chain] = result.value.address;
|
|
3808
4425
|
}
|
|
3809
4426
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
4427
|
+
context.addresses = addresses;
|
|
4428
|
+
} catch {
|
|
4429
|
+
}
|
|
4430
|
+
try {
|
|
4431
|
+
const balanceRecord = await vault.balances();
|
|
4432
|
+
const balanceInfos = [];
|
|
4433
|
+
for (const [key, balance] of Object.entries(balanceRecord)) {
|
|
4434
|
+
balanceInfos.push({
|
|
4435
|
+
chain: balance.chainId || key.split(":")[0] || "",
|
|
4436
|
+
asset: balance.symbol || "",
|
|
4437
|
+
symbol: balance.symbol || "",
|
|
4438
|
+
amount: balance.formattedAmount || balance.amount?.toString() || "0",
|
|
4439
|
+
decimals: balance.decimals || 18
|
|
4440
|
+
});
|
|
3813
4441
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
4442
|
+
context.balances = balanceInfos;
|
|
4443
|
+
} catch {
|
|
4444
|
+
}
|
|
4445
|
+
try {
|
|
4446
|
+
const coins = [];
|
|
4447
|
+
const chains = vault.chains;
|
|
4448
|
+
for (const chain of chains) {
|
|
4449
|
+
coins.push({
|
|
4450
|
+
chain: chain.toString(),
|
|
4451
|
+
ticker: getNativeTokenTicker(chain),
|
|
4452
|
+
is_native_token: true,
|
|
4453
|
+
decimals: getNativeTokenDecimals(chain)
|
|
4454
|
+
});
|
|
4455
|
+
const tokens = vault.tokens[chain] || [];
|
|
4456
|
+
for (const token of tokens) {
|
|
4457
|
+
coins.push({
|
|
4458
|
+
chain: chain.toString(),
|
|
4459
|
+
ticker: token.symbol || "",
|
|
4460
|
+
contract_address: token.contractAddress || token.id,
|
|
4461
|
+
is_native_token: false,
|
|
4462
|
+
decimals: token.decimals || 18
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
context.coins = coins;
|
|
4467
|
+
} catch {
|
|
4468
|
+
}
|
|
4469
|
+
return context;
|
|
4470
|
+
}
|
|
4471
|
+
function getNativeTokenTicker(chain) {
|
|
4472
|
+
const tickers = {
|
|
4473
|
+
[Chain8.Ethereum]: "ETH",
|
|
4474
|
+
[Chain8.Bitcoin]: "BTC",
|
|
4475
|
+
[Chain8.Solana]: "SOL",
|
|
4476
|
+
[Chain8.THORChain]: "RUNE",
|
|
4477
|
+
[Chain8.Cosmos]: "ATOM",
|
|
4478
|
+
[Chain8.Avalanche]: "AVAX",
|
|
4479
|
+
[Chain8.BSC]: "BNB",
|
|
4480
|
+
[Chain8.Polygon]: "MATIC",
|
|
4481
|
+
[Chain8.Arbitrum]: "ETH",
|
|
4482
|
+
[Chain8.Optimism]: "ETH",
|
|
4483
|
+
[Chain8.Base]: "ETH",
|
|
4484
|
+
[Chain8.Blast]: "ETH",
|
|
4485
|
+
[Chain8.Litecoin]: "LTC",
|
|
4486
|
+
[Chain8.Dogecoin]: "DOGE",
|
|
4487
|
+
[Chain8.Dash]: "DASH",
|
|
4488
|
+
[Chain8.MayaChain]: "CACAO",
|
|
4489
|
+
[Chain8.Polkadot]: "DOT",
|
|
4490
|
+
[Chain8.Sui]: "SUI",
|
|
4491
|
+
[Chain8.Ton]: "TON",
|
|
4492
|
+
[Chain8.Tron]: "TRX",
|
|
4493
|
+
[Chain8.Ripple]: "XRP",
|
|
4494
|
+
[Chain8.Dydx]: "DYDX",
|
|
4495
|
+
[Chain8.Osmosis]: "OSMO",
|
|
4496
|
+
[Chain8.Terra]: "LUNA",
|
|
4497
|
+
[Chain8.Noble]: "USDC",
|
|
4498
|
+
[Chain8.Kujira]: "KUJI",
|
|
4499
|
+
[Chain8.Zksync]: "ETH",
|
|
4500
|
+
[Chain8.CronosChain]: "CRO"
|
|
4501
|
+
};
|
|
4502
|
+
return tickers[chain] || chain.toString();
|
|
4503
|
+
}
|
|
4504
|
+
function getNativeTokenDecimals(chain) {
|
|
4505
|
+
const decimals = {
|
|
4506
|
+
[Chain8.Bitcoin]: 8,
|
|
4507
|
+
[Chain8.Litecoin]: 8,
|
|
4508
|
+
[Chain8.Dogecoin]: 8,
|
|
4509
|
+
[Chain8.Dash]: 8,
|
|
4510
|
+
[Chain8.Solana]: 9,
|
|
4511
|
+
[Chain8.Sui]: 9,
|
|
4512
|
+
[Chain8.Ton]: 9,
|
|
4513
|
+
[Chain8.Polkadot]: 10,
|
|
4514
|
+
[Chain8.Cosmos]: 6,
|
|
4515
|
+
[Chain8.THORChain]: 8,
|
|
4516
|
+
[Chain8.MayaChain]: 10,
|
|
4517
|
+
[Chain8.Osmosis]: 6,
|
|
4518
|
+
[Chain8.Dydx]: 18,
|
|
4519
|
+
[Chain8.Tron]: 6,
|
|
4520
|
+
[Chain8.Ripple]: 6,
|
|
4521
|
+
[Chain8.Noble]: 6,
|
|
4522
|
+
[Chain8.Kujira]: 6,
|
|
4523
|
+
[Chain8.Terra]: 6
|
|
4524
|
+
};
|
|
4525
|
+
return decimals[chain] || 18;
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
// src/agent/executor.ts
|
|
4529
|
+
import { Chain as Chain9, Vultisig as Vultisig6 } from "@vultisig/sdk";
|
|
4530
|
+
|
|
4531
|
+
// src/agent/types.ts
|
|
4532
|
+
var AUTO_EXECUTE_ACTIONS = /* @__PURE__ */ new Set([
|
|
4533
|
+
"add_chain",
|
|
4534
|
+
"add_coin",
|
|
4535
|
+
"remove_coin",
|
|
4536
|
+
"remove_chain",
|
|
4537
|
+
"address_book_add",
|
|
4538
|
+
"address_book_remove",
|
|
4539
|
+
"get_address_book",
|
|
4540
|
+
"get_market_price",
|
|
4541
|
+
"get_balances",
|
|
4542
|
+
"get_portfolio",
|
|
4543
|
+
"search_token",
|
|
4544
|
+
"list_vaults",
|
|
4545
|
+
"build_swap_tx",
|
|
4546
|
+
"build_send_tx",
|
|
4547
|
+
"build_custom_tx",
|
|
4548
|
+
"build_tx",
|
|
4549
|
+
"sign_tx",
|
|
4550
|
+
"sign_typed_data",
|
|
4551
|
+
"read_evm_contract",
|
|
4552
|
+
"scan_tx",
|
|
4553
|
+
"thorchain_query"
|
|
4554
|
+
]);
|
|
4555
|
+
var PASSWORD_REQUIRED_ACTIONS = /* @__PURE__ */ new Set(["sign_tx", "sign_typed_data", "build_custom_tx"]);
|
|
4556
|
+
|
|
4557
|
+
// src/agent/executor.ts
|
|
4558
|
+
var AgentExecutor = class {
|
|
4559
|
+
vault;
|
|
4560
|
+
pendingPayloads = /* @__PURE__ */ new Map();
|
|
4561
|
+
password = null;
|
|
4562
|
+
verbose;
|
|
4563
|
+
constructor(vault, verbose = false) {
|
|
4564
|
+
this.vault = vault;
|
|
4565
|
+
this.verbose = verbose;
|
|
4566
|
+
}
|
|
4567
|
+
setPassword(password) {
|
|
4568
|
+
this.password = password;
|
|
4569
|
+
}
|
|
4570
|
+
/**
|
|
4571
|
+
* Store a server-built transaction (from tx_ready SSE event).
|
|
4572
|
+
* This allows sign_tx to find and sign it when the backend requests signing.
|
|
4573
|
+
*/
|
|
4574
|
+
storeServerTransaction(txReadyData) {
|
|
4575
|
+
if (this.verbose) process.stderr.write(`[executor] storeServerTransaction called, keys: ${Object.keys(txReadyData || {}).join(",")}
|
|
4576
|
+
`);
|
|
4577
|
+
const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
|
|
4578
|
+
if (!swapTx) {
|
|
4579
|
+
if (this.verbose) process.stderr.write(`[executor] storeServerTransaction: no swap_tx/send_tx/tx found in data
|
|
4580
|
+
`);
|
|
4581
|
+
return;
|
|
4582
|
+
}
|
|
4583
|
+
const chain = resolveChainFromTxReady(txReadyData) || Chain9.Ethereum;
|
|
4584
|
+
this.pendingPayloads.set("latest", {
|
|
4585
|
+
payload: { __serverTx: true, ...txReadyData },
|
|
4586
|
+
coin: { chain, address: "", decimals: 18, ticker: "" },
|
|
4587
|
+
chain,
|
|
4588
|
+
timestamp: Date.now()
|
|
4589
|
+
});
|
|
4590
|
+
if (this.verbose) process.stderr.write(`[executor] Stored server tx for chain ${chain}, pendingPayloads size=${this.pendingPayloads.size}
|
|
4591
|
+
`);
|
|
4592
|
+
}
|
|
4593
|
+
hasPendingTransaction() {
|
|
4594
|
+
return this.pendingPayloads.has("latest");
|
|
4595
|
+
}
|
|
4596
|
+
shouldAutoExecute(action) {
|
|
4597
|
+
return action.auto_execute === true || AUTO_EXECUTE_ACTIONS.has(action.type);
|
|
4598
|
+
}
|
|
4599
|
+
requiresPassword(action) {
|
|
4600
|
+
return PASSWORD_REQUIRED_ACTIONS.has(action.type);
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Execute a single action and return the result.
|
|
4604
|
+
*/
|
|
4605
|
+
async executeAction(action) {
|
|
4606
|
+
try {
|
|
4607
|
+
const data = await this.dispatch(action);
|
|
4608
|
+
return {
|
|
4609
|
+
action: action.type,
|
|
4610
|
+
action_id: action.id,
|
|
4611
|
+
success: true,
|
|
4612
|
+
data
|
|
4613
|
+
};
|
|
4614
|
+
} catch (err) {
|
|
4615
|
+
return {
|
|
4616
|
+
action: action.type,
|
|
4617
|
+
action_id: action.id,
|
|
4618
|
+
success: false,
|
|
4619
|
+
error: err.message || String(err)
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
async dispatch(action) {
|
|
4624
|
+
if (this.verbose) process.stderr.write(`[dispatch] action.type=${action.type} action.id=${action.id}
|
|
4625
|
+
`);
|
|
4626
|
+
const params = action.params || {};
|
|
4627
|
+
switch (action.type) {
|
|
4628
|
+
case "get_balances":
|
|
4629
|
+
return this.getBalances(params);
|
|
4630
|
+
case "get_portfolio":
|
|
4631
|
+
return this.getPortfolio(params);
|
|
4632
|
+
case "add_chain":
|
|
4633
|
+
return this.addChain(params);
|
|
4634
|
+
case "remove_chain":
|
|
4635
|
+
return this.removeChain(params);
|
|
4636
|
+
case "add_coin":
|
|
4637
|
+
return this.addCoin(params);
|
|
4638
|
+
case "remove_coin":
|
|
4639
|
+
return this.removeCoin(params);
|
|
4640
|
+
case "build_send_tx":
|
|
4641
|
+
return this.buildSendTx(params);
|
|
4642
|
+
case "build_swap_tx":
|
|
4643
|
+
return this.buildSwapTx(params);
|
|
4644
|
+
case "build_tx":
|
|
4645
|
+
case "build_custom_tx":
|
|
4646
|
+
return this.buildTx(params);
|
|
4647
|
+
case "sign_tx":
|
|
4648
|
+
return this.signTx(params);
|
|
4649
|
+
case "get_address_book":
|
|
4650
|
+
return this.getAddressBook();
|
|
4651
|
+
case "address_book_add":
|
|
4652
|
+
return this.addAddressBookEntry(params);
|
|
4653
|
+
case "address_book_remove":
|
|
4654
|
+
return this.removeAddressBookEntry(params);
|
|
4655
|
+
case "search_token":
|
|
4656
|
+
return this.searchToken(params);
|
|
4657
|
+
case "list_vaults":
|
|
4658
|
+
return this.listVaults();
|
|
4659
|
+
case "sign_typed_data":
|
|
4660
|
+
return this.signTypedData(params);
|
|
4661
|
+
case "scan_tx":
|
|
4662
|
+
return this.scanTx(params);
|
|
4663
|
+
case "read_evm_contract":
|
|
4664
|
+
return this.readEvmContract(params);
|
|
4665
|
+
default:
|
|
4666
|
+
return { message: `Action type '${action.type}' not implemented locally` };
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
// ============================================================================
|
|
4670
|
+
// Balance & Portfolio
|
|
4671
|
+
// ============================================================================
|
|
4672
|
+
async getBalances(params) {
|
|
4673
|
+
const chainFilter = params.chain;
|
|
4674
|
+
const tickerFilter = params.ticker;
|
|
4675
|
+
const balanceRecord = await this.vault.balances();
|
|
4676
|
+
let entries = Object.entries(balanceRecord).map(([key, b]) => ({
|
|
4677
|
+
chain: b.chainId || key.split(":")[0] || "",
|
|
4678
|
+
symbol: b.symbol || "",
|
|
4679
|
+
amount: b.formattedAmount || b.amount?.toString() || "0",
|
|
4680
|
+
decimals: b.decimals || 18,
|
|
4681
|
+
raw_amount: b.amount?.toString()
|
|
4682
|
+
}));
|
|
4683
|
+
if (chainFilter) {
|
|
4684
|
+
const chain = resolveChain(chainFilter);
|
|
4685
|
+
if (chain) {
|
|
4686
|
+
entries = entries.filter((b) => b.chain.toLowerCase() === chain.toLowerCase());
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (tickerFilter) {
|
|
4690
|
+
entries = entries.filter((b) => b.symbol.toLowerCase() === tickerFilter.toLowerCase());
|
|
4691
|
+
}
|
|
4692
|
+
return { balances: entries };
|
|
4693
|
+
}
|
|
4694
|
+
async getPortfolio(_params) {
|
|
4695
|
+
const balanceRecord = await this.vault.balances();
|
|
4696
|
+
const entries = Object.entries(balanceRecord).map(([key, b]) => ({
|
|
4697
|
+
chain: b.chainId || key.split(":")[0] || "",
|
|
4698
|
+
symbol: b.symbol || "",
|
|
4699
|
+
amount: b.formattedAmount || b.amount?.toString() || "0",
|
|
4700
|
+
decimals: b.decimals || 18
|
|
4701
|
+
}));
|
|
4702
|
+
return { balances: entries };
|
|
4703
|
+
}
|
|
4704
|
+
// ============================================================================
|
|
4705
|
+
// Chain & Token Management
|
|
4706
|
+
// ============================================================================
|
|
4707
|
+
async addChain(params) {
|
|
4708
|
+
const chains = params.chains;
|
|
4709
|
+
if (chains && Array.isArray(chains)) {
|
|
4710
|
+
const results = [];
|
|
4711
|
+
for (const c of chains) {
|
|
4712
|
+
const name = typeof c === "string" ? c : c.chain;
|
|
4713
|
+
const chain2 = resolveChain(name);
|
|
4714
|
+
if (!chain2) throw new Error(`Unknown chain: ${name}`);
|
|
4715
|
+
await this.vault.addChain(chain2);
|
|
4716
|
+
const address2 = await this.vault.address(chain2);
|
|
4717
|
+
results.push({ chain: chain2.toString(), address: address2 });
|
|
4718
|
+
}
|
|
4719
|
+
return { added: results };
|
|
4720
|
+
}
|
|
4721
|
+
const chainName = params.chain;
|
|
4722
|
+
const chain = resolveChain(chainName);
|
|
4723
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4724
|
+
await this.vault.addChain(chain);
|
|
4725
|
+
const address = await this.vault.address(chain);
|
|
4726
|
+
return { chain: chain.toString(), address, added: true };
|
|
4727
|
+
}
|
|
4728
|
+
async removeChain(params) {
|
|
4729
|
+
const chainName = params.chain;
|
|
4730
|
+
const chain = resolveChain(chainName);
|
|
4731
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4732
|
+
await this.vault.removeChain(chain);
|
|
4733
|
+
return { chain: chain.toString(), removed: true };
|
|
4734
|
+
}
|
|
4735
|
+
async addCoin(params) {
|
|
4736
|
+
const tokens = params.tokens;
|
|
4737
|
+
if (tokens && Array.isArray(tokens)) {
|
|
4738
|
+
const results = [];
|
|
4739
|
+
for (const t of tokens) {
|
|
4740
|
+
const chain2 = resolveChain(t.chain);
|
|
4741
|
+
if (!chain2) throw new Error(`Unknown chain: ${t.chain}`);
|
|
4742
|
+
const symbol2 = t.symbol || t.ticker || "";
|
|
4743
|
+
await this.vault.addToken(chain2, {
|
|
4744
|
+
id: t.contract_address || t.contractAddress || "",
|
|
4745
|
+
symbol: symbol2,
|
|
4746
|
+
name: t.name || symbol2,
|
|
4747
|
+
decimals: t.decimals || 18,
|
|
4748
|
+
contractAddress: t.contract_address || t.contractAddress,
|
|
4749
|
+
chainId: chain2.toString()
|
|
4750
|
+
});
|
|
4751
|
+
results.push({ chain: chain2.toString(), symbol: symbol2 });
|
|
4752
|
+
}
|
|
4753
|
+
return { added: results };
|
|
4754
|
+
}
|
|
4755
|
+
const chainName = params.chain;
|
|
4756
|
+
const chain = resolveChain(chainName);
|
|
4757
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4758
|
+
const symbol = params.symbol || params.ticker;
|
|
4759
|
+
await this.vault.addToken(chain, {
|
|
4760
|
+
id: params.contract_address || params.contractAddress || "",
|
|
4761
|
+
symbol,
|
|
4762
|
+
name: params.name || symbol,
|
|
4763
|
+
decimals: params.decimals || 18,
|
|
4764
|
+
contractAddress: params.contract_address || params.contractAddress,
|
|
4765
|
+
chainId: chain.toString()
|
|
4766
|
+
});
|
|
4767
|
+
return { chain: chain.toString(), symbol, added: true };
|
|
4768
|
+
}
|
|
4769
|
+
async removeCoin(params) {
|
|
4770
|
+
const chainName = params.chain;
|
|
4771
|
+
const chain = resolveChain(chainName);
|
|
4772
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4773
|
+
const tokenId = params.token_id || params.id || params.contract_address;
|
|
4774
|
+
await this.vault.removeToken(chain, tokenId);
|
|
4775
|
+
return { chain: chain.toString(), removed: true };
|
|
4776
|
+
}
|
|
4777
|
+
// ============================================================================
|
|
4778
|
+
// Transaction Building
|
|
4779
|
+
// ============================================================================
|
|
4780
|
+
async buildSendTx(params) {
|
|
4781
|
+
const chainName = params.chain || params.from_chain;
|
|
4782
|
+
const chain = resolveChain(chainName);
|
|
4783
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4784
|
+
const symbol = params.symbol || params.ticker;
|
|
4785
|
+
const toAddress = params.address || params.to || params.destination;
|
|
4786
|
+
const amountStr = params.amount;
|
|
4787
|
+
if (!toAddress) throw new Error("Destination address is required");
|
|
4788
|
+
if (!amountStr) throw new Error("Amount is required");
|
|
4789
|
+
const address = await this.vault.address(chain);
|
|
4790
|
+
const balance = await this.vault.balance(chain, params.token_id);
|
|
4791
|
+
const coin = {
|
|
4792
|
+
chain,
|
|
4793
|
+
address,
|
|
4794
|
+
decimals: balance.decimals,
|
|
4795
|
+
ticker: symbol || balance.symbol,
|
|
4796
|
+
id: params.token_id
|
|
4797
|
+
};
|
|
4798
|
+
const amount = parseAmount(amountStr, balance.decimals);
|
|
4799
|
+
const memo = params.memo;
|
|
4800
|
+
const payload = await this.vault.prepareSendTx({ coin, receiver: toAddress, amount, memo });
|
|
4801
|
+
const payloadId = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
4802
|
+
this.pendingPayloads.set(payloadId, { payload, coin, chain, timestamp: Date.now() });
|
|
4803
|
+
this.pendingPayloads.set("latest", { payload, coin, chain, timestamp: Date.now() });
|
|
4804
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
4805
|
+
return {
|
|
4806
|
+
keysign_payload: payloadId,
|
|
4807
|
+
from_chain: chain.toString(),
|
|
4808
|
+
from_symbol: coin.ticker,
|
|
4809
|
+
amount: amountStr,
|
|
4810
|
+
sender: address,
|
|
4811
|
+
destination: toAddress,
|
|
4812
|
+
memo: memo || void 0,
|
|
4813
|
+
message_hashes: messageHashes,
|
|
4814
|
+
tx_details: {
|
|
4815
|
+
chain: chain.toString(),
|
|
4816
|
+
from: address,
|
|
4817
|
+
to: toAddress,
|
|
4818
|
+
amount: amountStr,
|
|
4819
|
+
symbol: coin.ticker
|
|
4820
|
+
}
|
|
4821
|
+
};
|
|
4822
|
+
}
|
|
4823
|
+
async buildSwapTx(params) {
|
|
4824
|
+
if (this.verbose) process.stderr.write(`[build_swap_tx] called with params: ${JSON.stringify(params).slice(0, 500)}
|
|
4825
|
+
`);
|
|
4826
|
+
const fromChainName = params.from_chain || params.chain;
|
|
4827
|
+
const toChainName = params.to_chain;
|
|
4828
|
+
const fromChain = resolveChain(fromChainName);
|
|
4829
|
+
const toChain = toChainName ? resolveChain(toChainName) : null;
|
|
4830
|
+
if (!fromChain) throw new Error(`Unknown from_chain: ${fromChainName}`);
|
|
4831
|
+
const amountStr = params.amount;
|
|
4832
|
+
const fromSymbol = params.from_symbol || params.from_token || "";
|
|
4833
|
+
const toSymbol = params.to_symbol || params.to_token || "";
|
|
4834
|
+
const fromToken = params.from_contract || params.from_token_id;
|
|
4835
|
+
const toToken = params.to_contract || params.to_token_id;
|
|
4836
|
+
const fromCoin = { chain: fromChain, token: fromToken || void 0 };
|
|
4837
|
+
const toCoin = { chain: toChain || fromChain, token: toToken || void 0 };
|
|
4838
|
+
const quote = await this.vault.getSwapQuote({
|
|
4839
|
+
fromCoin,
|
|
4840
|
+
toCoin,
|
|
4841
|
+
amount: parseFloat(amountStr)
|
|
4842
|
+
});
|
|
4843
|
+
const swapResult = await this.vault.prepareSwapTx({
|
|
4844
|
+
fromCoin,
|
|
4845
|
+
toCoin,
|
|
4846
|
+
amount: parseFloat(amountStr),
|
|
4847
|
+
swapQuote: quote,
|
|
4848
|
+
autoApprove: true
|
|
4849
|
+
});
|
|
4850
|
+
const chain = fromChain;
|
|
4851
|
+
const payload = swapResult.keysignPayload;
|
|
4852
|
+
const payloadId = `swap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
4853
|
+
this.pendingPayloads.set(payloadId, { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
|
|
4854
|
+
this.pendingPayloads.set("latest", { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
|
|
4855
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
4856
|
+
return {
|
|
4857
|
+
keysign_payload: payloadId,
|
|
4858
|
+
from_chain: fromChain.toString(),
|
|
4859
|
+
to_chain: (toChain || fromChain).toString(),
|
|
4860
|
+
from_symbol: fromSymbol,
|
|
4861
|
+
to_symbol: toSymbol,
|
|
4862
|
+
amount: amountStr,
|
|
4863
|
+
estimated_output: quote.estimatedOutput?.toString(),
|
|
4864
|
+
provider: quote.provider,
|
|
4865
|
+
message_hashes: messageHashes
|
|
4866
|
+
};
|
|
4867
|
+
}
|
|
4868
|
+
async buildTx(params) {
|
|
4869
|
+
if (params.function_name && params.contract_address) {
|
|
4870
|
+
return this.buildContractCallTx(params);
|
|
4871
|
+
}
|
|
4872
|
+
if (params.data || params.calldata || params.hex_payload) {
|
|
4873
|
+
const txData = {
|
|
4874
|
+
to: params.to || params.address || params.contract,
|
|
4875
|
+
value: params.value || "0",
|
|
4876
|
+
data: params.data || params.calldata || params.hex_payload,
|
|
4877
|
+
chain: params.chain,
|
|
4878
|
+
chain_id: params.chain_id
|
|
4879
|
+
};
|
|
4880
|
+
this.storeServerTransaction({
|
|
4881
|
+
tx: txData,
|
|
4882
|
+
chain: params.chain,
|
|
4883
|
+
from_chain: params.chain
|
|
4884
|
+
});
|
|
4885
|
+
const chain = resolveChain(params.chain) || Chain9.Ethereum;
|
|
4886
|
+
const address = await this.vault.address(chain);
|
|
4887
|
+
return {
|
|
4888
|
+
status: "ready",
|
|
4889
|
+
chain: chain.toString(),
|
|
4890
|
+
from: address,
|
|
4891
|
+
to: txData.to,
|
|
4892
|
+
value: txData.value,
|
|
4893
|
+
has_calldata: true,
|
|
4894
|
+
message: "Transaction built. Ready to sign."
|
|
4895
|
+
};
|
|
4896
|
+
}
|
|
4897
|
+
return this.buildSendTx(params);
|
|
4898
|
+
}
|
|
4899
|
+
/**
|
|
4900
|
+
* Build, sign, and broadcast an EVM contract call transaction from structured params.
|
|
4901
|
+
* Encodes function_name + typed params into ABI calldata, then signs via signServerTx.
|
|
4902
|
+
*/
|
|
4903
|
+
async buildContractCallTx(params) {
|
|
4904
|
+
const chainName = params.chain || "Ethereum";
|
|
4905
|
+
const chain = resolveChain(chainName);
|
|
4906
|
+
if (!chain) throw new Error(`Unknown chain: ${chainName}`);
|
|
4907
|
+
const contractAddress = params.contract_address;
|
|
4908
|
+
const functionName = params.function_name;
|
|
4909
|
+
const typedParams = params.params;
|
|
4910
|
+
const value = params.value || "0";
|
|
4911
|
+
const calldata = await encodeContractCall(functionName, typedParams || []);
|
|
4912
|
+
if (this.verbose) process.stderr.write(`[build_contract_tx] ${functionName}(${(typedParams || []).map((p) => p.type).join(",")}) on ${contractAddress} chain=${chain}
|
|
4913
|
+
`);
|
|
4914
|
+
const serverTxData = {
|
|
4915
|
+
__serverTx: true,
|
|
4916
|
+
tx: {
|
|
4917
|
+
to: contractAddress,
|
|
4918
|
+
value,
|
|
4919
|
+
data: calldata
|
|
4920
|
+
},
|
|
4921
|
+
chain: chainName,
|
|
4922
|
+
from_chain: chainName
|
|
4923
|
+
};
|
|
4924
|
+
this.pendingPayloads.set("latest", {
|
|
4925
|
+
payload: serverTxData,
|
|
4926
|
+
coin: { chain, address: "", decimals: 18, ticker: "" },
|
|
4927
|
+
chain,
|
|
4928
|
+
timestamp: Date.now()
|
|
4929
|
+
});
|
|
4930
|
+
return this.signServerTx(serverTxData, chain, { chain: chainName });
|
|
4931
|
+
}
|
|
4932
|
+
// ============================================================================
|
|
4933
|
+
// Transaction Signing
|
|
4934
|
+
// ============================================================================
|
|
4935
|
+
async signTx(params) {
|
|
4936
|
+
if (this.verbose) process.stderr.write(`[sign_tx] params: ${JSON.stringify(params).slice(0, 500)}
|
|
4937
|
+
`);
|
|
4938
|
+
if (this.verbose) process.stderr.write(`[sign_tx] pendingPayloads keys: ${[...this.pendingPayloads.keys()].join(", ")}
|
|
4939
|
+
`);
|
|
4940
|
+
const payloadId = params.keysign_payload || params.payload_id || "latest";
|
|
4941
|
+
const stored = this.pendingPayloads.get(payloadId);
|
|
4942
|
+
if (!stored) {
|
|
4943
|
+
throw new Error("No pending transaction to sign. Build a transaction first.");
|
|
4944
|
+
}
|
|
4945
|
+
const { payload, chain } = stored;
|
|
4946
|
+
if (payload.__serverTx) {
|
|
4947
|
+
return this.signServerTx(payload, chain, params);
|
|
4948
|
+
}
|
|
4949
|
+
return this.signSdkTx(payload, chain, payloadId);
|
|
4950
|
+
}
|
|
4951
|
+
/**
|
|
4952
|
+
* Sign and broadcast an SDK-built transaction (keysign payload from local build methods).
|
|
4953
|
+
*/
|
|
4954
|
+
async signSdkTx(payload, chain, payloadId) {
|
|
4955
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
4956
|
+
if (this.password) {
|
|
4957
|
+
await this.vault.unlock?.(this.password);
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
4961
|
+
const signature = await this.vault.sign(
|
|
4962
|
+
{
|
|
4963
|
+
transaction: payload,
|
|
4964
|
+
chain: payload.coin?.chain || chain,
|
|
4965
|
+
messageHashes
|
|
4966
|
+
},
|
|
4967
|
+
{}
|
|
4968
|
+
);
|
|
4969
|
+
const txHash = await this.vault.broadcastTx({
|
|
4970
|
+
chain,
|
|
4971
|
+
keysignPayload: payload,
|
|
4972
|
+
signature
|
|
4973
|
+
});
|
|
4974
|
+
this.pendingPayloads.delete(payloadId);
|
|
4975
|
+
if (payloadId !== "latest") {
|
|
4976
|
+
this.pendingPayloads.delete("latest");
|
|
4977
|
+
}
|
|
4978
|
+
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
4979
|
+
return {
|
|
4980
|
+
tx_hash: txHash,
|
|
4981
|
+
chain: chain.toString(),
|
|
4982
|
+
status: "pending",
|
|
4983
|
+
explorer_url: explorerUrl
|
|
4984
|
+
};
|
|
4985
|
+
}
|
|
4986
|
+
/**
|
|
4987
|
+
* Sign and broadcast a server-built transaction (raw EVM tx from tx_ready SSE).
|
|
4988
|
+
* Uses vault.prepareSendTx with memo field to carry the calldata.
|
|
4989
|
+
*/
|
|
4990
|
+
async signServerTx(serverTxData, defaultChain, params) {
|
|
4991
|
+
const swapTx = serverTxData.swap_tx || serverTxData.send_tx || serverTxData.tx;
|
|
4992
|
+
if (!swapTx?.to) {
|
|
4993
|
+
throw new Error("Server transaction missing required fields (to)");
|
|
4994
|
+
}
|
|
4995
|
+
const chainName = params.chain || serverTxData.chain || serverTxData.from_chain;
|
|
4996
|
+
const chainId = serverTxData.chain_id || swapTx.chainId;
|
|
4997
|
+
let chain = defaultChain;
|
|
4998
|
+
if (chainName) {
|
|
4999
|
+
chain = resolveChain(chainName) || defaultChain;
|
|
5000
|
+
} else if (chainId) {
|
|
5001
|
+
chain = resolveChainId(chainId) || defaultChain;
|
|
5002
|
+
}
|
|
5003
|
+
const address = await this.vault.address(chain);
|
|
5004
|
+
const balance = await this.vault.balance(chain);
|
|
5005
|
+
const coin = {
|
|
5006
|
+
chain,
|
|
5007
|
+
address,
|
|
5008
|
+
decimals: balance.decimals || 18,
|
|
5009
|
+
ticker: balance.symbol || chain.toString()
|
|
5010
|
+
};
|
|
5011
|
+
const amount = BigInt(swapTx.value || "0");
|
|
5012
|
+
const hasCalldata = !!(swapTx.data && swapTx.data !== "0x");
|
|
5013
|
+
if (this.verbose) process.stderr.write(`[sign_server_tx] chain=${chain}, to=${swapTx.to}, value=${swapTx.value}, amount=${amount}, hasCalldata=${hasCalldata}
|
|
5014
|
+
`);
|
|
5015
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5016
|
+
if (this.password) {
|
|
5017
|
+
await this.vault.unlock?.(this.password);
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
const buildAmount = amount === 0n && hasCalldata ? 1n : amount;
|
|
5021
|
+
const keysignPayload = await this.vault.prepareSendTx({
|
|
5022
|
+
coin,
|
|
5023
|
+
receiver: swapTx.to,
|
|
5024
|
+
amount: buildAmount,
|
|
5025
|
+
memo: swapTx.data
|
|
5026
|
+
});
|
|
5027
|
+
if (amount === 0n && hasCalldata) {
|
|
5028
|
+
;
|
|
5029
|
+
keysignPayload.toAmount = "0";
|
|
5030
|
+
}
|
|
5031
|
+
const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
|
|
5032
|
+
const signature = await this.vault.sign(
|
|
5033
|
+
{
|
|
5034
|
+
transaction: keysignPayload,
|
|
5035
|
+
chain,
|
|
5036
|
+
messageHashes
|
|
5037
|
+
},
|
|
5038
|
+
{}
|
|
5039
|
+
);
|
|
5040
|
+
const txHash = await this.vault.broadcastTx({
|
|
5041
|
+
chain,
|
|
5042
|
+
keysignPayload,
|
|
5043
|
+
signature
|
|
5044
|
+
});
|
|
5045
|
+
this.pendingPayloads.delete("latest");
|
|
5046
|
+
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
5047
|
+
return {
|
|
5048
|
+
tx_hash: txHash,
|
|
5049
|
+
chain: chain.toString(),
|
|
5050
|
+
status: "pending",
|
|
5051
|
+
explorer_url: explorerUrl
|
|
5052
|
+
};
|
|
5053
|
+
}
|
|
5054
|
+
// ============================================================================
|
|
5055
|
+
// EIP-712 Typed Data Signing
|
|
5056
|
+
// ============================================================================
|
|
5057
|
+
/**
|
|
5058
|
+
* Sign EIP-712 typed data. Computes the EIP-712 hash and signs with vault.signBytes().
|
|
5059
|
+
* Supports two formats:
|
|
5060
|
+
* - Flat: { domain, types, message, primaryType } — single typed data
|
|
5061
|
+
* - Payloads array: { payloads: [{id, domain, types, message, primaryType, chain}, ...] }
|
|
5062
|
+
* Used by Polymarket which requires signing both an Order and a ClobAuth.
|
|
5063
|
+
*/
|
|
5064
|
+
async signTypedData(params) {
|
|
5065
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5066
|
+
if (this.password) {
|
|
5067
|
+
await this.vault.unlock?.(this.password);
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
const payloads = params.payloads;
|
|
5071
|
+
if (payloads && Array.isArray(payloads)) {
|
|
5072
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] payloads mode, ${payloads.length} items
|
|
5073
|
+
`);
|
|
5074
|
+
const signatures = [];
|
|
5075
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
5076
|
+
const payload = payloads[i];
|
|
5077
|
+
const id = payload.id || payload.name || "default";
|
|
5078
|
+
if (i > 0) {
|
|
5079
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] waiting 5s between MPC sessions...
|
|
5080
|
+
`);
|
|
5081
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
5082
|
+
}
|
|
5083
|
+
const sig = await this.signSingleTypedData(payload);
|
|
5084
|
+
signatures.push({ id, ...sig });
|
|
5085
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] signed payload "${id}"
|
|
5086
|
+
`);
|
|
5087
|
+
}
|
|
5088
|
+
return {
|
|
5089
|
+
signatures,
|
|
5090
|
+
pm_order_ref: params.pm_order_ref,
|
|
5091
|
+
auto_submit: !!(params.__pm_auto_submit || params.auto_submit)
|
|
5092
|
+
};
|
|
5093
|
+
}
|
|
5094
|
+
return this.signSingleTypedData(params);
|
|
5095
|
+
}
|
|
5096
|
+
/**
|
|
5097
|
+
* Sign a single EIP-712 typed data object.
|
|
5098
|
+
*/
|
|
5099
|
+
async signSingleTypedData(params) {
|
|
5100
|
+
const domain = params.domain;
|
|
5101
|
+
const types = params.types;
|
|
5102
|
+
const message = params.message;
|
|
5103
|
+
const primaryType = params.primaryType || params.primary_type;
|
|
5104
|
+
if (!domain || !types || !message || !primaryType) {
|
|
5105
|
+
throw new Error("sign_typed_data requires domain, types, message, and primaryType");
|
|
5106
|
+
}
|
|
5107
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] primaryType=${primaryType} domain.name=${domain.name}
|
|
5108
|
+
`);
|
|
5109
|
+
const eip712Hash = await computeEIP712Hash(domain, types, primaryType, message);
|
|
5110
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] hash=${eip712Hash}
|
|
5111
|
+
`);
|
|
5112
|
+
const chainName = params.chain;
|
|
5113
|
+
const chainId = domain.chainId;
|
|
5114
|
+
let chain = Chain9.Ethereum;
|
|
5115
|
+
if (chainName) {
|
|
5116
|
+
chain = resolveChain(chainName) || Chain9.Ethereum;
|
|
5117
|
+
} else if (chainId) {
|
|
5118
|
+
chain = resolveChainId(chainId) || Chain9.Ethereum;
|
|
5119
|
+
}
|
|
5120
|
+
const sigResult = await this.vault.signBytes({
|
|
5121
|
+
data: eip712Hash,
|
|
5122
|
+
chain
|
|
5123
|
+
});
|
|
5124
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] signed, format=${sigResult.format}, recovery=${sigResult.recovery}
|
|
5125
|
+
`);
|
|
5126
|
+
const { r, s } = parseDERSignature(sigResult.signature);
|
|
5127
|
+
const v = (sigResult.recovery ?? 0) + 27;
|
|
5128
|
+
const ethSignature = "0x" + r + s + v.toString(16).padStart(2, "0");
|
|
5129
|
+
if (this.verbose) process.stderr.write(`[sign_typed_data] r=${r.slice(0, 16)}... s=${s.slice(0, 16)}... v=${v}
|
|
5130
|
+
`);
|
|
5131
|
+
return {
|
|
5132
|
+
signature: ethSignature,
|
|
5133
|
+
r: "0x" + r,
|
|
5134
|
+
s: "0x" + s,
|
|
5135
|
+
v,
|
|
5136
|
+
recovery: sigResult.recovery,
|
|
5137
|
+
hash: eip712Hash
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
// ============================================================================
|
|
5141
|
+
// Address Book
|
|
5142
|
+
// ============================================================================
|
|
5143
|
+
async getAddressBook() {
|
|
5144
|
+
return { entries: [], message: "Address book retrieved" };
|
|
5145
|
+
}
|
|
5146
|
+
async addAddressBookEntry(params) {
|
|
5147
|
+
return { added: true, name: params.name, address: params.address, chain: params.chain };
|
|
5148
|
+
}
|
|
5149
|
+
async removeAddressBookEntry(params) {
|
|
5150
|
+
return { removed: true, address: params.address };
|
|
5151
|
+
}
|
|
5152
|
+
// ============================================================================
|
|
5153
|
+
// Token Search & Other
|
|
5154
|
+
// ============================================================================
|
|
5155
|
+
async searchToken(params) {
|
|
5156
|
+
return { message: `Token search for '${params.query || params.symbol}' - delegated to backend` };
|
|
5157
|
+
}
|
|
5158
|
+
async listVaults() {
|
|
5159
|
+
return {
|
|
5160
|
+
vaults: [{
|
|
5161
|
+
name: this.vault.name,
|
|
5162
|
+
id: this.vault.id,
|
|
5163
|
+
type: this.vault.type,
|
|
5164
|
+
chains: this.vault.chains.map((c) => c.toString())
|
|
5165
|
+
}]
|
|
5166
|
+
};
|
|
5167
|
+
}
|
|
5168
|
+
async scanTx(params) {
|
|
5169
|
+
return { message: `Transaction scan for ${params.tx_hash || "unknown"} - delegated to backend` };
|
|
5170
|
+
}
|
|
5171
|
+
async readEvmContract(params) {
|
|
5172
|
+
return { message: `EVM contract read for ${params.contract || "unknown"} - delegated to backend` };
|
|
5173
|
+
}
|
|
5174
|
+
};
|
|
5175
|
+
async function encodeContractCall(functionName, params) {
|
|
5176
|
+
const types = params.map((p) => p.type);
|
|
5177
|
+
const sig = `${functionName}(${types.join(",")})`;
|
|
5178
|
+
const selector = await keccak256Selector(sig);
|
|
5179
|
+
let encoded = "";
|
|
5180
|
+
for (const param of params) {
|
|
5181
|
+
encoded += abiEncodeParam(param.type, param.value);
|
|
5182
|
+
}
|
|
5183
|
+
return "0x" + selector + encoded;
|
|
5184
|
+
}
|
|
5185
|
+
async function keccak256Selector(sig) {
|
|
5186
|
+
const { keccak_256: keccak_2562 } = await Promise.resolve().then(() => (init_sha3(), sha3_exports));
|
|
5187
|
+
const hash = keccak_2562(new TextEncoder().encode(sig));
|
|
5188
|
+
return Buffer.from(hash).toString("hex").slice(0, 8);
|
|
5189
|
+
}
|
|
5190
|
+
function abiEncodeParam(type, value) {
|
|
5191
|
+
if (type === "address") {
|
|
5192
|
+
const addr = value.startsWith("0x") ? value.slice(2) : value;
|
|
5193
|
+
return addr.toLowerCase().padStart(64, "0");
|
|
5194
|
+
}
|
|
5195
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
5196
|
+
const n = BigInt(value);
|
|
5197
|
+
const hex = n.toString(16);
|
|
5198
|
+
return hex.padStart(64, "0");
|
|
5199
|
+
}
|
|
5200
|
+
if (type === "bool") {
|
|
5201
|
+
return (value === "true" || value === "1" ? "1" : "0").padStart(64, "0");
|
|
5202
|
+
}
|
|
5203
|
+
if (type === "bytes32") {
|
|
5204
|
+
const b2 = value.startsWith("0x") ? value.slice(2) : value;
|
|
5205
|
+
return b2.padEnd(64, "0");
|
|
5206
|
+
}
|
|
5207
|
+
const b = value.startsWith("0x") ? value.slice(2) : Buffer.from(value).toString("hex");
|
|
5208
|
+
return b.padStart(64, "0");
|
|
5209
|
+
}
|
|
5210
|
+
function resolveChain(name) {
|
|
5211
|
+
if (!name) return null;
|
|
5212
|
+
if (Object.values(Chain9).includes(name)) {
|
|
5213
|
+
return name;
|
|
5214
|
+
}
|
|
5215
|
+
const lower = name.toLowerCase();
|
|
5216
|
+
for (const [, value] of Object.entries(Chain9)) {
|
|
5217
|
+
if (typeof value === "string" && value.toLowerCase() === lower) {
|
|
5218
|
+
return value;
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
const aliases = {
|
|
5222
|
+
eth: "Ethereum",
|
|
5223
|
+
btc: "Bitcoin",
|
|
5224
|
+
sol: "Solana",
|
|
5225
|
+
bnb: "BSC",
|
|
5226
|
+
avax: "Avalanche",
|
|
5227
|
+
matic: "Polygon",
|
|
5228
|
+
arb: "Arbitrum",
|
|
5229
|
+
op: "Optimism",
|
|
5230
|
+
ltc: "Litecoin",
|
|
5231
|
+
doge: "Dogecoin",
|
|
5232
|
+
dot: "Polkadot",
|
|
5233
|
+
atom: "Cosmos",
|
|
5234
|
+
rune: "THORChain",
|
|
5235
|
+
thor: "THORChain",
|
|
5236
|
+
sui: "Sui",
|
|
5237
|
+
ton: "Ton",
|
|
5238
|
+
trx: "Tron",
|
|
5239
|
+
xrp: "Ripple"
|
|
5240
|
+
};
|
|
5241
|
+
const aliased = aliases[lower];
|
|
5242
|
+
if (aliased && Object.values(Chain9).includes(aliased)) {
|
|
5243
|
+
return aliased;
|
|
5244
|
+
}
|
|
5245
|
+
return null;
|
|
5246
|
+
}
|
|
5247
|
+
function parseAmount(amountStr, decimals) {
|
|
5248
|
+
const [whole, frac = ""] = amountStr.split(".");
|
|
5249
|
+
const paddedFrac = frac.slice(0, decimals).padEnd(decimals, "0");
|
|
5250
|
+
return BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(paddedFrac || "0");
|
|
5251
|
+
}
|
|
5252
|
+
function resolveChainFromTxReady(txReadyData) {
|
|
5253
|
+
if (txReadyData.chain) {
|
|
5254
|
+
const chain = resolveChain(txReadyData.chain);
|
|
5255
|
+
if (chain) return chain;
|
|
5256
|
+
}
|
|
5257
|
+
if (txReadyData.from_chain) {
|
|
5258
|
+
const chain = resolveChain(txReadyData.from_chain);
|
|
5259
|
+
if (chain) return chain;
|
|
5260
|
+
}
|
|
5261
|
+
if (txReadyData.chain_id) {
|
|
5262
|
+
const chain = resolveChainId(txReadyData.chain_id);
|
|
5263
|
+
if (chain) return chain;
|
|
5264
|
+
}
|
|
5265
|
+
const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
|
|
5266
|
+
if (swapTx?.chainId) {
|
|
5267
|
+
const chain = resolveChainId(swapTx.chainId);
|
|
5268
|
+
if (chain) return chain;
|
|
5269
|
+
}
|
|
5270
|
+
return null;
|
|
5271
|
+
}
|
|
5272
|
+
function resolveChainId(chainId) {
|
|
5273
|
+
const id = typeof chainId === "string" ? parseInt(chainId, 10) : chainId;
|
|
5274
|
+
if (isNaN(id)) return null;
|
|
5275
|
+
const chainIdMap = {
|
|
5276
|
+
1: Chain9.Ethereum,
|
|
5277
|
+
56: Chain9.BSC,
|
|
5278
|
+
137: Chain9.Polygon,
|
|
5279
|
+
43114: Chain9.Avalanche,
|
|
5280
|
+
42161: Chain9.Arbitrum,
|
|
5281
|
+
10: Chain9.Optimism,
|
|
5282
|
+
8453: Chain9.Base,
|
|
5283
|
+
81457: Chain9.Blast,
|
|
5284
|
+
324: Chain9.Zksync,
|
|
5285
|
+
25: Chain9.CronosChain
|
|
5286
|
+
};
|
|
5287
|
+
return chainIdMap[id] || null;
|
|
5288
|
+
}
|
|
5289
|
+
async function computeEIP712Hash(domain, types, primaryType, message) {
|
|
5290
|
+
const { keccak_256: keccak_2562 } = await Promise.resolve().then(() => (init_sha3(), sha3_exports));
|
|
5291
|
+
const domainSeparator = hashStruct("EIP712Domain", domain, types, keccak_2562);
|
|
5292
|
+
const messageHash = hashStruct(primaryType, message, types, keccak_2562);
|
|
5293
|
+
const prefix = new Uint8Array([25, 1]);
|
|
5294
|
+
const combined = new Uint8Array(2 + 32 + 32);
|
|
5295
|
+
combined.set(prefix, 0);
|
|
5296
|
+
combined.set(domainSeparator, 2);
|
|
5297
|
+
combined.set(messageHash, 34);
|
|
5298
|
+
const finalHash = keccak_2562(combined);
|
|
5299
|
+
return "0x" + Buffer.from(finalHash).toString("hex");
|
|
5300
|
+
}
|
|
5301
|
+
function hashStruct(typeName, data, types, keccak) {
|
|
5302
|
+
const typeHash = hashType(typeName, types, keccak);
|
|
5303
|
+
const encodedData = encodeData(typeName, data, types, keccak);
|
|
5304
|
+
const combined = new Uint8Array(32 + encodedData.length);
|
|
5305
|
+
combined.set(typeHash, 0);
|
|
5306
|
+
combined.set(encodedData, 32);
|
|
5307
|
+
return keccak(combined);
|
|
5308
|
+
}
|
|
5309
|
+
function hashType(typeName, types, keccak) {
|
|
5310
|
+
const encoded = encodeType(typeName, types);
|
|
5311
|
+
return keccak(new TextEncoder().encode(encoded));
|
|
5312
|
+
}
|
|
5313
|
+
function encodeType(typeName, types) {
|
|
5314
|
+
const fields = getTypeFields(typeName, types);
|
|
5315
|
+
if (!fields) return "";
|
|
5316
|
+
const refs = /* @__PURE__ */ new Set();
|
|
5317
|
+
findReferencedTypes(typeName, types, refs);
|
|
5318
|
+
refs.delete(typeName);
|
|
5319
|
+
const sortedRefs = [...refs].sort();
|
|
5320
|
+
let result = `${typeName}(${fields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
5321
|
+
for (const ref of sortedRefs) {
|
|
5322
|
+
const refFields = getTypeFields(ref, types);
|
|
5323
|
+
if (refFields) {
|
|
5324
|
+
result += `${ref}(${refFields.map((f) => `${f.type} ${f.name}`).join(",")})`;
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
return result;
|
|
5328
|
+
}
|
|
5329
|
+
function findReferencedTypes(typeName, types, refs) {
|
|
5330
|
+
if (refs.has(typeName)) return;
|
|
5331
|
+
const fields = getTypeFields(typeName, types);
|
|
5332
|
+
if (!fields) return;
|
|
5333
|
+
refs.add(typeName);
|
|
5334
|
+
for (const field of fields) {
|
|
5335
|
+
const baseType = field.type.replace(/\[\d*\]$/, "");
|
|
5336
|
+
if (types[baseType]) {
|
|
5337
|
+
findReferencedTypes(baseType, types, refs);
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
function getTypeFields(typeName, types) {
|
|
5342
|
+
if (types[typeName]) return types[typeName];
|
|
5343
|
+
if (typeName === "EIP712Domain") {
|
|
5344
|
+
return [
|
|
5345
|
+
{ name: "name", type: "string" },
|
|
5346
|
+
{ name: "version", type: "string" },
|
|
5347
|
+
{ name: "chainId", type: "uint256" },
|
|
5348
|
+
{ name: "verifyingContract", type: "address" }
|
|
5349
|
+
];
|
|
5350
|
+
}
|
|
5351
|
+
return void 0;
|
|
5352
|
+
}
|
|
5353
|
+
function encodeData(typeName, data, types, keccak) {
|
|
5354
|
+
const fields = getTypeFields(typeName, types);
|
|
5355
|
+
if (!fields) return new Uint8Array(0);
|
|
5356
|
+
const chunks = [];
|
|
5357
|
+
for (const field of fields) {
|
|
5358
|
+
const value = data[field.name];
|
|
5359
|
+
if (value === void 0 || value === null) continue;
|
|
5360
|
+
chunks.push(encodeField(field.type, value, types, keccak));
|
|
5361
|
+
}
|
|
5362
|
+
const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
5363
|
+
const result = new Uint8Array(totalLen);
|
|
5364
|
+
let offset = 0;
|
|
5365
|
+
for (const chunk of chunks) {
|
|
5366
|
+
result.set(chunk, offset);
|
|
5367
|
+
offset += chunk.length;
|
|
5368
|
+
}
|
|
5369
|
+
return result;
|
|
5370
|
+
}
|
|
5371
|
+
function encodeField(type, value, types, keccak) {
|
|
5372
|
+
if (type === "string") {
|
|
5373
|
+
return keccak(new TextEncoder().encode(value));
|
|
5374
|
+
}
|
|
5375
|
+
if (type === "bytes") {
|
|
5376
|
+
const hex2 = value.startsWith("0x") ? value.slice(2) : value;
|
|
5377
|
+
const bytes2 = hexToBytes(hex2);
|
|
5378
|
+
return keccak(bytes2);
|
|
5379
|
+
}
|
|
5380
|
+
const baseType = type.replace(/\[\d*\]$/, "");
|
|
5381
|
+
if (types[baseType] && !type.endsWith("]")) {
|
|
5382
|
+
return hashStruct(baseType, value, types, keccak);
|
|
5383
|
+
}
|
|
5384
|
+
if (type.endsWith("]")) {
|
|
5385
|
+
const arr = value;
|
|
5386
|
+
const elementType = type.replace(/\[\d*\]$/, "");
|
|
5387
|
+
const encodedElements = arr.map((el) => encodeField(elementType, el, types, keccak));
|
|
5388
|
+
const totalLen = encodedElements.reduce((sum, e) => sum + e.length, 0);
|
|
5389
|
+
const concat = new Uint8Array(totalLen);
|
|
5390
|
+
let off = 0;
|
|
5391
|
+
for (const el of encodedElements) {
|
|
5392
|
+
concat.set(el, off);
|
|
5393
|
+
off += el.length;
|
|
5394
|
+
}
|
|
5395
|
+
return keccak(concat);
|
|
5396
|
+
}
|
|
5397
|
+
const result = new Uint8Array(32);
|
|
5398
|
+
if (type === "address") {
|
|
5399
|
+
const addr = value.startsWith("0x") ? value.slice(2) : value;
|
|
5400
|
+
const bytes2 = hexToBytes(addr.toLowerCase());
|
|
5401
|
+
result.set(bytes2, 32 - bytes2.length);
|
|
5402
|
+
return result;
|
|
5403
|
+
}
|
|
5404
|
+
if (type === "bool") {
|
|
5405
|
+
if (value === true || value === "true" || value === 1 || value === "1") {
|
|
5406
|
+
result[31] = 1;
|
|
5407
|
+
}
|
|
5408
|
+
return result;
|
|
5409
|
+
}
|
|
5410
|
+
if (type.startsWith("uint") || type.startsWith("int")) {
|
|
5411
|
+
const n2 = BigInt(value);
|
|
5412
|
+
const hex2 = n2.toString(16).padStart(64, "0");
|
|
5413
|
+
const bytes2 = hexToBytes(hex2);
|
|
5414
|
+
result.set(bytes2, 32 - bytes2.length);
|
|
5415
|
+
return result;
|
|
5416
|
+
}
|
|
5417
|
+
if (type.startsWith("bytes")) {
|
|
5418
|
+
const hex2 = value.startsWith("0x") ? value.slice(2) : value;
|
|
5419
|
+
const bytes2 = hexToBytes(hex2);
|
|
5420
|
+
result.set(bytes2, 0);
|
|
5421
|
+
return result;
|
|
5422
|
+
}
|
|
5423
|
+
const n = BigInt(value);
|
|
5424
|
+
const hex = n.toString(16).padStart(64, "0");
|
|
5425
|
+
const bytes = hexToBytes(hex);
|
|
5426
|
+
result.set(bytes, 32 - bytes.length);
|
|
5427
|
+
return result;
|
|
5428
|
+
}
|
|
5429
|
+
function hexToBytes(hex) {
|
|
5430
|
+
const clean2 = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
5431
|
+
const padded = clean2.length % 2 === 1 ? "0" + clean2 : clean2;
|
|
5432
|
+
const bytes = new Uint8Array(padded.length / 2);
|
|
5433
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5434
|
+
bytes[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
|
|
5435
|
+
}
|
|
5436
|
+
return bytes;
|
|
5437
|
+
}
|
|
5438
|
+
function parseDERSignature(sigHex) {
|
|
5439
|
+
const raw = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex;
|
|
5440
|
+
if (raw.length === 128) {
|
|
5441
|
+
return { r: raw.slice(0, 64), s: raw.slice(64) };
|
|
5442
|
+
}
|
|
5443
|
+
let offset = 0;
|
|
5444
|
+
if (raw.slice(offset, offset + 2) !== "30") {
|
|
5445
|
+
return { r: raw.slice(0, 64).padStart(64, "0"), s: raw.slice(64).padStart(64, "0") };
|
|
5446
|
+
}
|
|
5447
|
+
offset += 2;
|
|
5448
|
+
offset += 2;
|
|
5449
|
+
if (raw.slice(offset, offset + 2) !== "02") throw new Error("Invalid DER: expected 02 for R");
|
|
5450
|
+
offset += 2;
|
|
5451
|
+
const rLen = parseInt(raw.slice(offset, offset + 2), 16);
|
|
5452
|
+
offset += 2;
|
|
5453
|
+
let rHex = raw.slice(offset, offset + rLen * 2);
|
|
5454
|
+
offset += rLen * 2;
|
|
5455
|
+
if (rHex.length > 64 && rHex.startsWith("00")) {
|
|
5456
|
+
rHex = rHex.slice(rHex.length - 64);
|
|
5457
|
+
}
|
|
5458
|
+
rHex = rHex.padStart(64, "0");
|
|
5459
|
+
if (raw.slice(offset, offset + 2) !== "02") throw new Error("Invalid DER: expected 02 for S");
|
|
5460
|
+
offset += 2;
|
|
5461
|
+
const sLen = parseInt(raw.slice(offset, offset + 2), 16);
|
|
5462
|
+
offset += 2;
|
|
5463
|
+
let sHex = raw.slice(offset, offset + sLen * 2);
|
|
5464
|
+
if (sHex.length > 64 && sHex.startsWith("00")) {
|
|
5465
|
+
sHex = sHex.slice(sHex.length - 64);
|
|
5466
|
+
}
|
|
5467
|
+
sHex = sHex.padStart(64, "0");
|
|
5468
|
+
return { r: rHex, s: sHex };
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5471
|
+
// src/agent/pipe.ts
|
|
5472
|
+
import * as readline from "node:readline";
|
|
5473
|
+
var PipeInterface = class {
|
|
5474
|
+
session;
|
|
5475
|
+
rl = null;
|
|
5476
|
+
stopped = false;
|
|
5477
|
+
pendingPasswordResolve = null;
|
|
5478
|
+
pendingConfirmResolve = null;
|
|
5479
|
+
constructor(session) {
|
|
5480
|
+
this.session = session;
|
|
5481
|
+
process.stdin.pause();
|
|
5482
|
+
}
|
|
5483
|
+
/**
|
|
5484
|
+
* Start the pipe interface.
|
|
5485
|
+
*/
|
|
5486
|
+
async start(vaultName, addresses) {
|
|
5487
|
+
this.rl = readline.createInterface({
|
|
5488
|
+
input: process.stdin,
|
|
5489
|
+
output: void 0,
|
|
5490
|
+
// Don't write prompts to stdout
|
|
5491
|
+
terminal: false
|
|
5492
|
+
});
|
|
5493
|
+
this.emit({ type: "ready", vault: vaultName, addresses });
|
|
5494
|
+
const lines = [];
|
|
5495
|
+
let inputDone = false;
|
|
5496
|
+
let processing = false;
|
|
5497
|
+
this.rl.on("line", async (line) => {
|
|
5498
|
+
const trimmed = line.trim();
|
|
5499
|
+
if (!trimmed) return;
|
|
5500
|
+
lines.push(trimmed);
|
|
5501
|
+
if (!processing) {
|
|
5502
|
+
processing = true;
|
|
5503
|
+
while (lines.length > 0) {
|
|
5504
|
+
const nextLine = lines.shift();
|
|
5505
|
+
try {
|
|
5506
|
+
const cmd = JSON.parse(nextLine);
|
|
5507
|
+
await this.handleCommand(cmd);
|
|
5508
|
+
} catch (err) {
|
|
5509
|
+
this.emit({ type: "error", message: `Invalid input: ${err.message}` });
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
processing = false;
|
|
5513
|
+
if (inputDone && lines.length === 0) {
|
|
5514
|
+
this.stop();
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
});
|
|
5518
|
+
this.rl.on("close", () => {
|
|
5519
|
+
inputDone = true;
|
|
5520
|
+
if (!processing && lines.length === 0) {
|
|
5521
|
+
this.stop();
|
|
5522
|
+
}
|
|
5523
|
+
});
|
|
5524
|
+
await new Promise((resolve) => {
|
|
5525
|
+
const check = setInterval(() => {
|
|
5526
|
+
if (this.stopped) {
|
|
5527
|
+
clearInterval(check);
|
|
5528
|
+
resolve();
|
|
5529
|
+
}
|
|
5530
|
+
}, 100);
|
|
5531
|
+
});
|
|
5532
|
+
}
|
|
5533
|
+
stop() {
|
|
5534
|
+
if (this.stopped) return;
|
|
5535
|
+
this.stopped = true;
|
|
5536
|
+
this.rl?.close();
|
|
5537
|
+
this.session.dispose();
|
|
5538
|
+
}
|
|
5539
|
+
/**
|
|
5540
|
+
* Get UI callbacks for the session.
|
|
5541
|
+
*/
|
|
5542
|
+
getCallbacks() {
|
|
5543
|
+
return {
|
|
5544
|
+
onTextDelta: (delta) => {
|
|
5545
|
+
this.emit({ type: "text_delta", delta });
|
|
5546
|
+
},
|
|
5547
|
+
onToolCall: (id, action, params) => {
|
|
5548
|
+
this.emit({ type: "tool_call", id, action, params, status: "running" });
|
|
5549
|
+
},
|
|
5550
|
+
onToolResult: (id, action, success2, data, error2) => {
|
|
5551
|
+
this.emit({ type: "tool_result", id, action, success: success2, data, error: error2 });
|
|
5552
|
+
},
|
|
5553
|
+
onAssistantMessage: (content) => {
|
|
5554
|
+
this.emit({ type: "assistant", content });
|
|
5555
|
+
},
|
|
5556
|
+
onSuggestions: (suggestions) => {
|
|
5557
|
+
this.emit({ type: "suggestions", suggestions });
|
|
5558
|
+
},
|
|
5559
|
+
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
5560
|
+
this.emit({
|
|
5561
|
+
type: "tx_status",
|
|
5562
|
+
tx_hash: txHash,
|
|
5563
|
+
chain,
|
|
5564
|
+
status,
|
|
5565
|
+
explorer_url: explorerUrl
|
|
5566
|
+
});
|
|
5567
|
+
},
|
|
5568
|
+
onError: (message) => {
|
|
5569
|
+
this.emit({ type: "error", message });
|
|
5570
|
+
},
|
|
5571
|
+
onDone: () => {
|
|
5572
|
+
this.emit({ type: "done" });
|
|
5573
|
+
},
|
|
5574
|
+
requestPassword: async () => {
|
|
5575
|
+
return new Promise((resolve) => {
|
|
5576
|
+
this.pendingPasswordResolve = resolve;
|
|
5577
|
+
this.emit({ type: "error", message: "PASSWORD_REQUIRED" });
|
|
5578
|
+
});
|
|
5579
|
+
},
|
|
5580
|
+
requestConfirmation: async (message) => {
|
|
5581
|
+
return new Promise((resolve) => {
|
|
5582
|
+
this.pendingConfirmResolve = resolve;
|
|
5583
|
+
this.emit({ type: "error", message: `CONFIRMATION_REQUIRED: ${message}` });
|
|
5584
|
+
});
|
|
5585
|
+
}
|
|
5586
|
+
};
|
|
5587
|
+
}
|
|
5588
|
+
async handleCommand(cmd) {
|
|
5589
|
+
switch (cmd.type) {
|
|
5590
|
+
case "message": {
|
|
5591
|
+
const callbacks = this.getCallbacks();
|
|
5592
|
+
try {
|
|
5593
|
+
await this.session.sendMessage(cmd.content, callbacks);
|
|
5594
|
+
} catch (err) {
|
|
5595
|
+
this.emit({ type: "error", message: err.message });
|
|
5596
|
+
this.emit({ type: "done" });
|
|
5597
|
+
}
|
|
5598
|
+
break;
|
|
5599
|
+
}
|
|
5600
|
+
case "password": {
|
|
5601
|
+
if (this.pendingPasswordResolve) {
|
|
5602
|
+
this.pendingPasswordResolve(cmd.password);
|
|
5603
|
+
this.pendingPasswordResolve = null;
|
|
5604
|
+
}
|
|
5605
|
+
break;
|
|
5606
|
+
}
|
|
5607
|
+
case "confirm": {
|
|
5608
|
+
if (this.pendingConfirmResolve) {
|
|
5609
|
+
this.pendingConfirmResolve(cmd.confirmed);
|
|
5610
|
+
this.pendingConfirmResolve = null;
|
|
5611
|
+
}
|
|
5612
|
+
break;
|
|
5613
|
+
}
|
|
5614
|
+
default:
|
|
5615
|
+
this.emit({ type: "error", message: `Unknown command type: ${cmd.type}` });
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5618
|
+
emit(event) {
|
|
5619
|
+
process.stdout.write(JSON.stringify(event) + "\n");
|
|
5620
|
+
}
|
|
5621
|
+
};
|
|
5622
|
+
|
|
5623
|
+
// src/agent/session.ts
|
|
5624
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5625
|
+
import { homedir } from "node:os";
|
|
5626
|
+
import { join } from "node:path";
|
|
5627
|
+
var AgentSession = class {
|
|
5628
|
+
client;
|
|
5629
|
+
vault;
|
|
5630
|
+
executor;
|
|
5631
|
+
config;
|
|
5632
|
+
conversationId = null;
|
|
5633
|
+
publicKey;
|
|
5634
|
+
cachedContext = null;
|
|
5635
|
+
abortController = null;
|
|
5636
|
+
constructor(vault, config) {
|
|
5637
|
+
this.vault = vault;
|
|
5638
|
+
this.config = config;
|
|
5639
|
+
this.client = new AgentClient(config.backendUrl);
|
|
5640
|
+
this.client.verbose = !!config.verbose;
|
|
5641
|
+
this.executor = new AgentExecutor(vault, !!config.verbose);
|
|
5642
|
+
this.publicKey = vault.publicKeys.ecdsa;
|
|
5643
|
+
if (config.password) {
|
|
5644
|
+
this.executor.setPassword(config.password);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
/**
|
|
5648
|
+
* Initialize the session: health check, authenticate, create conversation.
|
|
5649
|
+
*/
|
|
5650
|
+
async initialize(ui) {
|
|
5651
|
+
const healthy = await this.client.healthCheck();
|
|
5652
|
+
if (!healthy) {
|
|
5653
|
+
throw new Error(`Agent backend unreachable at ${this.config.backendUrl}`);
|
|
5654
|
+
}
|
|
5655
|
+
try {
|
|
5656
|
+
if (this.vault.isEncrypted) {
|
|
5657
|
+
const password = this.config.password || await ui.requestPassword();
|
|
5658
|
+
await this.vault.unlock?.(password);
|
|
5659
|
+
this.executor.setPassword(password);
|
|
5660
|
+
}
|
|
5661
|
+
const cached = loadCachedToken(this.publicKey);
|
|
5662
|
+
if (cached) {
|
|
5663
|
+
this.client.setAuthToken(cached);
|
|
5664
|
+
} else {
|
|
5665
|
+
const auth = await authenticateVault(this.client, this.vault, this.config.password);
|
|
5666
|
+
this.client.setAuthToken(auth.token);
|
|
5667
|
+
saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
|
|
5668
|
+
}
|
|
5669
|
+
} catch (err) {
|
|
5670
|
+
throw new Error(`Authentication failed: ${err.message}`);
|
|
5671
|
+
}
|
|
5672
|
+
if (this.config.conversationId) {
|
|
5673
|
+
this.conversationId = this.config.conversationId;
|
|
5674
|
+
} else {
|
|
5675
|
+
const conv = await this.client.createConversation(this.publicKey);
|
|
5676
|
+
this.conversationId = conv.id;
|
|
5677
|
+
}
|
|
5678
|
+
this.cachedContext = await buildMessageContext(this.vault);
|
|
5679
|
+
}
|
|
5680
|
+
getConversationId() {
|
|
5681
|
+
return this.conversationId;
|
|
5682
|
+
}
|
|
5683
|
+
getVaultAddresses() {
|
|
5684
|
+
return this.cachedContext?.addresses || {};
|
|
5685
|
+
}
|
|
5686
|
+
/**
|
|
5687
|
+
* Send a user message and process the full response cycle.
|
|
5688
|
+
*
|
|
5689
|
+
* Flow:
|
|
5690
|
+
* 1. Send message to backend via SSE stream
|
|
5691
|
+
* 2. Collect text deltas and actions
|
|
5692
|
+
* 3. Execute auto-execute actions locally
|
|
5693
|
+
* 4. Report results back to backend
|
|
5694
|
+
* 5. Repeat if backend sends more actions
|
|
5695
|
+
*/
|
|
5696
|
+
async sendMessage(content, ui) {
|
|
5697
|
+
if (!this.conversationId) {
|
|
5698
|
+
throw new Error("Session not initialized");
|
|
5699
|
+
}
|
|
5700
|
+
this.abortController = new AbortController();
|
|
5701
|
+
try {
|
|
5702
|
+
this.cachedContext = await buildMessageContext(this.vault);
|
|
5703
|
+
} catch {
|
|
5704
|
+
}
|
|
5705
|
+
try {
|
|
5706
|
+
await this.processMessageLoop(content, null, ui);
|
|
5707
|
+
} catch (err) {
|
|
5708
|
+
if (err.message?.includes("401") || err.message?.includes("403")) {
|
|
5709
|
+
clearCachedToken(this.publicKey);
|
|
5710
|
+
const auth = await authenticateVault(this.client, this.vault, this.config.password);
|
|
5711
|
+
this.client.setAuthToken(auth.token);
|
|
5712
|
+
saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
|
|
5713
|
+
await this.processMessageLoop(content, null, ui);
|
|
5714
|
+
} else {
|
|
5715
|
+
throw err;
|
|
5716
|
+
}
|
|
5717
|
+
} finally {
|
|
5718
|
+
this.abortController = null;
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5722
|
+
* Core message processing loop.
|
|
5723
|
+
* Sends content or action results, executes returned actions, repeats.
|
|
5724
|
+
*/
|
|
5725
|
+
async processMessageLoop(content, actionResults, ui) {
|
|
5726
|
+
if (!this.conversationId) return;
|
|
5727
|
+
const request = {
|
|
5728
|
+
public_key: this.publicKey,
|
|
5729
|
+
context: this.cachedContext
|
|
5730
|
+
};
|
|
5731
|
+
if (content) {
|
|
5732
|
+
request.content = content;
|
|
5733
|
+
}
|
|
5734
|
+
if (actionResults && actionResults.length > 0) {
|
|
5735
|
+
const result = actionResults[0];
|
|
5736
|
+
request.action_result = {
|
|
5737
|
+
action: result.action,
|
|
5738
|
+
action_id: result.action_id,
|
|
5739
|
+
success: result.success,
|
|
5740
|
+
data: result.data || {},
|
|
5741
|
+
error: result.error || ""
|
|
5742
|
+
};
|
|
5743
|
+
}
|
|
5744
|
+
const streamResult = await this.client.sendMessageStream(
|
|
5745
|
+
this.conversationId,
|
|
5746
|
+
request,
|
|
5747
|
+
{
|
|
5748
|
+
onTextDelta: (delta) => ui.onTextDelta(delta),
|
|
5749
|
+
onToolProgress: (tool, status, label) => {
|
|
5750
|
+
if (status === "running") {
|
|
5751
|
+
ui.onToolCall(`mcp-${tool}`, tool);
|
|
5752
|
+
} else {
|
|
5753
|
+
ui.onToolResult(`mcp-${tool}`, tool, true, { label });
|
|
5754
|
+
}
|
|
5755
|
+
},
|
|
5756
|
+
onTitle: (_title) => {
|
|
5757
|
+
},
|
|
5758
|
+
onActions: (_actions) => {
|
|
5759
|
+
},
|
|
5760
|
+
onSuggestions: (suggestions) => {
|
|
5761
|
+
ui.onSuggestions(suggestions);
|
|
5762
|
+
},
|
|
5763
|
+
onTxReady: (tx) => {
|
|
5764
|
+
this.executor.storeServerTransaction(tx);
|
|
5765
|
+
if (this.config.password) {
|
|
5766
|
+
this.executor.setPassword(this.config.password);
|
|
5767
|
+
}
|
|
5768
|
+
},
|
|
5769
|
+
onMessage: (_msg) => {
|
|
5770
|
+
},
|
|
5771
|
+
onError: (error2) => {
|
|
5772
|
+
ui.onError(error2);
|
|
5773
|
+
}
|
|
5774
|
+
},
|
|
5775
|
+
this.abortController?.signal
|
|
5776
|
+
);
|
|
5777
|
+
const responseText = streamResult.fullText || streamResult.message?.content || "";
|
|
5778
|
+
const inlineActions = parseInlineToolCalls(responseText);
|
|
5779
|
+
if (inlineActions.length > 0) {
|
|
5780
|
+
const cleanText = responseText.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").trim();
|
|
5781
|
+
if (cleanText) {
|
|
5782
|
+
ui.onAssistantMessage(cleanText);
|
|
5783
|
+
}
|
|
5784
|
+
streamResult.actions.push(...inlineActions);
|
|
5785
|
+
} else if (responseText) {
|
|
5786
|
+
ui.onAssistantMessage(responseText);
|
|
5787
|
+
}
|
|
5788
|
+
const nonSignActions = streamResult.actions.filter((a) => a.type !== "sign_tx");
|
|
5789
|
+
const backendSignActions = streamResult.actions.filter((a) => a.type === "sign_tx");
|
|
5790
|
+
if (nonSignActions.length > 0) {
|
|
5791
|
+
const results = await this.executeActions(nonSignActions, ui);
|
|
5792
|
+
const hasBuildSuccess = results.some(
|
|
5793
|
+
(r) => r.success && r.action.startsWith("build_")
|
|
5794
|
+
);
|
|
5795
|
+
if (hasBuildSuccess && this.executor.hasPendingTransaction()) {
|
|
5796
|
+
if (this.config.verbose) process.stderr.write(`[session] build_* action produced pending tx, auto-chaining sign_tx
|
|
5797
|
+
`);
|
|
5798
|
+
const signAction = {
|
|
5799
|
+
id: `tx_sign_${Date.now()}`,
|
|
5800
|
+
type: "sign_tx",
|
|
5801
|
+
title: "Sign transaction",
|
|
5802
|
+
params: {},
|
|
5803
|
+
auto_execute: true
|
|
5804
|
+
};
|
|
5805
|
+
const signResults = await this.executeActions([signAction], ui);
|
|
5806
|
+
const allResults = [...results, ...signResults];
|
|
5807
|
+
for (const result of allResults) {
|
|
5808
|
+
await this.processMessageLoop(null, [result], ui);
|
|
5809
|
+
}
|
|
5810
|
+
return;
|
|
5811
|
+
}
|
|
5812
|
+
if (results.length > 0) {
|
|
5813
|
+
for (const result of results) {
|
|
5814
|
+
await this.processMessageLoop(null, [result], ui);
|
|
5815
|
+
}
|
|
5816
|
+
return;
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
if (streamResult.transactions.length > 0 && this.executor.hasPendingTransaction()) {
|
|
5820
|
+
if (this.config.verbose) process.stderr.write(`[session] ${streamResult.transactions.length} tx_ready events, signing client-side
|
|
5821
|
+
`);
|
|
5822
|
+
const signAction = {
|
|
5823
|
+
id: `tx_sign_${Date.now()}`,
|
|
5824
|
+
type: "sign_tx",
|
|
5825
|
+
title: "Sign transaction",
|
|
5826
|
+
params: {},
|
|
5827
|
+
auto_execute: true
|
|
5828
|
+
};
|
|
5829
|
+
const results = await this.executeActions([signAction], ui);
|
|
5830
|
+
if (results.length > 0) {
|
|
5831
|
+
for (const result of results) {
|
|
5832
|
+
await this.processMessageLoop(null, [result], ui);
|
|
5833
|
+
}
|
|
5834
|
+
return;
|
|
5835
|
+
}
|
|
5836
|
+
} else if (backendSignActions.length > 0 && this.executor.hasPendingTransaction()) {
|
|
5837
|
+
if (this.config.verbose) process.stderr.write(`[session] Backend sent sign_tx action, using it
|
|
5838
|
+
`);
|
|
5839
|
+
const results = await this.executeActions(backendSignActions, ui);
|
|
5840
|
+
if (results.length > 0) {
|
|
5841
|
+
for (const result of results) {
|
|
5842
|
+
await this.processMessageLoop(null, [result], ui);
|
|
5843
|
+
}
|
|
5844
|
+
return;
|
|
5845
|
+
}
|
|
5846
|
+
} else if (backendSignActions.length > 0 && !this.executor.hasPendingTransaction()) {
|
|
5847
|
+
if (this.config.verbose) process.stderr.write(`[session] Backend sent sign_tx but no pending tx, reporting error
|
|
5848
|
+
`);
|
|
5849
|
+
const errorResult = {
|
|
5850
|
+
action: "sign_tx",
|
|
5851
|
+
action_id: backendSignActions[0].id,
|
|
5852
|
+
success: false,
|
|
5853
|
+
error: "No pending transaction. The swap transaction data was not received."
|
|
5854
|
+
};
|
|
5855
|
+
await this.processMessageLoop(null, [errorResult], ui);
|
|
5856
|
+
return;
|
|
5857
|
+
}
|
|
5858
|
+
ui.onDone();
|
|
5859
|
+
}
|
|
5860
|
+
/**
|
|
5861
|
+
* Execute a list of actions, handling password requirements.
|
|
5862
|
+
*/
|
|
5863
|
+
async executeActions(actions, ui) {
|
|
5864
|
+
const results = [];
|
|
5865
|
+
for (const action of actions) {
|
|
5866
|
+
if (!this.executor.shouldAutoExecute(action)) {
|
|
5867
|
+
continue;
|
|
5868
|
+
}
|
|
5869
|
+
if (PASSWORD_REQUIRED_ACTIONS.has(action.type)) {
|
|
5870
|
+
if (!this.config.password) {
|
|
5871
|
+
try {
|
|
5872
|
+
const password = await ui.requestPassword();
|
|
5873
|
+
this.executor.setPassword(password);
|
|
5874
|
+
this.config.password = password;
|
|
5875
|
+
} catch {
|
|
5876
|
+
results.push({
|
|
5877
|
+
action: action.type,
|
|
5878
|
+
action_id: action.id,
|
|
5879
|
+
success: false,
|
|
5880
|
+
error: "Password not provided"
|
|
5881
|
+
});
|
|
5882
|
+
continue;
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
ui.onToolCall(action.id, action.type, action.params);
|
|
5887
|
+
const result = await this.executor.executeAction(action);
|
|
5888
|
+
results.push(result);
|
|
5889
|
+
ui.onToolResult(action.id, action.type, result.success, result.data, result.error);
|
|
5890
|
+
if (action.type === "sign_tx" && result.success && result.data) {
|
|
5891
|
+
const txHash = result.data.tx_hash;
|
|
5892
|
+
const chain = result.data.chain;
|
|
5893
|
+
const explorerUrl = result.data.explorer_url;
|
|
5894
|
+
if (txHash) {
|
|
5895
|
+
ui.onTxStatus(txHash, chain, "pending", explorerUrl);
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
return results;
|
|
5900
|
+
}
|
|
5901
|
+
/**
|
|
5902
|
+
* Cancel the current operation.
|
|
5903
|
+
*/
|
|
5904
|
+
cancel() {
|
|
5905
|
+
this.abortController?.abort();
|
|
5906
|
+
}
|
|
5907
|
+
/**
|
|
5908
|
+
* Clean up session resources.
|
|
5909
|
+
*/
|
|
5910
|
+
dispose() {
|
|
5911
|
+
this.cancel();
|
|
5912
|
+
this.cachedContext = null;
|
|
5913
|
+
this.conversationId = null;
|
|
5914
|
+
}
|
|
5915
|
+
};
|
|
5916
|
+
function parseInlineToolCalls(text) {
|
|
5917
|
+
const actions = [];
|
|
5918
|
+
const invokeRegex = /<invoke\s+name="([^"]+)">([\s\S]*?)<\/invoke>/g;
|
|
5919
|
+
let match;
|
|
5920
|
+
while ((match = invokeRegex.exec(text)) !== null) {
|
|
5921
|
+
const actionType = match[1];
|
|
5922
|
+
const body = match[2];
|
|
5923
|
+
const params = {};
|
|
5924
|
+
const paramRegex = /<parameter\s+name="([^"]+)">([\s\S]*?)<\/parameter>/g;
|
|
5925
|
+
let paramMatch;
|
|
5926
|
+
while ((paramMatch = paramRegex.exec(body)) !== null) {
|
|
5927
|
+
const key = paramMatch[1];
|
|
5928
|
+
const value = paramMatch[2];
|
|
5929
|
+
try {
|
|
5930
|
+
params[key] = JSON.parse(value);
|
|
5931
|
+
} catch {
|
|
5932
|
+
params[key] = value;
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
actions.push({
|
|
5936
|
+
id: `inline_${actionType}_${Date.now()}`,
|
|
5937
|
+
type: actionType,
|
|
5938
|
+
title: actionType,
|
|
5939
|
+
params,
|
|
5940
|
+
auto_execute: true
|
|
5941
|
+
});
|
|
5942
|
+
}
|
|
5943
|
+
return actions;
|
|
5944
|
+
}
|
|
5945
|
+
function getTokenCachePath() {
|
|
5946
|
+
const dir = process.env.VULTISIG_CONFIG_DIR ?? join(homedir(), ".vultisig");
|
|
5947
|
+
return join(dir, "agent-tokens.json");
|
|
5948
|
+
}
|
|
5949
|
+
function readTokenStore() {
|
|
5950
|
+
try {
|
|
5951
|
+
const path3 = getTokenCachePath();
|
|
5952
|
+
if (!existsSync(path3)) return {};
|
|
5953
|
+
return JSON.parse(readFileSync(path3, "utf-8"));
|
|
5954
|
+
} catch {
|
|
5955
|
+
return {};
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
function writeTokenStore(store) {
|
|
5959
|
+
const path3 = getTokenCachePath();
|
|
5960
|
+
const dir = join(path3, "..");
|
|
5961
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
5962
|
+
writeFileSync(path3, JSON.stringify(store, null, 2), { mode: 384 });
|
|
5963
|
+
}
|
|
5964
|
+
function loadCachedToken(publicKey) {
|
|
5965
|
+
const store = readTokenStore();
|
|
5966
|
+
const entry = store[publicKey];
|
|
5967
|
+
if (!entry) return null;
|
|
5968
|
+
const now = Date.now();
|
|
5969
|
+
const expiresMs = entry.expiresAt * (entry.expiresAt < 1e12 ? 1e3 : 1);
|
|
5970
|
+
if (now >= expiresMs - 6e4) {
|
|
5971
|
+
delete store[publicKey];
|
|
5972
|
+
try {
|
|
5973
|
+
writeTokenStore(store);
|
|
5974
|
+
} catch {
|
|
5975
|
+
}
|
|
5976
|
+
return null;
|
|
5977
|
+
}
|
|
5978
|
+
return entry.token;
|
|
5979
|
+
}
|
|
5980
|
+
function saveCachedToken(publicKey, token, expiresAt) {
|
|
5981
|
+
const store = readTokenStore();
|
|
5982
|
+
store[publicKey] = { token, expiresAt };
|
|
5983
|
+
try {
|
|
5984
|
+
writeTokenStore(store);
|
|
5985
|
+
} catch {
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
function clearCachedToken(publicKey) {
|
|
5989
|
+
const store = readTokenStore();
|
|
5990
|
+
delete store[publicKey];
|
|
5991
|
+
try {
|
|
5992
|
+
writeTokenStore(store);
|
|
5993
|
+
} catch {
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
// src/agent/tui.ts
|
|
5998
|
+
import * as readline2 from "node:readline";
|
|
5999
|
+
import chalk8 from "chalk";
|
|
6000
|
+
var ChatTUI = class {
|
|
6001
|
+
rl;
|
|
6002
|
+
session;
|
|
6003
|
+
isStreaming = false;
|
|
6004
|
+
currentStreamText = "";
|
|
6005
|
+
vaultName;
|
|
6006
|
+
stopped = false;
|
|
6007
|
+
verbose;
|
|
6008
|
+
constructor(session, vaultName, verbose = false) {
|
|
6009
|
+
this.session = session;
|
|
6010
|
+
this.vaultName = vaultName;
|
|
6011
|
+
this.verbose = verbose;
|
|
6012
|
+
this.rl = readline2.createInterface({
|
|
6013
|
+
input: process.stdin,
|
|
6014
|
+
output: process.stdout,
|
|
6015
|
+
prompt: "",
|
|
6016
|
+
terminal: true
|
|
6017
|
+
});
|
|
6018
|
+
}
|
|
6019
|
+
/**
|
|
6020
|
+
* Start the interactive chat loop.
|
|
6021
|
+
*/
|
|
6022
|
+
async start() {
|
|
6023
|
+
this.printHeader();
|
|
6024
|
+
this.printHelp();
|
|
6025
|
+
this.showPrompt();
|
|
6026
|
+
this.rl.on("line", async (line) => {
|
|
6027
|
+
const input = line.trim();
|
|
6028
|
+
readline2.moveCursor(process.stdout, 0, -1);
|
|
6029
|
+
readline2.clearLine(process.stdout, 0);
|
|
6030
|
+
if (!input) {
|
|
6031
|
+
this.showPrompt();
|
|
6032
|
+
return;
|
|
6033
|
+
}
|
|
6034
|
+
if (input === "/quit" || input === "/exit" || input === "/q") {
|
|
6035
|
+
this.stop();
|
|
6036
|
+
return;
|
|
6037
|
+
}
|
|
6038
|
+
if (input === "/help" || input === "/h") {
|
|
6039
|
+
this.printHelp();
|
|
6040
|
+
this.showPrompt();
|
|
6041
|
+
return;
|
|
6042
|
+
}
|
|
6043
|
+
if (input === "/clear") {
|
|
6044
|
+
console.clear();
|
|
6045
|
+
this.printHeader();
|
|
6046
|
+
this.showPrompt();
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
6049
|
+
this.printUserMessage(input);
|
|
6050
|
+
await this.handleMessage(input);
|
|
6051
|
+
this.showPrompt();
|
|
6052
|
+
});
|
|
6053
|
+
this.rl.on("close", () => {
|
|
6054
|
+
this.stop();
|
|
6055
|
+
});
|
|
6056
|
+
process.on("SIGINT", () => {
|
|
6057
|
+
if (this.isStreaming) {
|
|
6058
|
+
this.session.cancel();
|
|
6059
|
+
this.isStreaming = false;
|
|
6060
|
+
console.log(chalk8.yellow("\n [cancelled]"));
|
|
6061
|
+
this.showPrompt();
|
|
6062
|
+
} else {
|
|
6063
|
+
this.stop();
|
|
6064
|
+
}
|
|
6065
|
+
});
|
|
6066
|
+
await new Promise((resolve) => {
|
|
6067
|
+
const check = setInterval(() => {
|
|
6068
|
+
if (this.stopped) {
|
|
6069
|
+
clearInterval(check);
|
|
6070
|
+
resolve();
|
|
6071
|
+
}
|
|
6072
|
+
}, 100);
|
|
6073
|
+
});
|
|
6074
|
+
}
|
|
6075
|
+
stop() {
|
|
6076
|
+
if (this.stopped) return;
|
|
6077
|
+
this.stopped = true;
|
|
6078
|
+
console.log(chalk8.gray("\n Goodbye!\n"));
|
|
6079
|
+
this.rl.close();
|
|
6080
|
+
this.session.dispose();
|
|
6081
|
+
}
|
|
6082
|
+
/**
|
|
6083
|
+
* Get UI callbacks for the session.
|
|
6084
|
+
*/
|
|
6085
|
+
getCallbacks() {
|
|
6086
|
+
return {
|
|
6087
|
+
onTextDelta: (delta) => {
|
|
6088
|
+
if (!this.isStreaming) {
|
|
6089
|
+
this.isStreaming = true;
|
|
6090
|
+
this.currentStreamText = "";
|
|
6091
|
+
const ts = this.timestamp();
|
|
6092
|
+
process.stdout.write(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: `);
|
|
6093
|
+
}
|
|
6094
|
+
this.currentStreamText += delta;
|
|
6095
|
+
},
|
|
6096
|
+
onToolCall: (_id, action, params) => {
|
|
6097
|
+
if (this.isStreaming) {
|
|
6098
|
+
process.stdout.write("\n");
|
|
6099
|
+
this.isStreaming = false;
|
|
6100
|
+
}
|
|
6101
|
+
if (this.verbose) {
|
|
6102
|
+
const paramStr = params ? chalk8.gray(` ${JSON.stringify(params).slice(0, 80)}`) : "";
|
|
6103
|
+
console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)}${paramStr} ${chalk8.gray("...")}`);
|
|
6104
|
+
} else {
|
|
6105
|
+
console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)} ${chalk8.gray("...")}`);
|
|
6106
|
+
}
|
|
6107
|
+
},
|
|
6108
|
+
onToolResult: (_id, action, success2, data, error2) => {
|
|
6109
|
+
if (success2) {
|
|
6110
|
+
if (this.verbose) {
|
|
6111
|
+
const summary = data ? summarizeData(data) : "";
|
|
6112
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}${summary ? chalk8.gray(` \u2192 ${summary}`) : ""}`);
|
|
6113
|
+
} else {
|
|
6114
|
+
console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}`);
|
|
6115
|
+
}
|
|
6116
|
+
} else {
|
|
6117
|
+
console.log(` ${chalk8.red("\u2717")} ${chalk8.red(action)}: ${chalk8.red(error2 || "failed")}`);
|
|
6118
|
+
}
|
|
6119
|
+
},
|
|
6120
|
+
onAssistantMessage: (content) => {
|
|
6121
|
+
if (this.isStreaming) {
|
|
6122
|
+
process.stdout.write(renderMarkdown(this.currentStreamText) + "\n");
|
|
6123
|
+
this.isStreaming = false;
|
|
6124
|
+
} else if (content && content !== this.currentStreamText) {
|
|
6125
|
+
const ts = this.timestamp();
|
|
6126
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: ${renderMarkdown(content)}`);
|
|
6127
|
+
}
|
|
6128
|
+
this.currentStreamText = "";
|
|
6129
|
+
},
|
|
6130
|
+
onSuggestions: (suggestions) => {
|
|
6131
|
+
if (suggestions.length > 0) {
|
|
6132
|
+
console.log(chalk8.gray(" Suggestions:"));
|
|
6133
|
+
for (const s of suggestions) {
|
|
6134
|
+
console.log(chalk8.gray(` \u2022 ${s.title}`));
|
|
6135
|
+
}
|
|
6136
|
+
}
|
|
6137
|
+
},
|
|
6138
|
+
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
6139
|
+
const statusIcon = status === "confirmed" ? chalk8.green("\u2713") : status === "failed" ? chalk8.red("\u2717") : chalk8.yellow("\u23F3");
|
|
6140
|
+
console.log(` ${statusIcon} ${chalk8.bold("TX")} [${chain}]: ${txHash.slice(0, 12)}...${txHash.slice(-8)}`);
|
|
6141
|
+
if (explorerUrl) {
|
|
6142
|
+
console.log(` ${chalk8.blue.underline(explorerUrl)}`);
|
|
6143
|
+
}
|
|
6144
|
+
},
|
|
6145
|
+
onError: (message) => {
|
|
6146
|
+
if (this.isStreaming) {
|
|
6147
|
+
process.stdout.write("\n");
|
|
6148
|
+
this.isStreaming = false;
|
|
6149
|
+
}
|
|
6150
|
+
console.log(` ${chalk8.red("Error")}: ${message}`);
|
|
6151
|
+
},
|
|
6152
|
+
onDone: () => {
|
|
6153
|
+
if (this.isStreaming) {
|
|
6154
|
+
process.stdout.write(renderMarkdown(this.currentStreamText) + "\n");
|
|
6155
|
+
this.isStreaming = false;
|
|
6156
|
+
this.currentStreamText = "";
|
|
6157
|
+
}
|
|
6158
|
+
},
|
|
6159
|
+
requestPassword: async () => {
|
|
6160
|
+
return new Promise((resolve, reject) => {
|
|
6161
|
+
const rl2 = readline2.createInterface({
|
|
6162
|
+
input: process.stdin,
|
|
6163
|
+
output: process.stdout,
|
|
6164
|
+
terminal: true
|
|
6165
|
+
});
|
|
6166
|
+
if (process.stdin.isTTY) {
|
|
6167
|
+
process.stdout.write(chalk8.yellow(" \u{1F510} Enter vault password: "));
|
|
6168
|
+
const wasRaw = process.stdin.isRaw;
|
|
6169
|
+
process.stdin.setRawMode(true);
|
|
6170
|
+
let password = "";
|
|
6171
|
+
const onData = (key) => {
|
|
6172
|
+
const ch = key.toString();
|
|
6173
|
+
if (ch === "\r" || ch === "\n") {
|
|
6174
|
+
process.stdin.setRawMode(wasRaw || false);
|
|
6175
|
+
process.stdin.removeListener("data", onData);
|
|
6176
|
+
process.stdout.write("\n");
|
|
6177
|
+
rl2.close();
|
|
6178
|
+
resolve(password);
|
|
6179
|
+
} else if (ch === "") {
|
|
6180
|
+
process.stdin.setRawMode(wasRaw || false);
|
|
6181
|
+
process.stdin.removeListener("data", onData);
|
|
6182
|
+
rl2.close();
|
|
6183
|
+
reject(new Error("Password input cancelled"));
|
|
6184
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
6185
|
+
if (password.length > 0) {
|
|
6186
|
+
password = password.slice(0, -1);
|
|
6187
|
+
process.stdout.write("\b \b");
|
|
6188
|
+
}
|
|
6189
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
6190
|
+
password += ch;
|
|
6191
|
+
process.stdout.write("*");
|
|
6192
|
+
}
|
|
6193
|
+
};
|
|
6194
|
+
process.stdin.on("data", onData);
|
|
6195
|
+
} else {
|
|
6196
|
+
rl2.question("Password: ", (answer) => {
|
|
6197
|
+
rl2.close();
|
|
6198
|
+
resolve(answer.trim());
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
});
|
|
6202
|
+
},
|
|
6203
|
+
requestConfirmation: async (message) => {
|
|
6204
|
+
return new Promise((resolve) => {
|
|
6205
|
+
this.rl.question(chalk8.yellow(` ${message} (y/N): `), (answer) => {
|
|
6206
|
+
resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
|
|
6207
|
+
});
|
|
6208
|
+
});
|
|
6209
|
+
}
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
async handleMessage(content) {
|
|
6213
|
+
const callbacks = this.getCallbacks();
|
|
6214
|
+
this.isStreaming = false;
|
|
6215
|
+
try {
|
|
6216
|
+
await this.session.sendMessage(content, callbacks);
|
|
6217
|
+
} catch (err) {
|
|
6218
|
+
if (err.name === "AbortError") {
|
|
6219
|
+
console.log(chalk8.yellow(" [cancelled]"));
|
|
6220
|
+
} else {
|
|
6221
|
+
console.log(chalk8.red(` Error: ${err.message}`));
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
printHeader() {
|
|
6226
|
+
console.log("");
|
|
6227
|
+
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`));
|
|
6228
|
+
console.log(chalk8.bold.cyan(` \u2551`) + chalk8.bold(` Vultisig Agent - ${this.vaultName}`.padEnd(38).slice(0, 38)) + chalk8.bold.cyan(`\u2551`));
|
|
6229
|
+
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`));
|
|
6230
|
+
console.log("");
|
|
6231
|
+
}
|
|
6232
|
+
printHelp() {
|
|
6233
|
+
console.log(chalk8.gray(" Commands: /help, /clear, /quit"));
|
|
6234
|
+
console.log(chalk8.gray(" Press Ctrl+C to cancel a response, or to exit"));
|
|
6235
|
+
console.log("");
|
|
6236
|
+
}
|
|
6237
|
+
printUserMessage(content) {
|
|
6238
|
+
const ts = this.timestamp();
|
|
6239
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.green.bold("You")}: ${content}`);
|
|
6240
|
+
}
|
|
6241
|
+
showPrompt() {
|
|
6242
|
+
if (this.stopped) return;
|
|
6243
|
+
const prompt = chalk8.gray(`${this.timestamp()} `) + chalk8.green.bold("You") + ": ";
|
|
6244
|
+
this.rl.setPrompt(prompt);
|
|
6245
|
+
this.rl.prompt();
|
|
6246
|
+
}
|
|
6247
|
+
timestamp() {
|
|
6248
|
+
const now = /* @__PURE__ */ new Date();
|
|
6249
|
+
return `[${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}]`;
|
|
6250
|
+
}
|
|
6251
|
+
};
|
|
6252
|
+
function renderMarkdown(text) {
|
|
6253
|
+
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})`)}`);
|
|
6254
|
+
}
|
|
6255
|
+
function summarizeData(data) {
|
|
6256
|
+
if (data.balances && Array.isArray(data.balances)) {
|
|
6257
|
+
const balances = data.balances;
|
|
6258
|
+
if (balances.length === 1) {
|
|
6259
|
+
return `${balances[0].amount} ${balances[0].symbol}`;
|
|
6260
|
+
}
|
|
6261
|
+
return `${balances.length} balances`;
|
|
6262
|
+
}
|
|
6263
|
+
if (data.tx_hash) {
|
|
6264
|
+
return `tx: ${data.tx_hash.slice(0, 12)}...`;
|
|
6265
|
+
}
|
|
6266
|
+
if (data.added) return "added";
|
|
6267
|
+
if (data.removed) return "removed";
|
|
6268
|
+
if (data.message) return data.message;
|
|
6269
|
+
return "";
|
|
6270
|
+
}
|
|
6271
|
+
|
|
6272
|
+
// src/commands/agent.ts
|
|
6273
|
+
async function executeAgent(ctx2, options) {
|
|
6274
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6275
|
+
const config = {
|
|
6276
|
+
backendUrl: options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998",
|
|
6277
|
+
vaultName: vault.name,
|
|
6278
|
+
password: options.password,
|
|
6279
|
+
viaAgent: options.viaAgent,
|
|
6280
|
+
conversationId: options.conversationId,
|
|
6281
|
+
verbose: options.verbose
|
|
6282
|
+
};
|
|
6283
|
+
const session = new AgentSession(vault, config);
|
|
6284
|
+
if (options.viaAgent) {
|
|
6285
|
+
const pipe = new PipeInterface(session);
|
|
6286
|
+
const callbacks = pipe.getCallbacks();
|
|
6287
|
+
try {
|
|
6288
|
+
await session.initialize(callbacks);
|
|
6289
|
+
const addresses = session.getVaultAddresses();
|
|
6290
|
+
await pipe.start(vault.name, addresses);
|
|
6291
|
+
} catch (err) {
|
|
6292
|
+
process.stdout.write(JSON.stringify({ type: "error", message: err.message }) + "\n");
|
|
6293
|
+
process.exit(1);
|
|
6294
|
+
}
|
|
6295
|
+
} else {
|
|
6296
|
+
const tui = new ChatTUI(session, vault.name, config.verbose);
|
|
6297
|
+
const callbacks = tui.getCallbacks();
|
|
6298
|
+
try {
|
|
6299
|
+
await session.initialize(callbacks);
|
|
6300
|
+
await tui.start();
|
|
6301
|
+
} catch (err) {
|
|
6302
|
+
console.error(`Agent error: ${err.message}`);
|
|
6303
|
+
process.exit(1);
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
|
|
6308
|
+
// src/interactive/completer.ts
|
|
6309
|
+
import { Chain as Chain10 } from "@vultisig/sdk";
|
|
6310
|
+
import fs2 from "fs";
|
|
6311
|
+
import path2 from "path";
|
|
6312
|
+
var COMMANDS = [
|
|
6313
|
+
// Vault management
|
|
6314
|
+
"vaults",
|
|
6315
|
+
"vault",
|
|
6316
|
+
"import",
|
|
6317
|
+
"delete",
|
|
6318
|
+
"create-from-seedphrase",
|
|
6319
|
+
"create",
|
|
6320
|
+
"join",
|
|
6321
|
+
"info",
|
|
6322
|
+
"export",
|
|
6323
|
+
// Wallet operations
|
|
6324
|
+
"balance",
|
|
6325
|
+
"bal",
|
|
6326
|
+
"send",
|
|
6327
|
+
"tx-status",
|
|
6328
|
+
"portfolio",
|
|
6329
|
+
"addresses",
|
|
6330
|
+
"chains",
|
|
6331
|
+
"tokens",
|
|
6332
|
+
// Swap operations
|
|
6333
|
+
"swap-chains",
|
|
6334
|
+
"swap-quote",
|
|
6335
|
+
"swap",
|
|
6336
|
+
// Session commands (shell-only)
|
|
6337
|
+
"lock",
|
|
6338
|
+
"unlock",
|
|
6339
|
+
"status",
|
|
6340
|
+
// Settings
|
|
6341
|
+
"currency",
|
|
6342
|
+
"server",
|
|
6343
|
+
"address-book",
|
|
6344
|
+
// Help
|
|
6345
|
+
"help",
|
|
6346
|
+
"?",
|
|
6347
|
+
// REPL commands
|
|
6348
|
+
".help",
|
|
6349
|
+
".clear",
|
|
6350
|
+
".exit"
|
|
6351
|
+
];
|
|
6352
|
+
function createCompleter(ctx2) {
|
|
6353
|
+
return function completer(line) {
|
|
6354
|
+
try {
|
|
6355
|
+
const parts = line.split(/\s+/);
|
|
6356
|
+
const command = parts[0]?.toLowerCase();
|
|
6357
|
+
if ((command === "import" || command === "export") && parts.length > 1) {
|
|
6358
|
+
const partial = parts.slice(1).join(" ");
|
|
6359
|
+
return completeFilePath(partial, command === "import");
|
|
6360
|
+
}
|
|
6361
|
+
if (command === "vault" && parts.length > 1) {
|
|
6362
|
+
const partial = parts.slice(1).join(" ");
|
|
6363
|
+
return completeVaultName(ctx2, partial);
|
|
6364
|
+
}
|
|
6365
|
+
if (command === "chains" && parts.length >= 2) {
|
|
6366
|
+
const lastPart = parts[parts.length - 1] || "";
|
|
6367
|
+
const lastPartLower = lastPart.toLowerCase();
|
|
6368
|
+
if (lastPartLower.startsWith("-")) {
|
|
6369
|
+
const flags = ["--add", "--add-all", "--remove"];
|
|
6370
|
+
const matches = flags.filter((f) => f.startsWith(lastPartLower));
|
|
6371
|
+
return [matches.length ? matches : flags, lastPart];
|
|
6372
|
+
}
|
|
6373
|
+
const flag = parts[parts.length - 2]?.toLowerCase();
|
|
6374
|
+
if (flag === "--add" || flag === "--remove") {
|
|
6375
|
+
return completeChainName(lastPart);
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
if (["balance", "bal", "tokens", "send", "swap", "swap-quote", "tx-status"].includes(command) && parts.length === 2) {
|
|
6379
|
+
const partial = parts[1] || "";
|
|
6380
|
+
return completeChainName(partial);
|
|
6381
|
+
}
|
|
6382
|
+
if ((command === "create" || command === "create-from-seedphrase") && parts.length === 2) {
|
|
6383
|
+
const types = ["fast", "secure"];
|
|
6384
|
+
const partial = parts[1] || "";
|
|
6385
|
+
const partialLower = partial.toLowerCase();
|
|
6386
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
6387
|
+
return [matches.length ? matches : types, partial];
|
|
6388
|
+
}
|
|
6389
|
+
if (command === "join" && parts.length === 2) {
|
|
6390
|
+
const types = ["secure"];
|
|
6391
|
+
const partial = parts[1] || "";
|
|
6392
|
+
const partialLower = partial.toLowerCase();
|
|
6393
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
6394
|
+
return [matches.length ? matches : types, partial];
|
|
6395
|
+
}
|
|
6396
|
+
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
6397
|
+
const show = hits.length ? hits : COMMANDS;
|
|
6398
|
+
return [show, line];
|
|
6399
|
+
} catch {
|
|
6400
|
+
return [[], line];
|
|
6401
|
+
}
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
function completeFilePath(partial, filterVult) {
|
|
6405
|
+
try {
|
|
6406
|
+
const endsWithSeparator = partial.endsWith("/") || partial.endsWith(path2.sep);
|
|
6407
|
+
let dir;
|
|
6408
|
+
let basename;
|
|
6409
|
+
if (endsWithSeparator) {
|
|
6410
|
+
dir = partial;
|
|
6411
|
+
basename = "";
|
|
6412
|
+
} else {
|
|
6413
|
+
dir = path2.dirname(partial);
|
|
6414
|
+
basename = path2.basename(partial);
|
|
6415
|
+
if (fs2.existsSync(partial) && fs2.statSync(partial).isDirectory()) {
|
|
6416
|
+
dir = partial;
|
|
6417
|
+
basename = "";
|
|
6418
|
+
}
|
|
6419
|
+
}
|
|
6420
|
+
const resolvedDir = path2.resolve(dir);
|
|
6421
|
+
if (!fs2.existsSync(resolvedDir) || !fs2.statSync(resolvedDir).isDirectory()) {
|
|
6422
|
+
return [[], partial];
|
|
6423
|
+
}
|
|
6424
|
+
const files = fs2.readdirSync(resolvedDir);
|
|
6425
|
+
const matches = files.filter((file) => file.startsWith(basename)).map((file) => {
|
|
6426
|
+
const fullPath = path2.join(dir, file);
|
|
6427
|
+
const stats = fs2.statSync(path2.join(resolvedDir, file));
|
|
6428
|
+
if (stats.isDirectory()) {
|
|
6429
|
+
return fullPath + "/";
|
|
6430
|
+
}
|
|
6431
|
+
if (filterVult) {
|
|
6432
|
+
if (file.endsWith(".vult") || stats.isDirectory()) {
|
|
3823
6433
|
return fullPath;
|
|
3824
6434
|
}
|
|
3825
6435
|
return null;
|
|
@@ -3840,7 +6450,7 @@ function completeVaultName(ctx2, partial) {
|
|
|
3840
6450
|
return [show, partial];
|
|
3841
6451
|
}
|
|
3842
6452
|
function completeChainName(partial) {
|
|
3843
|
-
const allChains = Object.values(
|
|
6453
|
+
const allChains = Object.values(Chain10);
|
|
3844
6454
|
const partialLower = partial.toLowerCase();
|
|
3845
6455
|
const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
|
|
3846
6456
|
matches.sort();
|
|
@@ -3848,14 +6458,14 @@ function completeChainName(partial) {
|
|
|
3848
6458
|
return [show, partial];
|
|
3849
6459
|
}
|
|
3850
6460
|
function findChainByName(name) {
|
|
3851
|
-
const allChains = Object.values(
|
|
6461
|
+
const allChains = Object.values(Chain10);
|
|
3852
6462
|
const nameLower = name.toLowerCase();
|
|
3853
6463
|
const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
|
|
3854
6464
|
return found ? found : null;
|
|
3855
6465
|
}
|
|
3856
6466
|
|
|
3857
6467
|
// src/interactive/event-buffer.ts
|
|
3858
|
-
import
|
|
6468
|
+
import chalk9 from "chalk";
|
|
3859
6469
|
var EventBuffer = class {
|
|
3860
6470
|
eventBuffer = [];
|
|
3861
6471
|
isCommandRunning = false;
|
|
@@ -3895,17 +6505,17 @@ var EventBuffer = class {
|
|
|
3895
6505
|
displayEvent(message, type) {
|
|
3896
6506
|
switch (type) {
|
|
3897
6507
|
case "success":
|
|
3898
|
-
console.log(
|
|
6508
|
+
console.log(chalk9.green(message));
|
|
3899
6509
|
break;
|
|
3900
6510
|
case "warning":
|
|
3901
|
-
console.log(
|
|
6511
|
+
console.log(chalk9.yellow(message));
|
|
3902
6512
|
break;
|
|
3903
6513
|
case "error":
|
|
3904
|
-
console.error(
|
|
6514
|
+
console.error(chalk9.red(message));
|
|
3905
6515
|
break;
|
|
3906
6516
|
case "info":
|
|
3907
6517
|
default:
|
|
3908
|
-
console.log(
|
|
6518
|
+
console.log(chalk9.blue(message));
|
|
3909
6519
|
break;
|
|
3910
6520
|
}
|
|
3911
6521
|
}
|
|
@@ -3916,13 +6526,13 @@ var EventBuffer = class {
|
|
|
3916
6526
|
if (this.eventBuffer.length === 0) {
|
|
3917
6527
|
return;
|
|
3918
6528
|
}
|
|
3919
|
-
console.log(
|
|
6529
|
+
console.log(chalk9.gray("\n--- Background Events ---"));
|
|
3920
6530
|
this.eventBuffer.forEach((event) => {
|
|
3921
6531
|
const timeStr = event.timestamp.toLocaleTimeString();
|
|
3922
6532
|
const message = `[${timeStr}] ${event.message}`;
|
|
3923
6533
|
this.displayEvent(message, event.type);
|
|
3924
6534
|
});
|
|
3925
|
-
console.log(
|
|
6535
|
+
console.log(chalk9.gray("--- End Events ---\n"));
|
|
3926
6536
|
}
|
|
3927
6537
|
/**
|
|
3928
6538
|
* Setup all vault event listeners
|
|
@@ -3939,6 +6549,12 @@ var EventBuffer = class {
|
|
|
3939
6549
|
this.handleEvent(`+ Transaction broadcast on ${chain}`, "success");
|
|
3940
6550
|
this.handleEvent(` TX Hash: ${txHash}`, "info");
|
|
3941
6551
|
});
|
|
6552
|
+
vault.on("transactionConfirmed", ({ chain, txHash }) => {
|
|
6553
|
+
this.handleEvent(`+ Transaction confirmed on ${chain}: ${txHash}`, "success");
|
|
6554
|
+
});
|
|
6555
|
+
vault.on("transactionFailed", ({ chain, txHash }) => {
|
|
6556
|
+
this.handleEvent(`x Transaction failed on ${chain}: ${txHash}`, "error");
|
|
6557
|
+
});
|
|
3942
6558
|
vault.on("signingProgress", ({ step }) => {
|
|
3943
6559
|
this.handleEvent(`i Signing: ${step}`, "info");
|
|
3944
6560
|
});
|
|
@@ -4002,6 +6618,8 @@ var EventBuffer = class {
|
|
|
4002
6618
|
vault.removeAllListeners("balanceUpdated");
|
|
4003
6619
|
vault.removeAllListeners("transactionSigned");
|
|
4004
6620
|
vault.removeAllListeners("transactionBroadcast");
|
|
6621
|
+
vault.removeAllListeners("transactionConfirmed");
|
|
6622
|
+
vault.removeAllListeners("transactionFailed");
|
|
4005
6623
|
vault.removeAllListeners("signingProgress");
|
|
4006
6624
|
vault.removeAllListeners("chainAdded");
|
|
4007
6625
|
vault.removeAllListeners("chainRemoved");
|
|
@@ -4024,12 +6642,12 @@ var EventBuffer = class {
|
|
|
4024
6642
|
|
|
4025
6643
|
// src/interactive/session.ts
|
|
4026
6644
|
import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
|
|
4027
|
-
import
|
|
6645
|
+
import chalk11 from "chalk";
|
|
4028
6646
|
import ora3 from "ora";
|
|
4029
|
-
import * as
|
|
6647
|
+
import * as readline3 from "readline";
|
|
4030
6648
|
|
|
4031
6649
|
// src/interactive/shell-commands.ts
|
|
4032
|
-
import
|
|
6650
|
+
import chalk10 from "chalk";
|
|
4033
6651
|
import Table from "cli-table3";
|
|
4034
6652
|
import inquirer6 from "inquirer";
|
|
4035
6653
|
import ora2 from "ora";
|
|
@@ -4046,25 +6664,25 @@ function formatTimeRemaining(ms) {
|
|
|
4046
6664
|
async function executeLock(ctx2) {
|
|
4047
6665
|
const vault = ctx2.getActiveVault();
|
|
4048
6666
|
if (!vault) {
|
|
4049
|
-
console.log(
|
|
4050
|
-
console.log(
|
|
6667
|
+
console.log(chalk10.red("No active vault."));
|
|
6668
|
+
console.log(chalk10.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
4051
6669
|
return;
|
|
4052
6670
|
}
|
|
4053
6671
|
ctx2.lockVault(vault.id);
|
|
4054
|
-
console.log(
|
|
4055
|
-
console.log(
|
|
6672
|
+
console.log(chalk10.green("\n+ Vault locked"));
|
|
6673
|
+
console.log(chalk10.gray("Password cache cleared. You will need to enter the password again."));
|
|
4056
6674
|
}
|
|
4057
6675
|
async function executeUnlock(ctx2) {
|
|
4058
6676
|
const vault = ctx2.getActiveVault();
|
|
4059
6677
|
if (!vault) {
|
|
4060
|
-
console.log(
|
|
4061
|
-
console.log(
|
|
6678
|
+
console.log(chalk10.red("No active vault."));
|
|
6679
|
+
console.log(chalk10.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
4062
6680
|
return;
|
|
4063
6681
|
}
|
|
4064
6682
|
if (ctx2.isVaultUnlocked(vault.id)) {
|
|
4065
6683
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
4066
|
-
console.log(
|
|
4067
|
-
console.log(
|
|
6684
|
+
console.log(chalk10.yellow("\nVault is already unlocked."));
|
|
6685
|
+
console.log(chalk10.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
4068
6686
|
return;
|
|
4069
6687
|
}
|
|
4070
6688
|
const { password } = await inquirer6.prompt([
|
|
@@ -4081,19 +6699,19 @@ async function executeUnlock(ctx2) {
|
|
|
4081
6699
|
ctx2.cachePassword(vault.id, password);
|
|
4082
6700
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
4083
6701
|
spinner.succeed("Vault unlocked");
|
|
4084
|
-
console.log(
|
|
6702
|
+
console.log(chalk10.green(`
|
|
4085
6703
|
+ Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
|
|
4086
6704
|
} catch (err) {
|
|
4087
6705
|
spinner.fail("Failed to unlock vault");
|
|
4088
|
-
console.error(
|
|
6706
|
+
console.error(chalk10.red(`
|
|
4089
6707
|
x ${err.message}`));
|
|
4090
6708
|
}
|
|
4091
6709
|
}
|
|
4092
6710
|
async function executeStatus(ctx2) {
|
|
4093
6711
|
const vault = ctx2.getActiveVault();
|
|
4094
6712
|
if (!vault) {
|
|
4095
|
-
console.log(
|
|
4096
|
-
console.log(
|
|
6713
|
+
console.log(chalk10.red("No active vault."));
|
|
6714
|
+
console.log(chalk10.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
4097
6715
|
return;
|
|
4098
6716
|
}
|
|
4099
6717
|
const isUnlocked = ctx2.isVaultUnlocked(vault.id);
|
|
@@ -4124,30 +6742,30 @@ async function executeStatus(ctx2) {
|
|
|
4124
6742
|
displayStatus(status);
|
|
4125
6743
|
}
|
|
4126
6744
|
function displayStatus(status) {
|
|
4127
|
-
console.log(
|
|
4128
|
-
console.log(
|
|
4129
|
-
console.log(
|
|
4130
|
-
console.log(
|
|
4131
|
-
console.log(` Name: ${
|
|
6745
|
+
console.log(chalk10.cyan("\n+----------------------------------------+"));
|
|
6746
|
+
console.log(chalk10.cyan("| Vault Status |"));
|
|
6747
|
+
console.log(chalk10.cyan("+----------------------------------------+\n"));
|
|
6748
|
+
console.log(chalk10.bold("Vault:"));
|
|
6749
|
+
console.log(` Name: ${chalk10.green(status.name)}`);
|
|
4132
6750
|
console.log(` ID: ${status.id}`);
|
|
4133
|
-
console.log(` Type: ${
|
|
4134
|
-
console.log(
|
|
6751
|
+
console.log(` Type: ${chalk10.yellow(status.type)}`);
|
|
6752
|
+
console.log(chalk10.bold("\nSecurity:"));
|
|
4135
6753
|
if (status.isUnlocked) {
|
|
4136
|
-
console.log(` Status: ${
|
|
6754
|
+
console.log(` Status: ${chalk10.green("Unlocked")} ${chalk10.green("\u{1F513}")}`);
|
|
4137
6755
|
console.log(` Expires: ${status.timeRemainingFormatted}`);
|
|
4138
6756
|
} else {
|
|
4139
|
-
console.log(` Status: ${
|
|
6757
|
+
console.log(` Status: ${chalk10.yellow("Locked")} ${chalk10.yellow("\u{1F512}")}`);
|
|
4140
6758
|
}
|
|
4141
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
4142
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
4143
|
-
console.log(
|
|
6759
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk10.green("Yes") : chalk10.gray("No")}`);
|
|
6760
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk10.green("Yes") : chalk10.yellow("No")}`);
|
|
6761
|
+
console.log(chalk10.bold("\nMPC Configuration:"));
|
|
4144
6762
|
console.log(` Library: ${status.libType}`);
|
|
4145
|
-
console.log(` Threshold: ${
|
|
4146
|
-
console.log(
|
|
6763
|
+
console.log(` Threshold: ${chalk10.cyan(status.threshold)} of ${chalk10.cyan(status.totalSigners)}`);
|
|
6764
|
+
console.log(chalk10.bold("\nSigning Modes:"));
|
|
4147
6765
|
status.availableSigningModes.forEach((mode) => {
|
|
4148
6766
|
console.log(` - ${mode}`);
|
|
4149
6767
|
});
|
|
4150
|
-
console.log(
|
|
6768
|
+
console.log(chalk10.bold("\nDetails:"));
|
|
4151
6769
|
console.log(` Chains: ${status.chains}`);
|
|
4152
6770
|
console.log(` Currency: ${status.currency.toUpperCase()}`);
|
|
4153
6771
|
console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
|
|
@@ -4156,7 +6774,7 @@ function displayStatus(status) {
|
|
|
4156
6774
|
}
|
|
4157
6775
|
function showHelp() {
|
|
4158
6776
|
const table = new Table({
|
|
4159
|
-
head: [
|
|
6777
|
+
head: [chalk10.bold("Available Commands")],
|
|
4160
6778
|
colWidths: [50],
|
|
4161
6779
|
chars: {
|
|
4162
6780
|
mid: "",
|
|
@@ -4170,7 +6788,7 @@ function showHelp() {
|
|
|
4170
6788
|
}
|
|
4171
6789
|
});
|
|
4172
6790
|
table.push(
|
|
4173
|
-
[
|
|
6791
|
+
[chalk10.bold("Vault Management:")],
|
|
4174
6792
|
[" vaults - List all vaults"],
|
|
4175
6793
|
[" vault <name> - Switch to vault"],
|
|
4176
6794
|
[" import <file> - Import vault from file"],
|
|
@@ -4179,30 +6797,31 @@ function showHelp() {
|
|
|
4179
6797
|
[" info - Show vault details"],
|
|
4180
6798
|
[" export [path] - Export vault to file"],
|
|
4181
6799
|
[""],
|
|
4182
|
-
[
|
|
6800
|
+
[chalk10.bold("Wallet Operations:")],
|
|
4183
6801
|
[" balance [chain] - Show balances"],
|
|
4184
6802
|
[" send <chain> <to> <amount> - Send transaction"],
|
|
6803
|
+
[" tx-status <chain> <txHash> - Check transaction status"],
|
|
4185
6804
|
[" portfolio [-c usd] - Show portfolio value"],
|
|
4186
6805
|
[" addresses - Show all addresses"],
|
|
4187
6806
|
[" chains [--add/--remove/--add-all] - Manage chains"],
|
|
4188
6807
|
[" tokens <chain> - Manage tokens"],
|
|
4189
6808
|
[""],
|
|
4190
|
-
[
|
|
6809
|
+
[chalk10.bold("Swap Operations:")],
|
|
4191
6810
|
[" swap-chains - List swap-enabled chains"],
|
|
4192
6811
|
[" swap-quote <from> <to> <amount> - Get quote"],
|
|
4193
6812
|
[" swap <from> <to> <amount> - Execute swap"],
|
|
4194
6813
|
[""],
|
|
4195
|
-
[
|
|
6814
|
+
[chalk10.bold("Session Commands (shell only):")],
|
|
4196
6815
|
[" lock - Lock vault"],
|
|
4197
6816
|
[" unlock - Unlock vault"],
|
|
4198
6817
|
[" status - Show vault status"],
|
|
4199
6818
|
[""],
|
|
4200
|
-
[
|
|
6819
|
+
[chalk10.bold("Settings:")],
|
|
4201
6820
|
[" currency [code] - View/set currency"],
|
|
4202
6821
|
[" server - Check server status"],
|
|
4203
6822
|
[" address-book - Manage saved addresses"],
|
|
4204
6823
|
[""],
|
|
4205
|
-
[
|
|
6824
|
+
[chalk10.bold("Help & Navigation:")],
|
|
4206
6825
|
[" help, ? - Show this help"],
|
|
4207
6826
|
[" .clear - Clear screen"],
|
|
4208
6827
|
[" .exit - Exit shell"]
|
|
@@ -4340,12 +6959,12 @@ var ShellSession = class {
|
|
|
4340
6959
|
*/
|
|
4341
6960
|
async start() {
|
|
4342
6961
|
console.clear();
|
|
4343
|
-
console.log(
|
|
4344
|
-
console.log(
|
|
4345
|
-
console.log(
|
|
6962
|
+
console.log(chalk11.cyan.bold("\n=============================================="));
|
|
6963
|
+
console.log(chalk11.cyan.bold(" Vultisig Interactive Shell"));
|
|
6964
|
+
console.log(chalk11.cyan.bold("==============================================\n"));
|
|
4346
6965
|
await this.loadAllVaults();
|
|
4347
6966
|
this.displayVaultList();
|
|
4348
|
-
console.log(
|
|
6967
|
+
console.log(chalk11.gray('Type "help" for available commands, "exit" to quit\n'));
|
|
4349
6968
|
this.promptLoop().catch(() => {
|
|
4350
6969
|
});
|
|
4351
6970
|
}
|
|
@@ -4363,7 +6982,7 @@ var ShellSession = class {
|
|
|
4363
6982
|
*/
|
|
4364
6983
|
readLine(prompt) {
|
|
4365
6984
|
return new Promise((resolve) => {
|
|
4366
|
-
const rl =
|
|
6985
|
+
const rl = readline3.createInterface({
|
|
4367
6986
|
input: process.stdin,
|
|
4368
6987
|
output: process.stdout,
|
|
4369
6988
|
completer: (line, cb) => {
|
|
@@ -4379,12 +6998,12 @@ var ShellSession = class {
|
|
|
4379
6998
|
const now = Date.now();
|
|
4380
6999
|
if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
|
|
4381
7000
|
rl.close();
|
|
4382
|
-
console.log(
|
|
7001
|
+
console.log(chalk11.yellow("\nGoodbye!"));
|
|
4383
7002
|
this.ctx.dispose();
|
|
4384
7003
|
process.exit(0);
|
|
4385
7004
|
}
|
|
4386
7005
|
this.lastSigintTime = now;
|
|
4387
|
-
console.log(
|
|
7006
|
+
console.log(chalk11.yellow("\n(Press Ctrl+C again to exit)"));
|
|
4388
7007
|
rl.close();
|
|
4389
7008
|
resolve("");
|
|
4390
7009
|
});
|
|
@@ -4396,7 +7015,7 @@ var ShellSession = class {
|
|
|
4396
7015
|
prompt(message, defaultValue) {
|
|
4397
7016
|
return new Promise((resolve, reject) => {
|
|
4398
7017
|
const displayPrompt = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
4399
|
-
const rl =
|
|
7018
|
+
const rl = readline3.createInterface({
|
|
4400
7019
|
input: process.stdin,
|
|
4401
7020
|
output: process.stdout,
|
|
4402
7021
|
terminal: true
|
|
@@ -4416,7 +7035,7 @@ var ShellSession = class {
|
|
|
4416
7035
|
*/
|
|
4417
7036
|
promptPassword(message) {
|
|
4418
7037
|
return new Promise((resolve, reject) => {
|
|
4419
|
-
const rl =
|
|
7038
|
+
const rl = readline3.createInterface({
|
|
4420
7039
|
input: process.stdin,
|
|
4421
7040
|
output: process.stdout,
|
|
4422
7041
|
terminal: true
|
|
@@ -4479,7 +7098,7 @@ var ShellSession = class {
|
|
|
4479
7098
|
stopAllSpinners();
|
|
4480
7099
|
process.stdout.write("\x1B[?25h");
|
|
4481
7100
|
process.stdout.write("\r\x1B[K");
|
|
4482
|
-
console.log(
|
|
7101
|
+
console.log(chalk11.yellow("\nCancelling operation..."));
|
|
4483
7102
|
};
|
|
4484
7103
|
const cleanup = () => {
|
|
4485
7104
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -4516,10 +7135,10 @@ var ShellSession = class {
|
|
|
4516
7135
|
stopAllSpinners();
|
|
4517
7136
|
process.stdout.write("\x1B[?25h");
|
|
4518
7137
|
process.stdout.write("\r\x1B[K");
|
|
4519
|
-
console.log(
|
|
7138
|
+
console.log(chalk11.yellow("Operation cancelled"));
|
|
4520
7139
|
return;
|
|
4521
7140
|
}
|
|
4522
|
-
console.error(
|
|
7141
|
+
console.error(chalk11.red(`
|
|
4523
7142
|
Error: ${error2.message}`));
|
|
4524
7143
|
}
|
|
4525
7144
|
}
|
|
@@ -4552,7 +7171,7 @@ Error: ${error2.message}`));
|
|
|
4552
7171
|
break;
|
|
4553
7172
|
case "rename":
|
|
4554
7173
|
if (args.length === 0) {
|
|
4555
|
-
console.log(
|
|
7174
|
+
console.log(chalk11.yellow("Usage: rename <newName>"));
|
|
4556
7175
|
return;
|
|
4557
7176
|
}
|
|
4558
7177
|
await executeRename(this.ctx, args.join(" "));
|
|
@@ -4572,6 +7191,9 @@ Error: ${error2.message}`));
|
|
|
4572
7191
|
case "send":
|
|
4573
7192
|
await this.runSend(args);
|
|
4574
7193
|
break;
|
|
7194
|
+
case "tx-status":
|
|
7195
|
+
await this.runTxStatus(args);
|
|
7196
|
+
break;
|
|
4575
7197
|
// Chain management
|
|
4576
7198
|
case "addresses":
|
|
4577
7199
|
await executeAddresses(this.ctx);
|
|
@@ -4625,41 +7247,41 @@ Error: ${error2.message}`));
|
|
|
4625
7247
|
// Exit
|
|
4626
7248
|
case "exit":
|
|
4627
7249
|
case "quit":
|
|
4628
|
-
console.log(
|
|
7250
|
+
console.log(chalk11.yellow("\nGoodbye!"));
|
|
4629
7251
|
this.ctx.dispose();
|
|
4630
7252
|
process.exit(0);
|
|
4631
7253
|
break;
|
|
4632
7254
|
// eslint requires break even after process.exit
|
|
4633
7255
|
default:
|
|
4634
|
-
console.log(
|
|
4635
|
-
console.log(
|
|
7256
|
+
console.log(chalk11.yellow(`Unknown command: ${command}`));
|
|
7257
|
+
console.log(chalk11.gray('Type "help" for available commands'));
|
|
4636
7258
|
break;
|
|
4637
7259
|
}
|
|
4638
7260
|
}
|
|
4639
7261
|
// ===== Command Helpers =====
|
|
4640
7262
|
async switchVault(args) {
|
|
4641
7263
|
if (args.length === 0) {
|
|
4642
|
-
console.log(
|
|
4643
|
-
console.log(
|
|
7264
|
+
console.log(chalk11.yellow("Usage: vault <name>"));
|
|
7265
|
+
console.log(chalk11.gray('Run "vaults" to see available vaults'));
|
|
4644
7266
|
return;
|
|
4645
7267
|
}
|
|
4646
7268
|
const vaultName = args.join(" ");
|
|
4647
7269
|
const vault = this.ctx.findVaultByName(vaultName);
|
|
4648
7270
|
if (!vault) {
|
|
4649
|
-
console.log(
|
|
4650
|
-
console.log(
|
|
7271
|
+
console.log(chalk11.red(`Vault not found: ${vaultName}`));
|
|
7272
|
+
console.log(chalk11.gray('Run "vaults" to see available vaults'));
|
|
4651
7273
|
return;
|
|
4652
7274
|
}
|
|
4653
7275
|
await this.ctx.setActiveVault(vault);
|
|
4654
|
-
console.log(
|
|
7276
|
+
console.log(chalk11.green(`
|
|
4655
7277
|
+ Switched to: ${vault.name}`));
|
|
4656
7278
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4657
|
-
const status = isUnlocked ?
|
|
7279
|
+
const status = isUnlocked ? chalk11.green("Unlocked") : chalk11.yellow("Locked");
|
|
4658
7280
|
console.log(`Status: ${status}`);
|
|
4659
7281
|
}
|
|
4660
7282
|
async importVault(args) {
|
|
4661
7283
|
if (args.length === 0) {
|
|
4662
|
-
console.log(
|
|
7284
|
+
console.log(chalk11.yellow("Usage: import <file>"));
|
|
4663
7285
|
return;
|
|
4664
7286
|
}
|
|
4665
7287
|
const filePath = args.join(" ");
|
|
@@ -4674,45 +7296,45 @@ Error: ${error2.message}`));
|
|
|
4674
7296
|
async createVault(args) {
|
|
4675
7297
|
const type = args[0]?.toLowerCase();
|
|
4676
7298
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4677
|
-
console.log(
|
|
4678
|
-
console.log(
|
|
4679
|
-
console.log(
|
|
7299
|
+
console.log(chalk11.yellow("Usage: create <fast|secure>"));
|
|
7300
|
+
console.log(chalk11.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
7301
|
+
console.log(chalk11.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
4680
7302
|
return;
|
|
4681
7303
|
}
|
|
4682
7304
|
let vault;
|
|
4683
7305
|
if (type === "fast") {
|
|
4684
7306
|
const name = await this.prompt("Vault name");
|
|
4685
7307
|
if (!name) {
|
|
4686
|
-
console.log(
|
|
7308
|
+
console.log(chalk11.red("Name is required"));
|
|
4687
7309
|
return;
|
|
4688
7310
|
}
|
|
4689
7311
|
const password = await this.promptPassword("Vault password");
|
|
4690
7312
|
if (!password) {
|
|
4691
|
-
console.log(
|
|
7313
|
+
console.log(chalk11.red("Password is required"));
|
|
4692
7314
|
return;
|
|
4693
7315
|
}
|
|
4694
7316
|
const email = await this.prompt("Email for verification");
|
|
4695
7317
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4696
|
-
console.log(
|
|
7318
|
+
console.log(chalk11.red("Valid email is required"));
|
|
4697
7319
|
return;
|
|
4698
7320
|
}
|
|
4699
7321
|
vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
|
|
4700
7322
|
} else {
|
|
4701
7323
|
const name = await this.prompt("Vault name");
|
|
4702
7324
|
if (!name) {
|
|
4703
|
-
console.log(
|
|
7325
|
+
console.log(chalk11.red("Name is required"));
|
|
4704
7326
|
return;
|
|
4705
7327
|
}
|
|
4706
7328
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4707
7329
|
const shares = parseInt(sharesStr, 10);
|
|
4708
7330
|
if (isNaN(shares) || shares < 2) {
|
|
4709
|
-
console.log(
|
|
7331
|
+
console.log(chalk11.red("Must have at least 2 shares"));
|
|
4710
7332
|
return;
|
|
4711
7333
|
}
|
|
4712
7334
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4713
7335
|
const threshold = parseInt(thresholdStr, 10);
|
|
4714
7336
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4715
|
-
console.log(
|
|
7337
|
+
console.log(chalk11.red(`Threshold must be between 1 and ${shares}`));
|
|
4716
7338
|
return;
|
|
4717
7339
|
}
|
|
4718
7340
|
const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
|
|
@@ -4734,37 +7356,37 @@ Error: ${error2.message}`));
|
|
|
4734
7356
|
async importSeedphrase(args) {
|
|
4735
7357
|
const type = args[0]?.toLowerCase();
|
|
4736
7358
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4737
|
-
console.log(
|
|
4738
|
-
console.log(
|
|
4739
|
-
console.log(
|
|
7359
|
+
console.log(chalk11.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
7360
|
+
console.log(chalk11.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
7361
|
+
console.log(chalk11.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4740
7362
|
return;
|
|
4741
7363
|
}
|
|
4742
|
-
console.log(
|
|
7364
|
+
console.log(chalk11.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
4743
7365
|
const mnemonic = await this.promptPassword("Seedphrase");
|
|
4744
7366
|
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
4745
7367
|
if (!validation.valid) {
|
|
4746
|
-
console.log(
|
|
7368
|
+
console.log(chalk11.red(`Invalid seedphrase: ${validation.error}`));
|
|
4747
7369
|
if (validation.invalidWords?.length) {
|
|
4748
|
-
console.log(
|
|
7370
|
+
console.log(chalk11.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4749
7371
|
}
|
|
4750
7372
|
return;
|
|
4751
7373
|
}
|
|
4752
|
-
console.log(
|
|
7374
|
+
console.log(chalk11.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
4753
7375
|
let vault;
|
|
4754
7376
|
if (type === "fast") {
|
|
4755
7377
|
const name = await this.prompt("Vault name");
|
|
4756
7378
|
if (!name) {
|
|
4757
|
-
console.log(
|
|
7379
|
+
console.log(chalk11.red("Name is required"));
|
|
4758
7380
|
return;
|
|
4759
7381
|
}
|
|
4760
7382
|
const password = await this.promptPassword("Vault password");
|
|
4761
7383
|
if (!password) {
|
|
4762
|
-
console.log(
|
|
7384
|
+
console.log(chalk11.red("Password is required"));
|
|
4763
7385
|
return;
|
|
4764
7386
|
}
|
|
4765
7387
|
const email = await this.prompt("Email for verification");
|
|
4766
7388
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4767
|
-
console.log(
|
|
7389
|
+
console.log(chalk11.red("Valid email is required"));
|
|
4768
7390
|
return;
|
|
4769
7391
|
}
|
|
4770
7392
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
@@ -4782,19 +7404,19 @@ Error: ${error2.message}`));
|
|
|
4782
7404
|
} else {
|
|
4783
7405
|
const name = await this.prompt("Vault name");
|
|
4784
7406
|
if (!name) {
|
|
4785
|
-
console.log(
|
|
7407
|
+
console.log(chalk11.red("Name is required"));
|
|
4786
7408
|
return;
|
|
4787
7409
|
}
|
|
4788
7410
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4789
7411
|
const shares = parseInt(sharesStr, 10);
|
|
4790
7412
|
if (isNaN(shares) || shares < 2) {
|
|
4791
|
-
console.log(
|
|
7413
|
+
console.log(chalk11.red("Must have at least 2 shares"));
|
|
4792
7414
|
return;
|
|
4793
7415
|
}
|
|
4794
7416
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4795
7417
|
const threshold = parseInt(thresholdStr, 10);
|
|
4796
7418
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4797
|
-
console.log(
|
|
7419
|
+
console.log(chalk11.red(`Threshold must be between 1 and ${shares}`));
|
|
4798
7420
|
return;
|
|
4799
7421
|
}
|
|
4800
7422
|
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
@@ -4838,8 +7460,8 @@ Error: ${error2.message}`));
|
|
|
4838
7460
|
}
|
|
4839
7461
|
}
|
|
4840
7462
|
if (!fiatCurrencies3.includes(currency)) {
|
|
4841
|
-
console.log(
|
|
4842
|
-
console.log(
|
|
7463
|
+
console.log(chalk11.red(`Invalid currency: ${currency}`));
|
|
7464
|
+
console.log(chalk11.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
|
|
4843
7465
|
return;
|
|
4844
7466
|
}
|
|
4845
7467
|
const raw = args.includes("--raw");
|
|
@@ -4847,7 +7469,7 @@ Error: ${error2.message}`));
|
|
|
4847
7469
|
}
|
|
4848
7470
|
async runSend(args) {
|
|
4849
7471
|
if (args.length < 3) {
|
|
4850
|
-
console.log(
|
|
7472
|
+
console.log(chalk11.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
|
|
4851
7473
|
return;
|
|
4852
7474
|
}
|
|
4853
7475
|
const [chainStr, to, amount, ...rest] = args;
|
|
@@ -4867,12 +7489,22 @@ Error: ${error2.message}`));
|
|
|
4867
7489
|
await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
|
|
4868
7490
|
} catch (err) {
|
|
4869
7491
|
if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4870
|
-
console.log(
|
|
7492
|
+
console.log(chalk11.yellow("\nTransaction cancelled"));
|
|
4871
7493
|
return;
|
|
4872
7494
|
}
|
|
4873
7495
|
throw err;
|
|
4874
7496
|
}
|
|
4875
7497
|
}
|
|
7498
|
+
async runTxStatus(args) {
|
|
7499
|
+
if (args.length < 2) {
|
|
7500
|
+
console.log(chalk11.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
|
|
7501
|
+
return;
|
|
7502
|
+
}
|
|
7503
|
+
const [chainStr, txHash, ...rest] = args;
|
|
7504
|
+
const chain = findChainByName(chainStr) || chainStr;
|
|
7505
|
+
const noWait = rest.includes("--no-wait");
|
|
7506
|
+
await this.withCancellation(() => executeTxStatus(this.ctx, { chain, txHash, noWait }));
|
|
7507
|
+
}
|
|
4876
7508
|
async runChains(args) {
|
|
4877
7509
|
let addChain;
|
|
4878
7510
|
let removeChain;
|
|
@@ -4883,8 +7515,8 @@ Error: ${error2.message}`));
|
|
|
4883
7515
|
} else if (args[i] === "--add" && i + 1 < args.length) {
|
|
4884
7516
|
const chain = findChainByName(args[i + 1]);
|
|
4885
7517
|
if (!chain) {
|
|
4886
|
-
console.log(
|
|
4887
|
-
console.log(
|
|
7518
|
+
console.log(chalk11.red(`Unknown chain: ${args[i + 1]}`));
|
|
7519
|
+
console.log(chalk11.gray("Use tab completion to see available chains"));
|
|
4888
7520
|
return;
|
|
4889
7521
|
}
|
|
4890
7522
|
addChain = chain;
|
|
@@ -4892,8 +7524,8 @@ Error: ${error2.message}`));
|
|
|
4892
7524
|
} else if (args[i] === "--remove" && i + 1 < args.length) {
|
|
4893
7525
|
const chain = findChainByName(args[i + 1]);
|
|
4894
7526
|
if (!chain) {
|
|
4895
|
-
console.log(
|
|
4896
|
-
console.log(
|
|
7527
|
+
console.log(chalk11.red(`Unknown chain: ${args[i + 1]}`));
|
|
7528
|
+
console.log(chalk11.gray("Use tab completion to see available chains"));
|
|
4897
7529
|
return;
|
|
4898
7530
|
}
|
|
4899
7531
|
removeChain = chain;
|
|
@@ -4904,7 +7536,7 @@ Error: ${error2.message}`));
|
|
|
4904
7536
|
}
|
|
4905
7537
|
async runTokens(args) {
|
|
4906
7538
|
if (args.length === 0) {
|
|
4907
|
-
console.log(
|
|
7539
|
+
console.log(chalk11.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
|
|
4908
7540
|
return;
|
|
4909
7541
|
}
|
|
4910
7542
|
const chainStr = args[0];
|
|
@@ -4925,7 +7557,7 @@ Error: ${error2.message}`));
|
|
|
4925
7557
|
async runSwapQuote(args) {
|
|
4926
7558
|
if (args.length < 3) {
|
|
4927
7559
|
console.log(
|
|
4928
|
-
|
|
7560
|
+
chalk11.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
|
|
4929
7561
|
);
|
|
4930
7562
|
return;
|
|
4931
7563
|
}
|
|
@@ -4949,7 +7581,7 @@ Error: ${error2.message}`));
|
|
|
4949
7581
|
async runSwap(args) {
|
|
4950
7582
|
if (args.length < 3) {
|
|
4951
7583
|
console.log(
|
|
4952
|
-
|
|
7584
|
+
chalk11.yellow(
|
|
4953
7585
|
"Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
|
|
4954
7586
|
)
|
|
4955
7587
|
);
|
|
@@ -4980,7 +7612,7 @@ Error: ${error2.message}`));
|
|
|
4980
7612
|
);
|
|
4981
7613
|
} catch (err) {
|
|
4982
7614
|
if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4983
|
-
console.log(
|
|
7615
|
+
console.log(chalk11.yellow("\nSwap cancelled"));
|
|
4984
7616
|
return;
|
|
4985
7617
|
}
|
|
4986
7618
|
throw err;
|
|
@@ -5042,24 +7674,24 @@ Error: ${error2.message}`));
|
|
|
5042
7674
|
}
|
|
5043
7675
|
getPrompt() {
|
|
5044
7676
|
const vault = this.ctx.getActiveVault();
|
|
5045
|
-
if (!vault) return
|
|
7677
|
+
if (!vault) return chalk11.cyan("wallet> ");
|
|
5046
7678
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
5047
|
-
const status = isUnlocked ?
|
|
5048
|
-
return
|
|
7679
|
+
const status = isUnlocked ? chalk11.green("\u{1F513}") : chalk11.yellow("\u{1F512}");
|
|
7680
|
+
return chalk11.cyan(`wallet[${vault.name}]${status}> `);
|
|
5049
7681
|
}
|
|
5050
7682
|
displayVaultList() {
|
|
5051
7683
|
const vaults = Array.from(this.ctx.getVaults().values());
|
|
5052
7684
|
const activeVault = this.ctx.getActiveVault();
|
|
5053
7685
|
if (vaults.length === 0) {
|
|
5054
|
-
console.log(
|
|
7686
|
+
console.log(chalk11.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
|
|
5055
7687
|
return;
|
|
5056
7688
|
}
|
|
5057
|
-
console.log(
|
|
7689
|
+
console.log(chalk11.cyan("Loaded Vaults:\n"));
|
|
5058
7690
|
vaults.forEach((vault) => {
|
|
5059
7691
|
const isActive = vault.id === activeVault?.id;
|
|
5060
7692
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
5061
|
-
const activeMarker = isActive ?
|
|
5062
|
-
const lockIcon = isUnlocked ?
|
|
7693
|
+
const activeMarker = isActive ? chalk11.green(" (active)") : "";
|
|
7694
|
+
const lockIcon = isUnlocked ? chalk11.green("\u{1F513}") : chalk11.yellow("\u{1F512}");
|
|
5063
7695
|
console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
|
|
5064
7696
|
});
|
|
5065
7697
|
console.log();
|
|
@@ -5067,23 +7699,23 @@ Error: ${error2.message}`));
|
|
|
5067
7699
|
};
|
|
5068
7700
|
|
|
5069
7701
|
// src/lib/errors.ts
|
|
5070
|
-
import
|
|
7702
|
+
import chalk12 from "chalk";
|
|
5071
7703
|
|
|
5072
7704
|
// src/lib/version.ts
|
|
5073
|
-
import
|
|
5074
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
5075
|
-
import { homedir } from "os";
|
|
5076
|
-
import { join } from "path";
|
|
7705
|
+
import chalk13 from "chalk";
|
|
7706
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
7707
|
+
import { homedir as homedir2 } from "os";
|
|
7708
|
+
import { join as join2 } from "path";
|
|
5077
7709
|
var cachedVersion = null;
|
|
5078
7710
|
function getVersion() {
|
|
5079
7711
|
if (cachedVersion) return cachedVersion;
|
|
5080
7712
|
if (true) {
|
|
5081
|
-
cachedVersion = "0.
|
|
7713
|
+
cachedVersion = "0.7.0";
|
|
5082
7714
|
return cachedVersion;
|
|
5083
7715
|
}
|
|
5084
7716
|
try {
|
|
5085
7717
|
const packagePath = new URL("../../package.json", import.meta.url);
|
|
5086
|
-
const pkg = JSON.parse(
|
|
7718
|
+
const pkg = JSON.parse(readFileSync2(packagePath, "utf-8"));
|
|
5087
7719
|
cachedVersion = pkg.version;
|
|
5088
7720
|
return cachedVersion;
|
|
5089
7721
|
} catch {
|
|
@@ -5091,13 +7723,13 @@ function getVersion() {
|
|
|
5091
7723
|
return cachedVersion;
|
|
5092
7724
|
}
|
|
5093
7725
|
}
|
|
5094
|
-
var CACHE_DIR =
|
|
5095
|
-
var VERSION_CACHE_FILE =
|
|
7726
|
+
var CACHE_DIR = join2(homedir2(), ".vultisig", "cache");
|
|
7727
|
+
var VERSION_CACHE_FILE = join2(CACHE_DIR, "version-check.json");
|
|
5096
7728
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5097
7729
|
function readVersionCache() {
|
|
5098
7730
|
try {
|
|
5099
|
-
if (!
|
|
5100
|
-
const data =
|
|
7731
|
+
if (!existsSync2(VERSION_CACHE_FILE)) return null;
|
|
7732
|
+
const data = readFileSync2(VERSION_CACHE_FILE, "utf-8");
|
|
5101
7733
|
return JSON.parse(data);
|
|
5102
7734
|
} catch {
|
|
5103
7735
|
return null;
|
|
@@ -5105,10 +7737,10 @@ function readVersionCache() {
|
|
|
5105
7737
|
}
|
|
5106
7738
|
function writeVersionCache(cache) {
|
|
5107
7739
|
try {
|
|
5108
|
-
if (!
|
|
5109
|
-
|
|
7740
|
+
if (!existsSync2(CACHE_DIR)) {
|
|
7741
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
5110
7742
|
}
|
|
5111
|
-
|
|
7743
|
+
writeFileSync2(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
5112
7744
|
} catch {
|
|
5113
7745
|
}
|
|
5114
7746
|
}
|
|
@@ -5132,8 +7764,8 @@ async function fetchLatestVersion() {
|
|
|
5132
7764
|
}
|
|
5133
7765
|
function isNewerVersion(v1, v2) {
|
|
5134
7766
|
const parse = (v) => {
|
|
5135
|
-
const
|
|
5136
|
-
return
|
|
7767
|
+
const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
|
|
7768
|
+
return clean2.split(".").map((n) => parseInt(n, 10) || 0);
|
|
5137
7769
|
};
|
|
5138
7770
|
const p1 = parse(v1);
|
|
5139
7771
|
const p2 = parse(v2);
|
|
@@ -5175,7 +7807,7 @@ function formatVersionShort() {
|
|
|
5175
7807
|
}
|
|
5176
7808
|
function formatVersionDetailed() {
|
|
5177
7809
|
const lines = [];
|
|
5178
|
-
lines.push(
|
|
7810
|
+
lines.push(chalk13.bold(`Vultisig CLI v${getVersion()}`));
|
|
5179
7811
|
lines.push("");
|
|
5180
7812
|
lines.push(` Node.js: ${process.version}`);
|
|
5181
7813
|
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
@@ -5213,9 +7845,9 @@ function getUpdateCommand() {
|
|
|
5213
7845
|
}
|
|
5214
7846
|
|
|
5215
7847
|
// src/lib/completion.ts
|
|
5216
|
-
import { homedir as
|
|
5217
|
-
import { join as
|
|
5218
|
-
import { readFileSync as
|
|
7848
|
+
import { homedir as homedir3 } from "os";
|
|
7849
|
+
import { join as join3 } from "path";
|
|
7850
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
5219
7851
|
var tabtab = null;
|
|
5220
7852
|
async function getTabtab() {
|
|
5221
7853
|
if (!tabtab) {
|
|
@@ -5280,15 +7912,15 @@ var CHAINS = [
|
|
|
5280
7912
|
];
|
|
5281
7913
|
function getVaultNames() {
|
|
5282
7914
|
try {
|
|
5283
|
-
const vaultDir =
|
|
5284
|
-
if (!
|
|
7915
|
+
const vaultDir = join3(homedir3(), ".vultisig", "vaults");
|
|
7916
|
+
if (!existsSync3(vaultDir)) return [];
|
|
5285
7917
|
const { readdirSync } = __require("fs");
|
|
5286
7918
|
const files = readdirSync(vaultDir);
|
|
5287
7919
|
const names = [];
|
|
5288
7920
|
for (const file of files) {
|
|
5289
7921
|
if (file.startsWith("vault:") && file.endsWith(".json")) {
|
|
5290
7922
|
try {
|
|
5291
|
-
const content =
|
|
7923
|
+
const content = readFileSync3(join3(vaultDir, file), "utf-8");
|
|
5292
7924
|
const vault = JSON.parse(content);
|
|
5293
7925
|
if (vault.name) names.push(vault.name);
|
|
5294
7926
|
if (vault.id) names.push(vault.id);
|
|
@@ -5526,7 +8158,26 @@ complete -c vsig -n "__fish_seen_subcommand_from import export" -a "(__fish_comp
|
|
|
5526
8158
|
`.trim();
|
|
5527
8159
|
}
|
|
5528
8160
|
|
|
8161
|
+
// src/lib/user-agent.ts
|
|
8162
|
+
function setupUserAgent() {
|
|
8163
|
+
const userAgent = `vultisig-cli/${getVersion()}`;
|
|
8164
|
+
const originalFetch = globalThis.fetch;
|
|
8165
|
+
globalThis.fetch = (input, init2) => {
|
|
8166
|
+
const headers = new Headers(input instanceof Request ? input.headers : void 0);
|
|
8167
|
+
if (init2?.headers) {
|
|
8168
|
+
new Headers(init2.headers).forEach((value, key) => {
|
|
8169
|
+
headers.set(key, value);
|
|
8170
|
+
});
|
|
8171
|
+
}
|
|
8172
|
+
if (!headers.has("User-Agent")) {
|
|
8173
|
+
headers.set("User-Agent", userAgent);
|
|
8174
|
+
}
|
|
8175
|
+
return originalFetch(input, { ...init2, headers });
|
|
8176
|
+
};
|
|
8177
|
+
}
|
|
8178
|
+
|
|
5529
8179
|
// src/index.ts
|
|
8180
|
+
setupUserAgent();
|
|
5530
8181
|
(async () => {
|
|
5531
8182
|
const handled = await handleCompletion();
|
|
5532
8183
|
if (handled) process.exit(0);
|
|
@@ -5552,7 +8203,7 @@ async function init(vaultOverride, unlockPassword) {
|
|
|
5552
8203
|
if (unlockPassword && vaultSelector) {
|
|
5553
8204
|
cachePassword(vaultSelector, unlockPassword);
|
|
5554
8205
|
}
|
|
5555
|
-
const sdk = new
|
|
8206
|
+
const sdk = new Vultisig7({
|
|
5556
8207
|
onPasswordRequired: createPasswordCallback()
|
|
5557
8208
|
});
|
|
5558
8209
|
await sdk.initialize();
|
|
@@ -5574,10 +8225,10 @@ async function init(vaultOverride, unlockPassword) {
|
|
|
5574
8225
|
return ctx;
|
|
5575
8226
|
}
|
|
5576
8227
|
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(
|
|
8228
|
+
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
8229
|
withExit(async (options) => {
|
|
5579
8230
|
const context = await init(program.opts().vault);
|
|
5580
|
-
await executeCreateFast(context, options);
|
|
8231
|
+
await executeCreateFast(context, { ...options, twoStep: options.twoStep });
|
|
5581
8232
|
})
|
|
5582
8233
|
);
|
|
5583
8234
|
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 +8474,16 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
|
|
|
5823
8474
|
});
|
|
5824
8475
|
})
|
|
5825
8476
|
);
|
|
8477
|
+
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(
|
|
8478
|
+
withExit(async (options) => {
|
|
8479
|
+
const context = await init(program.opts().vault);
|
|
8480
|
+
await executeTxStatus(context, {
|
|
8481
|
+
chain: findChainByName(options.chain) || options.chain,
|
|
8482
|
+
txHash: options.txHash,
|
|
8483
|
+
noWait: !options.wait
|
|
8484
|
+
});
|
|
8485
|
+
})
|
|
8486
|
+
);
|
|
5826
8487
|
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
8488
|
withExit(async (options) => {
|
|
5828
8489
|
const context = await init(program.opts().vault);
|
|
@@ -6051,14 +8712,24 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
|
|
|
6051
8712
|
}
|
|
6052
8713
|
)
|
|
6053
8714
|
);
|
|
8715
|
+
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("--conversation <id>", "Resume an existing conversation").action(async (options) => {
|
|
8716
|
+
const context = await init(program.opts().vault, options.password);
|
|
8717
|
+
await executeAgent(context, {
|
|
8718
|
+
viaAgent: options.viaAgent,
|
|
8719
|
+
verbose: options.verbose,
|
|
8720
|
+
backendUrl: options.backendUrl,
|
|
8721
|
+
password: options.password,
|
|
8722
|
+
conversationId: options.conversation
|
|
8723
|
+
});
|
|
8724
|
+
});
|
|
6054
8725
|
program.command("version").description("Show detailed version information").action(
|
|
6055
8726
|
withExit(async () => {
|
|
6056
8727
|
printResult(formatVersionDetailed());
|
|
6057
8728
|
const result = await checkForUpdates();
|
|
6058
8729
|
if (result?.updateAvailable && result.latestVersion) {
|
|
6059
8730
|
info("");
|
|
6060
|
-
info(
|
|
6061
|
-
info(
|
|
8731
|
+
info(chalk14.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
8732
|
+
info(chalk14.gray(`Run "${getUpdateCommand()}" to update`));
|
|
6062
8733
|
}
|
|
6063
8734
|
})
|
|
6064
8735
|
);
|
|
@@ -6067,28 +8738,28 @@ program.command("update").description("Check for updates and show update command
|
|
|
6067
8738
|
info("Checking for updates...");
|
|
6068
8739
|
const result = await checkForUpdates();
|
|
6069
8740
|
if (!result) {
|
|
6070
|
-
printResult(
|
|
8741
|
+
printResult(chalk14.gray("Update checking is disabled"));
|
|
6071
8742
|
return;
|
|
6072
8743
|
}
|
|
6073
8744
|
if (result.updateAvailable && result.latestVersion) {
|
|
6074
8745
|
printResult("");
|
|
6075
|
-
printResult(
|
|
8746
|
+
printResult(chalk14.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
6076
8747
|
printResult("");
|
|
6077
8748
|
if (options.check) {
|
|
6078
8749
|
printResult(`Run "${getUpdateCommand()}" to update`);
|
|
6079
8750
|
} else {
|
|
6080
8751
|
const updateCmd = getUpdateCommand();
|
|
6081
8752
|
printResult(`To update, run:`);
|
|
6082
|
-
printResult(
|
|
8753
|
+
printResult(chalk14.cyan(` ${updateCmd}`));
|
|
6083
8754
|
}
|
|
6084
8755
|
} else {
|
|
6085
|
-
printResult(
|
|
8756
|
+
printResult(chalk14.green(`You're on the latest version (${result.currentVersion})`));
|
|
6086
8757
|
}
|
|
6087
8758
|
})
|
|
6088
8759
|
);
|
|
6089
8760
|
setupCompletionCommand(program);
|
|
6090
8761
|
async function startInteractiveMode() {
|
|
6091
|
-
const sdk = new
|
|
8762
|
+
const sdk = new Vultisig7({
|
|
6092
8763
|
onPasswordRequired: createPasswordCallback()
|
|
6093
8764
|
});
|
|
6094
8765
|
await sdk.initialize();
|
|
@@ -6110,3 +8781,8 @@ if (isInteractiveMode) {
|
|
|
6110
8781
|
} else {
|
|
6111
8782
|
program.parse();
|
|
6112
8783
|
}
|
|
8784
|
+
/*! Bundled license information:
|
|
8785
|
+
|
|
8786
|
+
@noble/hashes/esm/utils.js:
|
|
8787
|
+
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
8788
|
+
*/
|