@vultisig/cli 0.5.0 → 0.8.0

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