@vultisig/cli 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +39 -0
  3. package/dist/index.js +3371 -215
  4. package/package.json +18 -12
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))
@@ -1042,14 +1049,14 @@ var require_main = __commonJS({
1042
1049
  cb = opts;
1043
1050
  opts = {};
1044
1051
  }
1045
- var qrcode4 = new QRCode(-1, this.error);
1046
- qrcode4.addData(input);
1047
- qrcode4.make();
1052
+ var qrcode5 = new QRCode(-1, this.error);
1053
+ qrcode5.addData(input);
1054
+ qrcode5.make();
1048
1055
  var output = "";
1049
1056
  if (opts && opts.small) {
1050
1057
  var BLACK = true, WHITE = false;
1051
- var moduleCount = qrcode4.getModuleCount();
1052
- var moduleData = qrcode4.modules.slice();
1058
+ var moduleCount = qrcode5.getModuleCount();
1059
+ var moduleData = qrcode5.modules.slice();
1053
1060
  var oddRow = moduleCount % 2 === 1;
1054
1061
  if (oddRow) {
1055
1062
  moduleData.push(fill(moduleCount, WHITE));
@@ -1082,9 +1089,9 @@ var require_main = __commonJS({
1082
1089
  output += borderBottom;
1083
1090
  }
1084
1091
  } else {
1085
- var border = repeat(white).times(qrcode4.getModuleCount() + 3);
1092
+ var border = repeat(white).times(qrcode5.getModuleCount() + 3);
1086
1093
  output += border + "\n";
1087
- qrcode4.modules.forEach(function(row2) {
1094
+ qrcode5.modules.forEach(function(row2) {
1088
1095
  output += white;
1089
1096
  output += row2.map(toCell).join("");
1090
1097
  output += white + "\n";
@@ -1101,12 +1108,334 @@ 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
- import { parseKeygenQR, Vultisig as Vultisig4 } from "@vultisig/sdk";
1107
- import chalk13 from "chalk";
1435
+ import { promises as fs3 } from "node:fs";
1436
+ import { parseKeygenQR, Vultisig as Vultisig7 } from "@vultisig/sdk";
1437
+ import chalk14 from "chalk";
1108
1438
  import { program } from "commander";
1109
- import { promises as fs3 } from "fs";
1110
1439
  import inquirer8 from "inquirer";
1111
1440
 
1112
1441
  // src/core/command-context.ts
@@ -1446,26 +1775,25 @@ import { fiatCurrencies, fiatCurrencyNameRecord as fiatCurrencyNameRecord2 } fro
1446
1775
  import { fiatCurrencyNameRecord, Vultisig } from "@vultisig/sdk";
1447
1776
  import chalk2 from "chalk";
1448
1777
  import inquirer2 from "inquirer";
1449
- function displayBalance(chain, balance, raw = false) {
1778
+ function displayBalance(chain, balance, _raw = false) {
1450
1779
  printResult(chalk2.cyan(`
1451
1780
  ${chain} Balance:`));
1452
- const displayAmount = raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals);
1453
- printResult(` Amount: ${displayAmount} ${balance.symbol}`);
1781
+ printResult(` Amount: ${balance.formattedAmount} ${balance.symbol}`);
1454
1782
  if (balance.fiatValue && balance.fiatCurrency) {
1455
1783
  printResult(` Value: ${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}`);
1456
1784
  }
1457
1785
  }
1458
- function displayBalancesTable(balances, raw = false) {
1786
+ function displayBalancesTable(balances, _raw = false) {
1459
1787
  printResult(chalk2.cyan("\nPortfolio Balances:\n"));
1460
1788
  const tableData = Object.entries(balances).map(([chain, balance]) => ({
1461
1789
  Chain: chain,
1462
- Amount: raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals),
1790
+ Amount: balance.formattedAmount,
1463
1791
  Symbol: balance.symbol,
1464
1792
  Value: balance.fiatValue && balance.fiatCurrency ? `${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}` : "N/A"
1465
1793
  }));
1466
1794
  printTable(tableData);
1467
1795
  }
1468
- function displayPortfolio(portfolio, currency, raw = false) {
1796
+ function displayPortfolio(portfolio, currency, _raw = false) {
1469
1797
  const currencyName = fiatCurrencyNameRecord[currency];
1470
1798
  printResult(chalk2.cyan("\n+----------------------------------------+"));
1471
1799
  printResult(chalk2.cyan(`| Portfolio Total Value (${currencyName}) |`));
@@ -1476,7 +1804,7 @@ function displayPortfolio(portfolio, currency, raw = false) {
1476
1804
  printResult(chalk2.bold("Chain Breakdown:\n"));
1477
1805
  const table = portfolio.chainBalances.map(({ chain, balance, value }) => ({
1478
1806
  Chain: chain,
1479
- Amount: raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals),
1807
+ Amount: balance.formattedAmount,
1480
1808
  Symbol: balance.symbol,
1481
1809
  Value: value ? `${value.amount} ${value.currency.toUpperCase()}` : "N/A"
1482
1810
  }));
@@ -1572,7 +1900,7 @@ async function confirmTransaction() {
1572
1900
  function setupVaultEvents(vault) {
1573
1901
  vault.on("balanceUpdated", ({ chain, balance, tokenId }) => {
1574
1902
  const asset = tokenId ? `${balance.symbol} token` : balance.symbol;
1575
- info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.amount}`));
1903
+ info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.formattedAmount}`));
1576
1904
  });
1577
1905
  vault.on("transactionBroadcast", ({ chain, txHash }) => {
1578
1906
  info(chalk2.green(`+ Transaction broadcast on ${chain}`));
@@ -1612,14 +1940,6 @@ function formatBigintAmount(amount, decimals) {
1612
1940
  const trimmed = fractionStr.replace(/0+$/, "");
1613
1941
  return `${whole}.${trimmed}`;
1614
1942
  }
1615
- function formatBalanceAmount(amount, decimals) {
1616
- if (!amount || amount === "0") return "0";
1617
- try {
1618
- return formatBigintAmount(BigInt(amount), decimals);
1619
- } catch {
1620
- return amount;
1621
- }
1622
- }
1623
1943
  function displaySwapPreview(quote, fromAmount, fromSymbol, toSymbol, options) {
1624
1944
  const estimatedOutputFormatted = formatBigintAmount(quote.estimatedOutput, options.toDecimals);
1625
1945
  printResult(chalk2.cyan("\nSwap Preview:"));
@@ -1923,7 +2243,8 @@ async function executeSend(ctx2, params) {
1923
2243
  if (!Object.values(Chain).includes(params.chain)) {
1924
2244
  throw new Error(`Invalid chain: ${params.chain}`);
1925
2245
  }
1926
- if (isNaN(parseFloat(params.amount)) || parseFloat(params.amount) <= 0) {
2246
+ const isMax = params.amount === "max";
2247
+ if (!isMax && (isNaN(parseFloat(params.amount)) || parseFloat(params.amount) <= 0)) {
1927
2248
  throw new Error("Invalid amount");
1928
2249
  }
1929
2250
  return sendTransaction(vault, params);
@@ -1939,7 +2260,25 @@ async function sendTransaction(vault, params) {
1939
2260
  ticker: balance.symbol,
1940
2261
  id: params.tokenId
1941
2262
  };
1942
- const amount = BigInt(Math.floor(parseFloat(params.amount) * Math.pow(10, balance.decimals)));
2263
+ const isMax = params.amount === "max";
2264
+ let amount;
2265
+ let displayAmount;
2266
+ if (isMax) {
2267
+ const maxInfo = await vault.getMaxSendAmount({ coin, receiver: params.to, memo: params.memo });
2268
+ amount = maxInfo.maxSendable;
2269
+ if (amount === 0n) {
2270
+ throw new Error("Insufficient balance to cover network fees");
2271
+ }
2272
+ displayAmount = formatBigintAmount(amount, balance.decimals);
2273
+ } else {
2274
+ const [whole, frac = ""] = params.amount.split(".");
2275
+ if (frac.length > balance.decimals) {
2276
+ throw new Error(`Amount has more than ${balance.decimals} decimal places`);
2277
+ }
2278
+ const paddedFrac = frac.padEnd(balance.decimals, "0");
2279
+ amount = BigInt(whole || "0") * 10n ** BigInt(balance.decimals) + BigInt(paddedFrac || "0");
2280
+ displayAmount = params.amount;
2281
+ }
1943
2282
  const payload = await vault.prepareSendTx({
1944
2283
  coin,
1945
2284
  receiver: params.to,
@@ -1957,7 +2296,7 @@ async function sendTransaction(vault, params) {
1957
2296
  displayTransactionPreview(
1958
2297
  payload.coin.address,
1959
2298
  params.to,
1960
- params.amount,
2299
+ displayAmount,
1961
2300
  payload.coin.ticker,
1962
2301
  params.chain,
1963
2302
  params.memo,
@@ -2042,12 +2381,175 @@ Or use this URL: ${qrPayload}
2042
2381
  }
2043
2382
  }
2044
2383
 
2045
- // src/commands/sign.ts
2384
+ // src/commands/execute.ts
2046
2385
  var import_qrcode_terminal2 = __toESM(require_main(), 1);
2047
- import { Chain as Chain2 } from "@vultisig/sdk";
2386
+ import { Vultisig as Vultisig3 } from "@vultisig/sdk";
2387
+ var COSMOS_CHAIN_CONFIG = {
2388
+ THORChain: {
2389
+ chainId: "thorchain-1",
2390
+ prefix: "thor",
2391
+ denom: "rune",
2392
+ gasLimit: "500000"
2393
+ },
2394
+ MayaChain: {
2395
+ chainId: "mayachain-mainnet-v1",
2396
+ prefix: "maya",
2397
+ denom: "cacao",
2398
+ gasLimit: "500000"
2399
+ }
2400
+ };
2401
+ function parseFunds(fundsStr) {
2402
+ if (!fundsStr) return [];
2403
+ return fundsStr.split(",").map((fund) => {
2404
+ const [denom, amount] = fund.trim().split(":");
2405
+ if (!denom || !amount) {
2406
+ throw new Error(`Invalid funds format: "${fund}". Expected "denom:amount"`);
2407
+ }
2408
+ return { denom: denom.toLowerCase(), amount };
2409
+ });
2410
+ }
2411
+ async function executeExecute(ctx2, params) {
2412
+ const vault = await ctx2.ensureActiveVault();
2413
+ const chainConfig = COSMOS_CHAIN_CONFIG[params.chain];
2414
+ if (!chainConfig) {
2415
+ throw new Error(`Chain ${params.chain} does not support CosmWasm execute. Supported chains: ${Object.keys(COSMOS_CHAIN_CONFIG).join(", ")}`);
2416
+ }
2417
+ let msg;
2418
+ try {
2419
+ msg = JSON.parse(params.msg);
2420
+ } catch {
2421
+ throw new Error(`Invalid JSON message: ${params.msg}`);
2422
+ }
2423
+ const funds = parseFunds(params.funds);
2424
+ return executeContractTransaction(vault, params, chainConfig, msg, funds);
2425
+ }
2426
+ async function executeContractTransaction(vault, params, chainConfig, msg, funds) {
2427
+ const prepareSpinner = createSpinner("Preparing contract execution...");
2428
+ const address = await vault.address(params.chain);
2429
+ prepareSpinner.succeed("Transaction prepared");
2430
+ if (!isJsonOutput()) {
2431
+ info("\n\u{1F4DD} Contract Execution Preview");
2432
+ info("\u2501".repeat(50));
2433
+ info(`Chain: ${params.chain}`);
2434
+ info(`From: ${address}`);
2435
+ info(`Contract: ${params.contract}`);
2436
+ info(`Message: ${JSON.stringify(msg, null, 2).substring(0, 200)}${JSON.stringify(msg).length > 200 ? "..." : ""}`);
2437
+ if (funds.length > 0) {
2438
+ info(`Funds: ${funds.map((f) => `${f.amount} ${f.denom}`).join(", ")}`);
2439
+ }
2440
+ if (params.memo) {
2441
+ info(`Memo: ${params.memo}`);
2442
+ }
2443
+ info("\u2501".repeat(50));
2444
+ }
2445
+ if (!params.yes && !isJsonOutput()) {
2446
+ const confirmed = await confirmTransaction();
2447
+ if (!confirmed) {
2448
+ warn("Transaction cancelled");
2449
+ throw new Error("Transaction cancelled by user");
2450
+ }
2451
+ }
2452
+ await ensureVaultUnlocked(vault, params.password);
2453
+ const isSecureVault = vault.type === "secure";
2454
+ const signSpinner = createSpinner(isSecureVault ? "Preparing secure signing session..." : "Signing transaction...");
2455
+ vault.on("signingProgress", ({ step }) => {
2456
+ signSpinner.text = `${step.message} (${step.progress}%)`;
2457
+ });
2458
+ if (isSecureVault) {
2459
+ vault.on("qrCodeReady", ({ qrPayload }) => {
2460
+ if (isJsonOutput()) {
2461
+ printResult(qrPayload);
2462
+ } else if (isSilent()) {
2463
+ printResult(`QR Payload: ${qrPayload}`);
2464
+ } else {
2465
+ signSpinner.stop();
2466
+ info("\nScan this QR code with your Vultisig mobile app to sign:");
2467
+ import_qrcode_terminal2.default.generate(qrPayload, { small: true });
2468
+ info(`
2469
+ Or use this URL: ${qrPayload}
2470
+ `);
2471
+ signSpinner.start("Waiting for devices to join signing session...");
2472
+ }
2473
+ });
2474
+ vault.on("deviceJoined", ({ deviceId, totalJoined, required }) => {
2475
+ if (!isSilent()) {
2476
+ signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
2477
+ } else if (!isJsonOutput()) {
2478
+ printResult(`Device joined: ${totalJoined}/${required}`);
2479
+ }
2480
+ });
2481
+ }
2482
+ try {
2483
+ const coin = {
2484
+ chain: params.chain,
2485
+ address,
2486
+ decimals: 8,
2487
+ // THORChain uses 8 decimals
2488
+ ticker: chainConfig.denom.toUpperCase()
2489
+ };
2490
+ const executeContractMsg = {
2491
+ type: "wasm/MsgExecuteContract",
2492
+ value: JSON.stringify({
2493
+ sender: address,
2494
+ contract: params.contract,
2495
+ msg,
2496
+ funds: funds.map((f) => ({ denom: f.denom, amount: f.amount }))
2497
+ })
2498
+ };
2499
+ const fee = {
2500
+ amount: [{ denom: chainConfig.denom, amount: "0" }],
2501
+ gas: chainConfig.gasLimit
2502
+ };
2503
+ const keysignPayload = await vault.prepareSignAminoTx({
2504
+ chain: params.chain,
2505
+ coin,
2506
+ msgs: [executeContractMsg],
2507
+ fee,
2508
+ memo: params.memo
2509
+ });
2510
+ const messageHashes = await vault.extractMessageHashes(keysignPayload);
2511
+ const signature = await vault.sign(
2512
+ {
2513
+ transaction: keysignPayload,
2514
+ chain: params.chain,
2515
+ messageHashes
2516
+ },
2517
+ { signal: params.signal }
2518
+ );
2519
+ signSpinner.succeed("Transaction signed");
2520
+ const broadcastSpinner = createSpinner("Broadcasting transaction...");
2521
+ const txHash = await vault.broadcastTx({
2522
+ chain: params.chain,
2523
+ keysignPayload,
2524
+ signature
2525
+ });
2526
+ broadcastSpinner.succeed(`Transaction broadcast: ${txHash}`);
2527
+ const result = {
2528
+ txHash,
2529
+ chain: params.chain,
2530
+ explorerUrl: Vultisig3.getTxExplorerUrl(params.chain, txHash)
2531
+ };
2532
+ if (isJsonOutput()) {
2533
+ outputJson(result);
2534
+ } else {
2535
+ displayTransactionResult(params.chain, txHash);
2536
+ }
2537
+ return result;
2538
+ } finally {
2539
+ vault.removeAllListeners("signingProgress");
2540
+ if (isSecureVault) {
2541
+ vault.removeAllListeners("qrCodeReady");
2542
+ vault.removeAllListeners("deviceJoined");
2543
+ }
2544
+ }
2545
+ }
2546
+
2547
+ // src/commands/sign.ts
2548
+ var import_qrcode_terminal3 = __toESM(require_main(), 1);
2549
+ import { Chain as Chain3 } from "@vultisig/sdk";
2048
2550
  async function executeSignBytes(ctx2, params) {
2049
2551
  const vault = await ctx2.ensureActiveVault();
2050
- if (!Object.values(Chain2).includes(params.chain)) {
2552
+ if (!Object.values(Chain3).includes(params.chain)) {
2051
2553
  throw new Error(`Invalid chain: ${params.chain}`);
2052
2554
  }
2053
2555
  return signBytes(vault, params);
@@ -2069,7 +2571,7 @@ async function signBytes(vault, params) {
2069
2571
  } else {
2070
2572
  signSpinner.stop();
2071
2573
  info("\nScan this QR code with your Vultisig mobile app to sign:");
2072
- import_qrcode_terminal2.default.generate(qrPayload, { small: true });
2574
+ import_qrcode_terminal3.default.generate(qrPayload, { small: true });
2073
2575
  info(`
2074
2576
  Or use this URL: ${qrPayload}
2075
2577
  `);
@@ -2123,10 +2625,10 @@ Or use this URL: ${qrPayload}
2123
2625
  }
2124
2626
 
2125
2627
  // src/commands/broadcast.ts
2126
- import { Chain as Chain3, Vultisig as Vultisig3 } from "@vultisig/sdk";
2628
+ import { Chain as Chain4, Vultisig as Vultisig4 } from "@vultisig/sdk";
2127
2629
  async function executeBroadcast(ctx2, params) {
2128
2630
  const vault = await ctx2.ensureActiveVault();
2129
- if (!Object.values(Chain3).includes(params.chain)) {
2631
+ if (!Object.values(Chain4).includes(params.chain)) {
2130
2632
  throw new Error(`Invalid chain: ${params.chain}`);
2131
2633
  }
2132
2634
  const broadcastSpinner = createSpinner("Broadcasting transaction...");
@@ -2139,7 +2641,7 @@ async function executeBroadcast(ctx2, params) {
2139
2641
  const result = {
2140
2642
  txHash,
2141
2643
  chain: params.chain,
2142
- explorerUrl: Vultisig3.getTxExplorerUrl(params.chain, txHash)
2644
+ explorerUrl: Vultisig4.getTxExplorerUrl(params.chain, txHash)
2143
2645
  };
2144
2646
  if (isJsonOutput()) {
2145
2647
  outputJson(result);
@@ -2154,8 +2656,68 @@ async function executeBroadcast(ctx2, params) {
2154
2656
  }
2155
2657
  }
2156
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
+
2157
2719
  // src/commands/vault-management.ts
2158
- var import_qrcode_terminal3 = __toESM(require_main(), 1);
2720
+ var import_qrcode_terminal4 = __toESM(require_main(), 1);
2159
2721
  import chalk5 from "chalk";
2160
2722
  import { promises as fs } from "fs";
2161
2723
  import inquirer4 from "inquirer";
@@ -2171,13 +2733,14 @@ function withAbortSignal(promise, signal) {
2171
2733
  ]);
2172
2734
  }
2173
2735
  async function executeCreateFast(ctx2, options) {
2174
- const { name, password, email, signal } = options;
2736
+ const { name, password, email, signal, twoStep } = options;
2175
2737
  const spinner = createSpinner("Creating vault...");
2176
2738
  const vaultId = await withAbortSignal(
2177
2739
  ctx2.sdk.createFastVault({
2178
2740
  name,
2179
2741
  password,
2180
2742
  email,
2743
+ persistPending: twoStep,
2181
2744
  onProgress: (step) => {
2182
2745
  spinner.text = `${step.message} (${step.progress}%)`;
2183
2746
  }
@@ -2185,6 +2748,17 @@ async function executeCreateFast(ctx2, options) {
2185
2748
  signal
2186
2749
  );
2187
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
+ }
2188
2762
  warn("\nA verification code has been sent to your email.");
2189
2763
  info("Please check your inbox and enter the code.");
2190
2764
  const MAX_VERIFY_ATTEMPTS = 5;
@@ -2276,7 +2850,7 @@ async function executeCreateSecure(ctx2, options) {
2276
2850
  } else {
2277
2851
  spinner.stop();
2278
2852
  info("\nScan this QR code with your Vultisig mobile app:");
2279
- import_qrcode_terminal3.default.generate(qrPayload, { small: true });
2853
+ import_qrcode_terminal4.default.generate(qrPayload, { small: true });
2280
2854
  info(`
2281
2855
  Or use this URL: ${qrPayload}
2282
2856
  `);
@@ -2733,7 +3307,7 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
2733
3307
  } else {
2734
3308
  importSpinner.stop();
2735
3309
  info("\nScan this QR code with your Vultisig mobile app:");
2736
- import_qrcode_terminal3.default.generate(qrPayload, { small: true });
3310
+ import_qrcode_terminal4.default.generate(qrPayload, { small: true });
2737
3311
  info(`
2738
3312
  Or use this URL: ${qrPayload}
2739
3313
  `);
@@ -2918,34 +3492,47 @@ async function executeSwapChains(ctx2) {
2918
3492
  }
2919
3493
  async function executeSwapQuote(ctx2, options) {
2920
3494
  const vault = await ctx2.ensureActiveVault();
2921
- if (isNaN(options.amount) || options.amount <= 0) {
3495
+ const isMax = options.amount === "max";
3496
+ if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
2922
3497
  throw new Error("Invalid amount");
2923
3498
  }
2924
3499
  const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
2925
3500
  if (!isSupported) {
2926
3501
  throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
2927
3502
  }
3503
+ let resolvedAmount;
3504
+ if (isMax) {
3505
+ const bal = await vault.balance(options.fromChain, options.fromToken);
3506
+ resolvedAmount = parseFloat(bal.formattedAmount);
3507
+ if (resolvedAmount <= 0) {
3508
+ throw new Error("Zero balance \u2014 nothing to swap");
3509
+ }
3510
+ } else {
3511
+ resolvedAmount = options.amount;
3512
+ }
2928
3513
  const spinner = createSpinner("Getting swap quote...");
2929
3514
  const quote = await vault.getSwapQuote({
2930
3515
  fromCoin: { chain: options.fromChain, token: options.fromToken },
2931
3516
  toCoin: { chain: options.toChain, token: options.toToken },
2932
- amount: options.amount,
3517
+ amount: resolvedAmount,
2933
3518
  fiatCurrency: "usd"
2934
3519
  // Request fiat conversion
2935
3520
  });
2936
3521
  spinner.succeed("Quote received");
3522
+ const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
2937
3523
  if (isJsonOutput()) {
2938
3524
  outputJson({
2939
3525
  fromChain: options.fromChain,
2940
3526
  toChain: options.toChain,
2941
- amount: options.amount,
3527
+ amount: resolvedAmount,
3528
+ isMax,
2942
3529
  quote
2943
3530
  });
2944
3531
  return quote;
2945
3532
  }
2946
3533
  const feeBalance = await vault.balance(options.fromChain);
2947
3534
  const discountTier = await vault.getDiscountTier();
2948
- displaySwapPreview(quote, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
3535
+ displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
2949
3536
  fromDecimals: quote.fromCoin.decimals,
2950
3537
  toDecimals: quote.toCoin.decimals,
2951
3538
  feeDecimals: feeBalance.decimals,
@@ -2957,26 +3544,38 @@ async function executeSwapQuote(ctx2, options) {
2957
3544
  }
2958
3545
  async function executeSwap(ctx2, options) {
2959
3546
  const vault = await ctx2.ensureActiveVault();
2960
- if (isNaN(options.amount) || options.amount <= 0) {
3547
+ const isMax = options.amount === "max";
3548
+ if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
2961
3549
  throw new Error("Invalid amount");
2962
3550
  }
2963
3551
  const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
2964
3552
  if (!isSupported) {
2965
3553
  throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
2966
3554
  }
3555
+ let resolvedAmount;
3556
+ if (isMax) {
3557
+ const bal = await vault.balance(options.fromChain, options.fromToken);
3558
+ resolvedAmount = parseFloat(bal.formattedAmount);
3559
+ if (resolvedAmount <= 0) {
3560
+ throw new Error("Zero balance \u2014 nothing to swap");
3561
+ }
3562
+ } else {
3563
+ resolvedAmount = options.amount;
3564
+ }
2967
3565
  const quoteSpinner = createSpinner("Getting swap quote...");
2968
3566
  const quote = await vault.getSwapQuote({
2969
3567
  fromCoin: { chain: options.fromChain, token: options.fromToken },
2970
3568
  toCoin: { chain: options.toChain, token: options.toToken },
2971
- amount: options.amount,
3569
+ amount: resolvedAmount,
2972
3570
  fiatCurrency: "usd"
2973
3571
  // Request fiat conversion
2974
3572
  });
2975
3573
  quoteSpinner.succeed("Quote received");
3574
+ const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
2976
3575
  const feeBalance = await vault.balance(options.fromChain);
2977
3576
  const discountTier = await vault.getDiscountTier();
2978
3577
  if (!isJsonOutput()) {
2979
- displaySwapPreview(quote, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
3578
+ displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
2980
3579
  fromDecimals: quote.fromCoin.decimals,
2981
3580
  toDecimals: quote.toCoin.decimals,
2982
3581
  feeDecimals: feeBalance.decimals,
@@ -2995,7 +3594,7 @@ async function executeSwap(ctx2, options) {
2995
3594
  const { keysignPayload, approvalPayload } = await vault.prepareSwapTx({
2996
3595
  fromCoin: { chain: options.fromChain, token: options.fromToken },
2997
3596
  toCoin: { chain: options.toChain, token: options.toToken },
2998
- amount: options.amount,
3597
+ amount: resolvedAmount,
2999
3598
  swapQuote: quote,
3000
3599
  autoApprove: false
3001
3600
  });
@@ -3070,7 +3669,7 @@ async function executeSwap(ctx2, options) {
3070
3669
  }
3071
3670
 
3072
3671
  // src/commands/settings.ts
3073
- import { Chain as Chain4, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
3672
+ import { Chain as Chain6, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
3074
3673
  import chalk6 from "chalk";
3075
3674
  import inquirer5 from "inquirer";
3076
3675
  async function executeCurrency(ctx2, newCurrency) {
@@ -3138,7 +3737,7 @@ async function executeAddressBook(ctx2, options = {}) {
3138
3737
  type: "list",
3139
3738
  name: "chain",
3140
3739
  message: "Select chain:",
3141
- choices: Object.values(Chain4)
3740
+ choices: Object.values(Chain6)
3142
3741
  });
3143
3742
  }
3144
3743
  if (!address) {
@@ -3214,6 +3813,192 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
3214
3813
  return allEntries;
3215
3814
  }
3216
3815
 
3816
+ // src/commands/rujira.ts
3817
+ import { getRoutesSummary, listEasyRoutes, RujiraClient, VultisigRujiraProvider } from "@vultisig/rujira";
3818
+ async function createRujiraClient(ctx2, options = {}) {
3819
+ const vault = await ctx2.ensureActiveVault();
3820
+ const provider = new VultisigRujiraProvider(vault);
3821
+ const client = new RujiraClient({
3822
+ signer: provider,
3823
+ rpcEndpoint: options.rpcEndpoint,
3824
+ config: {
3825
+ // Allow overriding rest endpoint via config (used for thornode calls)
3826
+ ...options.restEndpoint ? { restEndpoint: options.restEndpoint } : {}
3827
+ }
3828
+ });
3829
+ const spinner = createSpinner("Connecting to Rujira/THORChain...");
3830
+ await client.connect();
3831
+ spinner.succeed("Connected");
3832
+ return client;
3833
+ }
3834
+ async function executeRujiraBalance(ctx2, options = {}) {
3835
+ const vault = await ctx2.ensureActiveVault();
3836
+ const thorAddress = await vault.address("THORChain");
3837
+ const client = await createRujiraClient(ctx2, options);
3838
+ const spinner = createSpinner("Loading THORChain balances...");
3839
+ const balances = await client.deposit.getBalances(thorAddress);
3840
+ spinner.succeed("Balances loaded");
3841
+ const filtered = options.securedOnly ? balances.filter((b) => b.denom.includes("-") || b.denom.includes("/")) : balances;
3842
+ if (isJsonOutput()) {
3843
+ outputJson({ thorAddress, balances: filtered });
3844
+ return;
3845
+ }
3846
+ info(`THORChain address: ${thorAddress}`);
3847
+ if (!filtered.length) {
3848
+ printResult("No balances found");
3849
+ return;
3850
+ }
3851
+ printTable(
3852
+ filtered.map((b) => ({
3853
+ asset: b.asset,
3854
+ denom: b.denom,
3855
+ amount: b.formatted,
3856
+ raw: b.amount
3857
+ }))
3858
+ );
3859
+ }
3860
+ async function executeRujiraRoutes() {
3861
+ const routes = listEasyRoutes();
3862
+ const summary = getRoutesSummary();
3863
+ if (isJsonOutput()) {
3864
+ outputJson({ routes, summary });
3865
+ return;
3866
+ }
3867
+ printResult(summary);
3868
+ printResult("");
3869
+ printTable(
3870
+ routes.map((r) => ({
3871
+ name: r.name,
3872
+ from: r.from,
3873
+ to: r.to,
3874
+ liquidity: r.liquidity,
3875
+ description: r.description
3876
+ }))
3877
+ );
3878
+ }
3879
+ async function executeRujiraDeposit(ctx2, options = {}) {
3880
+ const vault = await ctx2.ensureActiveVault();
3881
+ const thorAddress = await vault.address("THORChain");
3882
+ const client = await createRujiraClient(ctx2, options);
3883
+ if (!options.asset) {
3884
+ const spinner2 = createSpinner("Loading THORChain inbound addresses...");
3885
+ const inbound = await client.deposit.getInboundAddresses();
3886
+ spinner2.succeed("Inbound addresses loaded");
3887
+ if (isJsonOutput()) {
3888
+ outputJson({ thorAddress, inboundAddresses: inbound });
3889
+ return;
3890
+ }
3891
+ info(`THORChain address: ${thorAddress}`);
3892
+ printResult("Provide an L1 asset to get a chain-specific inbound address + memo.");
3893
+ printResult("Example: vultisig rujira deposit --asset BTC.BTC --amount 100000");
3894
+ printResult("");
3895
+ printTable(
3896
+ inbound.map((a) => ({
3897
+ chain: a.chain,
3898
+ address: a.address,
3899
+ halted: a.halted,
3900
+ globalTradingPaused: a.global_trading_paused,
3901
+ chainTradingPaused: a.chain_trading_paused
3902
+ }))
3903
+ );
3904
+ return;
3905
+ }
3906
+ const amount = options.amount ?? "1";
3907
+ const spinner = createSpinner("Preparing deposit instructions...");
3908
+ const prepared = await client.deposit.prepare({
3909
+ fromAsset: options.asset,
3910
+ amount,
3911
+ thorAddress,
3912
+ affiliate: options.affiliate,
3913
+ affiliateBps: options.affiliateBps
3914
+ });
3915
+ spinner.succeed("Deposit prepared");
3916
+ if (isJsonOutput()) {
3917
+ outputJson({ thorAddress, deposit: prepared });
3918
+ return;
3919
+ }
3920
+ info(`THORChain address: ${thorAddress}`);
3921
+ printResult("Deposit instructions (send from L1):");
3922
+ printResult(` Chain: ${prepared.chain}`);
3923
+ printResult(` Asset: ${prepared.asset}`);
3924
+ printResult(` Inbound address:${prepared.inboundAddress}`);
3925
+ printResult(` Memo: ${prepared.memo}`);
3926
+ printResult(` Min amount: ${prepared.minimumAmount}`);
3927
+ if (prepared.warning) {
3928
+ warn(prepared.warning);
3929
+ }
3930
+ }
3931
+ async function executeRujiraSwap(ctx2, options) {
3932
+ const vault = await ctx2.ensureActiveVault();
3933
+ await ensureVaultUnlocked(vault, options.password);
3934
+ const client = await createRujiraClient(ctx2, options);
3935
+ const destination = options.destination ?? await vault.address("THORChain");
3936
+ const quoteSpinner = createSpinner("Getting FIN swap quote...");
3937
+ const quote = await client.swap.getQuote({
3938
+ fromAsset: options.fromAsset,
3939
+ toAsset: options.toAsset,
3940
+ amount: options.amount,
3941
+ destination,
3942
+ slippageBps: options.slippageBps
3943
+ });
3944
+ quoteSpinner.succeed("Quote received");
3945
+ if (isJsonOutput()) {
3946
+ const result2 = await client.swap.execute(quote, { slippageBps: options.slippageBps });
3947
+ outputJson({ quote, result: result2 });
3948
+ return;
3949
+ }
3950
+ printResult("FIN Swap Preview");
3951
+ printResult(` From: ${options.fromAsset}`);
3952
+ printResult(` To: ${options.toAsset}`);
3953
+ printResult(` Amount (in): ${options.amount}`);
3954
+ printResult(` Expected out:${quote.expectedOutput}`);
3955
+ printResult(` Min out: ${quote.minimumOutput}`);
3956
+ printResult(` Contract: ${quote.contractAddress}`);
3957
+ if (quote.warning) {
3958
+ warn(quote.warning);
3959
+ }
3960
+ if (!options.yes) {
3961
+ warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
3962
+ throw new Error("Confirmation required (use --yes)");
3963
+ }
3964
+ const execSpinner = createSpinner("Executing FIN swap...");
3965
+ const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
3966
+ execSpinner.succeed("Swap submitted");
3967
+ printResult(`Tx Hash: ${result.txHash}`);
3968
+ }
3969
+ async function executeRujiraWithdraw(ctx2, options) {
3970
+ const vault = await ctx2.ensureActiveVault();
3971
+ await ensureVaultUnlocked(vault, options.password);
3972
+ const client = await createRujiraClient(ctx2, options);
3973
+ const prepSpinner = createSpinner("Preparing withdrawal (MsgDeposit)...");
3974
+ const prepared = await client.withdraw.prepare({
3975
+ asset: options.asset,
3976
+ amount: options.amount,
3977
+ l1Address: options.l1Address,
3978
+ maxFeeBps: options.maxFeeBps
3979
+ });
3980
+ prepSpinner.succeed("Withdrawal prepared");
3981
+ if (isJsonOutput()) {
3982
+ const result2 = await client.withdraw.execute(prepared);
3983
+ outputJson({ prepared, result: result2 });
3984
+ return;
3985
+ }
3986
+ printResult("Withdraw Preview");
3987
+ printResult(` Asset: ${prepared.asset}`);
3988
+ printResult(` Amount: ${prepared.amount}`);
3989
+ printResult(` Destination: ${prepared.destination}`);
3990
+ printResult(` Memo: ${prepared.memo}`);
3991
+ printResult(` Est. fee: ${prepared.estimatedFee}`);
3992
+ if (!options.yes) {
3993
+ warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
3994
+ throw new Error("Confirmation required (use --yes)");
3995
+ }
3996
+ const execSpinner = createSpinner("Broadcasting withdrawal...");
3997
+ const result = await client.withdraw.execute(prepared);
3998
+ execSpinner.succeed("Withdrawal submitted");
3999
+ printResult(`Tx Hash: ${result.txHash}`);
4000
+ }
4001
+
3217
4002
  // src/commands/discount.ts
3218
4003
  import {
3219
4004
  baseAffiliateBps,
@@ -3312,25 +4097,2234 @@ function displayDiscountTier(tierInfo) {
3312
4097
  printResult("");
3313
4098
  }
3314
4099
 
3315
- // src/interactive/completer.ts
3316
- import { Chain as Chain5 } from "@vultisig/sdk";
3317
- import fs2 from "fs";
3318
- import path2 from "path";
3319
- var COMMANDS = [
3320
- // Vault management
3321
- "vaults",
3322
- "vault",
3323
- "import",
3324
- "delete",
3325
- "create-from-seedphrase",
3326
- "create",
3327
- "join",
3328
- "info",
3329
- "export",
4100
+ // src/agent/auth.ts
4101
+ init_sha3();
4102
+ import { randomBytes } from "node:crypto";
4103
+ import { Chain as Chain7 } from "@vultisig/sdk";
4104
+ async function authenticateVault(client, vault, password, maxAttempts = 3) {
4105
+ const publicKey = vault.publicKeys.ecdsa;
4106
+ const chainCode = vault.hexChainCode;
4107
+ const ethAddress = await vault.address(Chain7.Ethereum);
4108
+ const nonce = "0x" + randomBytes(16).toString("hex");
4109
+ const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
4110
+ const authMessage = JSON.stringify({
4111
+ message: "Sign into Vultisig Plugin Marketplace",
4112
+ nonce,
4113
+ expiresAt,
4114
+ address: ethAddress
4115
+ });
4116
+ const messageHash = computePersonalSignHash(authMessage);
4117
+ let lastError = null;
4118
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4119
+ try {
4120
+ if (attempt > 1) {
4121
+ process.stderr.write(` Retry ${attempt}/${maxAttempts}...
4122
+ `);
4123
+ }
4124
+ const signature = await vault.signBytes(
4125
+ { data: Buffer.from(messageHash), chain: Chain7.Ethereum },
4126
+ {}
4127
+ );
4128
+ const sigHex = formatSignature65(signature.signature, signature.recovery ?? 0);
4129
+ const authResponse = await client.authenticate({
4130
+ public_key: publicKey,
4131
+ chain_code_hex: chainCode,
4132
+ message: authMessage,
4133
+ signature: sigHex
4134
+ });
4135
+ return {
4136
+ token: authResponse.token,
4137
+ expiresAt: authResponse.expires_at
4138
+ };
4139
+ } catch (err) {
4140
+ lastError = err;
4141
+ if (attempt < maxAttempts && err.message?.includes("timeout")) {
4142
+ continue;
4143
+ }
4144
+ throw err;
4145
+ }
4146
+ }
4147
+ throw lastError || new Error("Authentication failed after all attempts");
4148
+ }
4149
+ function computePersonalSignHash(message) {
4150
+ const messageBytes = new TextEncoder().encode(message);
4151
+ const prefix = `Ethereum Signed Message:
4152
+ ${messageBytes.length}`;
4153
+ const prefixBytes = new TextEncoder().encode(prefix);
4154
+ const combined = new Uint8Array(prefixBytes.length + messageBytes.length);
4155
+ combined.set(prefixBytes);
4156
+ combined.set(messageBytes, prefixBytes.length);
4157
+ return keccak_256(combined);
4158
+ }
4159
+ function formatSignature65(sigHex, recovery) {
4160
+ const hex = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex;
4161
+ const bytes = Buffer.from(hex, "hex");
4162
+ if (bytes[0] === 48) {
4163
+ const { r, s } = decodeDERSignature(bytes);
4164
+ const v = (recovery + 27).toString(16).padStart(2, "0");
4165
+ return r + s + v;
4166
+ }
4167
+ if (hex.length >= 128) {
4168
+ const rs = hex.slice(0, 128);
4169
+ const v = (recovery + 27).toString(16).padStart(2, "0");
4170
+ return rs + v;
4171
+ }
4172
+ throw new Error(`Cannot format signature: unrecognized format (${hex.length} hex chars)`);
4173
+ }
4174
+ function decodeDERSignature(der) {
4175
+ let offset = 0;
4176
+ if (der[offset++] !== 48) throw new Error("Invalid DER: expected SEQUENCE");
4177
+ offset++;
4178
+ if (der[offset++] !== 2) throw new Error("Invalid DER: expected INTEGER for r");
4179
+ const rLen = der[offset++];
4180
+ const rBytes = der.subarray(offset, offset + rLen);
4181
+ offset += rLen;
4182
+ if (der[offset++] !== 2) throw new Error("Invalid DER: expected INTEGER for s");
4183
+ const sLen = der[offset++];
4184
+ const sBytes = der.subarray(offset, offset + sLen);
4185
+ const r = padTo32Bytes(stripLeadingZeros(rBytes));
4186
+ const s = padTo32Bytes(stripLeadingZeros(sBytes));
4187
+ return { r, s };
4188
+ }
4189
+ function stripLeadingZeros(buf) {
4190
+ let start = 0;
4191
+ while (start < buf.length - 1 && buf[start] === 0) start++;
4192
+ return Buffer.from(buf.subarray(start));
4193
+ }
4194
+ function padTo32Bytes(buf) {
4195
+ if (buf.length > 32) {
4196
+ return buf.subarray(buf.length - 32).toString("hex");
4197
+ }
4198
+ return buf.toString("hex").padStart(64, "0");
4199
+ }
4200
+
4201
+ // src/agent/client.ts
4202
+ var AgentClient = class {
4203
+ baseUrl;
4204
+ authToken = null;
4205
+ verbose = false;
4206
+ constructor(baseUrl) {
4207
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
4208
+ }
4209
+ setAuthToken(token) {
4210
+ this.authToken = token;
4211
+ }
4212
+ // ============================================================================
4213
+ // Authentication
4214
+ // ============================================================================
4215
+ async authenticate(req) {
4216
+ const res = await fetch(`${this.baseUrl}/auth/token`, {
4217
+ method: "POST",
4218
+ headers: { "Content-Type": "application/json" },
4219
+ body: JSON.stringify(req)
4220
+ });
4221
+ if (!res.ok) {
4222
+ const body = await res.json().catch(() => ({ error: res.statusText }));
4223
+ throw new Error(`Auth failed (${res.status}): ${body.error || res.statusText}`);
4224
+ }
4225
+ const data = await res.json();
4226
+ this.authToken = data.token;
4227
+ return data;
4228
+ }
4229
+ // ============================================================================
4230
+ // Health
4231
+ // ============================================================================
4232
+ async healthCheck() {
4233
+ try {
4234
+ const res = await fetch(`${this.baseUrl}/healthz`);
4235
+ return res.ok;
4236
+ } catch {
4237
+ return false;
4238
+ }
4239
+ }
4240
+ // ============================================================================
4241
+ // Conversations
4242
+ // ============================================================================
4243
+ async createConversation(publicKey) {
4244
+ const req = { public_key: publicKey };
4245
+ return this.post("/agent/conversations", req);
4246
+ }
4247
+ async listConversations(publicKey, skip = 0, take = 20) {
4248
+ const req = { public_key: publicKey, skip, take };
4249
+ return this.post("/agent/conversations/list", req);
4250
+ }
4251
+ async getConversation(conversationId, publicKey) {
4252
+ const req = { public_key: publicKey };
4253
+ return this.post(`/agent/conversations/${conversationId}`, req);
4254
+ }
4255
+ async deleteConversation(conversationId, publicKey) {
4256
+ await this.delete(`/agent/conversations/${conversationId}`, { public_key: publicKey });
4257
+ }
4258
+ // ============================================================================
4259
+ // Messages - JSON mode
4260
+ // ============================================================================
4261
+ async sendMessage(conversationId, req) {
4262
+ return this.post(`/agent/conversations/${conversationId}/messages`, req);
4263
+ }
4264
+ // ============================================================================
4265
+ // Messages - SSE Streaming mode
4266
+ // ============================================================================
4267
+ async sendMessageStream(conversationId, req, callbacks, signal) {
4268
+ const res = await fetch(`${this.baseUrl}/agent/conversations/${conversationId}/messages`, {
4269
+ method: "POST",
4270
+ headers: {
4271
+ "Content-Type": "application/json",
4272
+ Accept: "text/event-stream",
4273
+ ...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
4274
+ },
4275
+ body: JSON.stringify(req),
4276
+ signal
4277
+ });
4278
+ if (!res.ok) {
4279
+ const body = await res.json().catch(() => ({ error: res.statusText }));
4280
+ throw new Error(`Message failed (${res.status}): ${body.error || res.statusText}`);
4281
+ }
4282
+ if (!res.body) {
4283
+ throw new Error("No response body for SSE stream");
4284
+ }
4285
+ const result = {
4286
+ fullText: "",
4287
+ actions: [],
4288
+ suggestions: [],
4289
+ transactions: [],
4290
+ message: null
4291
+ };
4292
+ const reader = res.body.getReader();
4293
+ const decoder = new TextDecoder();
4294
+ let buffer = "";
4295
+ try {
4296
+ while (true) {
4297
+ const { done, value } = await reader.read();
4298
+ if (done) break;
4299
+ buffer += decoder.decode(value, { stream: true });
4300
+ const lines = buffer.split("\n");
4301
+ buffer = lines.pop() || "";
4302
+ let currentEvent = "";
4303
+ let currentData = "";
4304
+ for (const line of lines) {
4305
+ if (line.startsWith("event: ")) {
4306
+ currentEvent = line.slice(7).trim();
4307
+ } else if (line.startsWith("data: ")) {
4308
+ currentData += (currentData ? "\n" : "") + line.slice(6);
4309
+ } else if (line === "") {
4310
+ if (currentEvent && currentData) {
4311
+ this.handleSSEEvent(currentEvent, currentData, result, callbacks);
4312
+ }
4313
+ currentEvent = "";
4314
+ currentData = "";
4315
+ } else if (line.startsWith(": ")) {
4316
+ }
4317
+ }
4318
+ if (currentEvent && currentData) {
4319
+ this.handleSSEEvent(currentEvent, currentData, result, callbacks);
4320
+ }
4321
+ }
4322
+ } finally {
4323
+ reader.releaseLock();
4324
+ }
4325
+ return result;
4326
+ }
4327
+ handleSSEEvent(event, data, result, callbacks) {
4328
+ try {
4329
+ const parsed = JSON.parse(data);
4330
+ switch (event) {
4331
+ case "text_delta":
4332
+ result.fullText += parsed.delta;
4333
+ callbacks.onTextDelta?.(parsed.delta);
4334
+ break;
4335
+ case "tool_progress":
4336
+ if (this.verbose) process.stderr.write(`[SSE:tool_progress] raw: ${data.slice(0, 1e3)}
4337
+ `);
4338
+ callbacks.onToolProgress?.(parsed.tool, parsed.status, parsed.label);
4339
+ break;
4340
+ case "title":
4341
+ callbacks.onTitle?.(parsed.title);
4342
+ break;
4343
+ case "actions":
4344
+ if (this.verbose) process.stderr.write(`[SSE:actions] raw: ${data.slice(0, 1e3)}
4345
+ `);
4346
+ result.actions.push(...parsed.actions || []);
4347
+ callbacks.onActions?.(parsed.actions || []);
4348
+ break;
4349
+ case "suggestions":
4350
+ result.suggestions.push(...parsed.suggestions || []);
4351
+ callbacks.onSuggestions?.(parsed.suggestions || []);
4352
+ break;
4353
+ case "tx_ready":
4354
+ if (this.verbose) process.stderr.write(`[SSE:tx_ready] raw: ${data.slice(0, 2e3)}
4355
+ `);
4356
+ result.transactions.push(parsed);
4357
+ callbacks.onTxReady?.(parsed);
4358
+ break;
4359
+ case "message":
4360
+ result.message = parsed.message || parsed;
4361
+ callbacks.onMessage?.(result.message);
4362
+ break;
4363
+ case "error":
4364
+ callbacks.onError?.(parsed.error);
4365
+ break;
4366
+ case "done":
4367
+ break;
4368
+ }
4369
+ } catch {
4370
+ }
4371
+ }
4372
+ // ============================================================================
4373
+ // Private helpers
4374
+ // ============================================================================
4375
+ async post(path3, body) {
4376
+ const res = await fetch(`${this.baseUrl}${path3}`, {
4377
+ method: "POST",
4378
+ headers: {
4379
+ "Content-Type": "application/json",
4380
+ ...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
4381
+ },
4382
+ body: JSON.stringify(body)
4383
+ });
4384
+ if (!res.ok) {
4385
+ const errorBody = await res.json().catch(() => ({ error: res.statusText }));
4386
+ throw new Error(`Request failed (${res.status}): ${errorBody.error || res.statusText}`);
4387
+ }
4388
+ return await res.json();
4389
+ }
4390
+ async delete(path3, body) {
4391
+ const res = await fetch(`${this.baseUrl}${path3}`, {
4392
+ method: "DELETE",
4393
+ headers: {
4394
+ "Content-Type": "application/json",
4395
+ ...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
4396
+ },
4397
+ body: JSON.stringify(body)
4398
+ });
4399
+ if (!res.ok) {
4400
+ const errorBody = await res.json().catch(() => ({ error: res.statusText }));
4401
+ throw new Error(`Delete failed (${res.status}): ${errorBody.error || res.statusText}`);
4402
+ }
4403
+ }
4404
+ };
4405
+
4406
+ // src/agent/context.ts
4407
+ import { Chain as Chain8 } from "@vultisig/sdk";
4408
+ async function buildMessageContext(vault) {
4409
+ const context = {
4410
+ vault_address: vault.publicKeys.ecdsa,
4411
+ vault_name: vault.name
4412
+ };
4413
+ try {
4414
+ const chains = vault.chains;
4415
+ const addressEntries = await Promise.allSettled(
4416
+ chains.map(async (chain) => ({
4417
+ chain: chain.toString(),
4418
+ address: await vault.address(chain)
4419
+ }))
4420
+ );
4421
+ const addresses = {};
4422
+ for (const result of addressEntries) {
4423
+ if (result.status === "fulfilled") {
4424
+ addresses[result.value.chain] = result.value.address;
4425
+ }
4426
+ }
4427
+ context.addresses = addresses;
4428
+ } catch {
4429
+ }
4430
+ try {
4431
+ const balanceRecord = await vault.balances();
4432
+ const balanceInfos = [];
4433
+ for (const [key, balance] of Object.entries(balanceRecord)) {
4434
+ balanceInfos.push({
4435
+ chain: balance.chainId || key.split(":")[0] || "",
4436
+ asset: balance.symbol || "",
4437
+ symbol: balance.symbol || "",
4438
+ amount: balance.formattedAmount || balance.amount?.toString() || "0",
4439
+ decimals: balance.decimals || 18
4440
+ });
4441
+ }
4442
+ context.balances = balanceInfos;
4443
+ } catch {
4444
+ }
4445
+ try {
4446
+ const coins = [];
4447
+ const chains = vault.chains;
4448
+ for (const chain of chains) {
4449
+ coins.push({
4450
+ chain: chain.toString(),
4451
+ ticker: getNativeTokenTicker(chain),
4452
+ is_native_token: true,
4453
+ decimals: getNativeTokenDecimals(chain)
4454
+ });
4455
+ const tokens = vault.tokens[chain] || [];
4456
+ for (const token of tokens) {
4457
+ coins.push({
4458
+ chain: chain.toString(),
4459
+ ticker: token.symbol || "",
4460
+ contract_address: token.contractAddress || token.id,
4461
+ is_native_token: false,
4462
+ decimals: token.decimals || 18
4463
+ });
4464
+ }
4465
+ }
4466
+ context.coins = coins;
4467
+ } catch {
4468
+ }
4469
+ return context;
4470
+ }
4471
+ function getNativeTokenTicker(chain) {
4472
+ const tickers = {
4473
+ [Chain8.Ethereum]: "ETH",
4474
+ [Chain8.Bitcoin]: "BTC",
4475
+ [Chain8.Solana]: "SOL",
4476
+ [Chain8.THORChain]: "RUNE",
4477
+ [Chain8.Cosmos]: "ATOM",
4478
+ [Chain8.Avalanche]: "AVAX",
4479
+ [Chain8.BSC]: "BNB",
4480
+ [Chain8.Polygon]: "MATIC",
4481
+ [Chain8.Arbitrum]: "ETH",
4482
+ [Chain8.Optimism]: "ETH",
4483
+ [Chain8.Base]: "ETH",
4484
+ [Chain8.Blast]: "ETH",
4485
+ [Chain8.Litecoin]: "LTC",
4486
+ [Chain8.Dogecoin]: "DOGE",
4487
+ [Chain8.Dash]: "DASH",
4488
+ [Chain8.MayaChain]: "CACAO",
4489
+ [Chain8.Polkadot]: "DOT",
4490
+ [Chain8.Sui]: "SUI",
4491
+ [Chain8.Ton]: "TON",
4492
+ [Chain8.Tron]: "TRX",
4493
+ [Chain8.Ripple]: "XRP",
4494
+ [Chain8.Dydx]: "DYDX",
4495
+ [Chain8.Osmosis]: "OSMO",
4496
+ [Chain8.Terra]: "LUNA",
4497
+ [Chain8.Noble]: "USDC",
4498
+ [Chain8.Kujira]: "KUJI",
4499
+ [Chain8.Zksync]: "ETH",
4500
+ [Chain8.CronosChain]: "CRO"
4501
+ };
4502
+ return tickers[chain] || chain.toString();
4503
+ }
4504
+ function getNativeTokenDecimals(chain) {
4505
+ const decimals = {
4506
+ [Chain8.Bitcoin]: 8,
4507
+ [Chain8.Litecoin]: 8,
4508
+ [Chain8.Dogecoin]: 8,
4509
+ [Chain8.Dash]: 8,
4510
+ [Chain8.Solana]: 9,
4511
+ [Chain8.Sui]: 9,
4512
+ [Chain8.Ton]: 9,
4513
+ [Chain8.Polkadot]: 10,
4514
+ [Chain8.Cosmos]: 6,
4515
+ [Chain8.THORChain]: 8,
4516
+ [Chain8.MayaChain]: 10,
4517
+ [Chain8.Osmosis]: 6,
4518
+ [Chain8.Dydx]: 18,
4519
+ [Chain8.Tron]: 6,
4520
+ [Chain8.Ripple]: 6,
4521
+ [Chain8.Noble]: 6,
4522
+ [Chain8.Kujira]: 6,
4523
+ [Chain8.Terra]: 6
4524
+ };
4525
+ return decimals[chain] || 18;
4526
+ }
4527
+
4528
+ // src/agent/executor.ts
4529
+ import { Chain as Chain9, Vultisig as Vultisig6 } from "@vultisig/sdk";
4530
+
4531
+ // src/agent/types.ts
4532
+ var AUTO_EXECUTE_ACTIONS = /* @__PURE__ */ new Set([
4533
+ "add_chain",
4534
+ "add_coin",
4535
+ "remove_coin",
4536
+ "remove_chain",
4537
+ "address_book_add",
4538
+ "address_book_remove",
4539
+ "get_address_book",
4540
+ "get_market_price",
4541
+ "get_balances",
4542
+ "get_portfolio",
4543
+ "search_token",
4544
+ "list_vaults",
4545
+ "build_swap_tx",
4546
+ "build_send_tx",
4547
+ "build_custom_tx",
4548
+ "build_tx",
4549
+ "sign_tx",
4550
+ "sign_typed_data",
4551
+ "read_evm_contract",
4552
+ "scan_tx",
4553
+ "thorchain_query"
4554
+ ]);
4555
+ var PASSWORD_REQUIRED_ACTIONS = /* @__PURE__ */ new Set(["sign_tx", "sign_typed_data", "build_custom_tx"]);
4556
+
4557
+ // src/agent/executor.ts
4558
+ var AgentExecutor = class {
4559
+ vault;
4560
+ pendingPayloads = /* @__PURE__ */ new Map();
4561
+ password = null;
4562
+ verbose;
4563
+ constructor(vault, verbose = false) {
4564
+ this.vault = vault;
4565
+ this.verbose = verbose;
4566
+ }
4567
+ setPassword(password) {
4568
+ this.password = password;
4569
+ }
4570
+ /**
4571
+ * Store a server-built transaction (from tx_ready SSE event).
4572
+ * This allows sign_tx to find and sign it when the backend requests signing.
4573
+ */
4574
+ storeServerTransaction(txReadyData) {
4575
+ if (this.verbose) process.stderr.write(`[executor] storeServerTransaction called, keys: ${Object.keys(txReadyData || {}).join(",")}
4576
+ `);
4577
+ const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
4578
+ if (!swapTx) {
4579
+ if (this.verbose) process.stderr.write(`[executor] storeServerTransaction: no swap_tx/send_tx/tx found in data
4580
+ `);
4581
+ return;
4582
+ }
4583
+ const chain = resolveChainFromTxReady(txReadyData) || Chain9.Ethereum;
4584
+ this.pendingPayloads.set("latest", {
4585
+ payload: { __serverTx: true, ...txReadyData },
4586
+ coin: { chain, address: "", decimals: 18, ticker: "" },
4587
+ chain,
4588
+ timestamp: Date.now()
4589
+ });
4590
+ if (this.verbose) process.stderr.write(`[executor] Stored server tx for chain ${chain}, pendingPayloads size=${this.pendingPayloads.size}
4591
+ `);
4592
+ }
4593
+ hasPendingTransaction() {
4594
+ return this.pendingPayloads.has("latest");
4595
+ }
4596
+ shouldAutoExecute(action) {
4597
+ return action.auto_execute === true || AUTO_EXECUTE_ACTIONS.has(action.type);
4598
+ }
4599
+ requiresPassword(action) {
4600
+ return PASSWORD_REQUIRED_ACTIONS.has(action.type);
4601
+ }
4602
+ /**
4603
+ * Execute a single action and return the result.
4604
+ */
4605
+ async executeAction(action) {
4606
+ try {
4607
+ const data = await this.dispatch(action);
4608
+ return {
4609
+ action: action.type,
4610
+ action_id: action.id,
4611
+ success: true,
4612
+ data
4613
+ };
4614
+ } catch (err) {
4615
+ return {
4616
+ action: action.type,
4617
+ action_id: action.id,
4618
+ success: false,
4619
+ error: err.message || String(err)
4620
+ };
4621
+ }
4622
+ }
4623
+ async dispatch(action) {
4624
+ if (this.verbose) process.stderr.write(`[dispatch] action.type=${action.type} action.id=${action.id}
4625
+ `);
4626
+ const params = action.params || {};
4627
+ switch (action.type) {
4628
+ case "get_balances":
4629
+ return this.getBalances(params);
4630
+ case "get_portfolio":
4631
+ return this.getPortfolio(params);
4632
+ case "add_chain":
4633
+ return this.addChain(params);
4634
+ case "remove_chain":
4635
+ return this.removeChain(params);
4636
+ case "add_coin":
4637
+ return this.addCoin(params);
4638
+ case "remove_coin":
4639
+ return this.removeCoin(params);
4640
+ case "build_send_tx":
4641
+ return this.buildSendTx(params);
4642
+ case "build_swap_tx":
4643
+ return this.buildSwapTx(params);
4644
+ case "build_tx":
4645
+ case "build_custom_tx":
4646
+ return this.buildTx(params);
4647
+ case "sign_tx":
4648
+ return this.signTx(params);
4649
+ case "get_address_book":
4650
+ return this.getAddressBook();
4651
+ case "address_book_add":
4652
+ return this.addAddressBookEntry(params);
4653
+ case "address_book_remove":
4654
+ return this.removeAddressBookEntry(params);
4655
+ case "search_token":
4656
+ return this.searchToken(params);
4657
+ case "list_vaults":
4658
+ return this.listVaults();
4659
+ case "sign_typed_data":
4660
+ return this.signTypedData(params);
4661
+ case "scan_tx":
4662
+ return this.scanTx(params);
4663
+ case "read_evm_contract":
4664
+ return this.readEvmContract(params);
4665
+ default:
4666
+ return { message: `Action type '${action.type}' not implemented locally` };
4667
+ }
4668
+ }
4669
+ // ============================================================================
4670
+ // Balance & Portfolio
4671
+ // ============================================================================
4672
+ async getBalances(params) {
4673
+ const chainFilter = params.chain;
4674
+ const tickerFilter = params.ticker;
4675
+ const balanceRecord = await this.vault.balances();
4676
+ let entries = Object.entries(balanceRecord).map(([key, b]) => ({
4677
+ chain: b.chainId || key.split(":")[0] || "",
4678
+ symbol: b.symbol || "",
4679
+ amount: b.formattedAmount || b.amount?.toString() || "0",
4680
+ decimals: b.decimals || 18,
4681
+ raw_amount: b.amount?.toString()
4682
+ }));
4683
+ if (chainFilter) {
4684
+ const chain = resolveChain(chainFilter);
4685
+ if (chain) {
4686
+ entries = entries.filter((b) => b.chain.toLowerCase() === chain.toLowerCase());
4687
+ }
4688
+ }
4689
+ if (tickerFilter) {
4690
+ entries = entries.filter((b) => b.symbol.toLowerCase() === tickerFilter.toLowerCase());
4691
+ }
4692
+ return { balances: entries };
4693
+ }
4694
+ async getPortfolio(_params) {
4695
+ const balanceRecord = await this.vault.balances();
4696
+ const entries = Object.entries(balanceRecord).map(([key, b]) => ({
4697
+ chain: b.chainId || key.split(":")[0] || "",
4698
+ symbol: b.symbol || "",
4699
+ amount: b.formattedAmount || b.amount?.toString() || "0",
4700
+ decimals: b.decimals || 18
4701
+ }));
4702
+ return { balances: entries };
4703
+ }
4704
+ // ============================================================================
4705
+ // Chain & Token Management
4706
+ // ============================================================================
4707
+ async addChain(params) {
4708
+ const chains = params.chains;
4709
+ if (chains && Array.isArray(chains)) {
4710
+ const results = [];
4711
+ for (const c of chains) {
4712
+ const name = typeof c === "string" ? c : c.chain;
4713
+ const chain2 = resolveChain(name);
4714
+ if (!chain2) throw new Error(`Unknown chain: ${name}`);
4715
+ await this.vault.addChain(chain2);
4716
+ const address2 = await this.vault.address(chain2);
4717
+ results.push({ chain: chain2.toString(), address: address2 });
4718
+ }
4719
+ return { added: results };
4720
+ }
4721
+ const chainName = params.chain;
4722
+ const chain = resolveChain(chainName);
4723
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
4724
+ await this.vault.addChain(chain);
4725
+ const address = await this.vault.address(chain);
4726
+ return { chain: chain.toString(), address, added: true };
4727
+ }
4728
+ async removeChain(params) {
4729
+ const chainName = params.chain;
4730
+ const chain = resolveChain(chainName);
4731
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
4732
+ await this.vault.removeChain(chain);
4733
+ return { chain: chain.toString(), removed: true };
4734
+ }
4735
+ async addCoin(params) {
4736
+ const tokens = params.tokens;
4737
+ if (tokens && Array.isArray(tokens)) {
4738
+ const results = [];
4739
+ for (const t of tokens) {
4740
+ const chain2 = resolveChain(t.chain);
4741
+ if (!chain2) throw new Error(`Unknown chain: ${t.chain}`);
4742
+ const symbol2 = t.symbol || t.ticker || "";
4743
+ await this.vault.addToken(chain2, {
4744
+ id: t.contract_address || t.contractAddress || "",
4745
+ symbol: symbol2,
4746
+ name: t.name || symbol2,
4747
+ decimals: t.decimals || 18,
4748
+ contractAddress: t.contract_address || t.contractAddress,
4749
+ chainId: chain2.toString()
4750
+ });
4751
+ results.push({ chain: chain2.toString(), symbol: symbol2 });
4752
+ }
4753
+ return { added: results };
4754
+ }
4755
+ const chainName = params.chain;
4756
+ const chain = resolveChain(chainName);
4757
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
4758
+ const symbol = params.symbol || params.ticker;
4759
+ await this.vault.addToken(chain, {
4760
+ id: params.contract_address || params.contractAddress || "",
4761
+ symbol,
4762
+ name: params.name || symbol,
4763
+ decimals: params.decimals || 18,
4764
+ contractAddress: params.contract_address || params.contractAddress,
4765
+ chainId: chain.toString()
4766
+ });
4767
+ return { chain: chain.toString(), symbol, added: true };
4768
+ }
4769
+ async removeCoin(params) {
4770
+ const chainName = params.chain;
4771
+ const chain = resolveChain(chainName);
4772
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
4773
+ const tokenId = params.token_id || params.id || params.contract_address;
4774
+ await this.vault.removeToken(chain, tokenId);
4775
+ return { chain: chain.toString(), removed: true };
4776
+ }
4777
+ // ============================================================================
4778
+ // Transaction Building
4779
+ // ============================================================================
4780
+ async buildSendTx(params) {
4781
+ const chainName = params.chain || params.from_chain;
4782
+ const chain = resolveChain(chainName);
4783
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
4784
+ const symbol = params.symbol || params.ticker;
4785
+ const toAddress = params.address || params.to || params.destination;
4786
+ const amountStr = params.amount;
4787
+ if (!toAddress) throw new Error("Destination address is required");
4788
+ if (!amountStr) throw new Error("Amount is required");
4789
+ const address = await this.vault.address(chain);
4790
+ const balance = await this.vault.balance(chain, params.token_id);
4791
+ const coin = {
4792
+ chain,
4793
+ address,
4794
+ decimals: balance.decimals,
4795
+ ticker: symbol || balance.symbol,
4796
+ id: params.token_id
4797
+ };
4798
+ const amount = parseAmount(amountStr, balance.decimals);
4799
+ const memo = params.memo;
4800
+ const payload = await this.vault.prepareSendTx({ coin, receiver: toAddress, amount, memo });
4801
+ const payloadId = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
4802
+ this.pendingPayloads.set(payloadId, { payload, coin, chain, timestamp: Date.now() });
4803
+ this.pendingPayloads.set("latest", { payload, coin, chain, timestamp: Date.now() });
4804
+ const messageHashes = await this.vault.extractMessageHashes(payload);
4805
+ return {
4806
+ keysign_payload: payloadId,
4807
+ from_chain: chain.toString(),
4808
+ from_symbol: coin.ticker,
4809
+ amount: amountStr,
4810
+ sender: address,
4811
+ destination: toAddress,
4812
+ memo: memo || void 0,
4813
+ message_hashes: messageHashes,
4814
+ tx_details: {
4815
+ chain: chain.toString(),
4816
+ from: address,
4817
+ to: toAddress,
4818
+ amount: amountStr,
4819
+ symbol: coin.ticker
4820
+ }
4821
+ };
4822
+ }
4823
+ async buildSwapTx(params) {
4824
+ if (this.verbose) process.stderr.write(`[build_swap_tx] called with params: ${JSON.stringify(params).slice(0, 500)}
4825
+ `);
4826
+ const fromChainName = params.from_chain || params.chain;
4827
+ const toChainName = params.to_chain;
4828
+ const fromChain = resolveChain(fromChainName);
4829
+ const toChain = toChainName ? resolveChain(toChainName) : null;
4830
+ if (!fromChain) throw new Error(`Unknown from_chain: ${fromChainName}`);
4831
+ const amountStr = params.amount;
4832
+ const fromSymbol = params.from_symbol || params.from_token || "";
4833
+ const toSymbol = params.to_symbol || params.to_token || "";
4834
+ const fromToken = params.from_contract || params.from_token_id;
4835
+ const toToken = params.to_contract || params.to_token_id;
4836
+ const fromCoin = { chain: fromChain, token: fromToken || void 0 };
4837
+ const toCoin = { chain: toChain || fromChain, token: toToken || void 0 };
4838
+ const quote = await this.vault.getSwapQuote({
4839
+ fromCoin,
4840
+ toCoin,
4841
+ amount: parseFloat(amountStr)
4842
+ });
4843
+ const swapResult = await this.vault.prepareSwapTx({
4844
+ fromCoin,
4845
+ toCoin,
4846
+ amount: parseFloat(amountStr),
4847
+ swapQuote: quote,
4848
+ autoApprove: true
4849
+ });
4850
+ const chain = fromChain;
4851
+ const payload = swapResult.keysignPayload;
4852
+ const payloadId = `swap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
4853
+ this.pendingPayloads.set(payloadId, { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
4854
+ this.pendingPayloads.set("latest", { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
4855
+ const messageHashes = await this.vault.extractMessageHashes(payload);
4856
+ return {
4857
+ keysign_payload: payloadId,
4858
+ from_chain: fromChain.toString(),
4859
+ to_chain: (toChain || fromChain).toString(),
4860
+ from_symbol: fromSymbol,
4861
+ to_symbol: toSymbol,
4862
+ amount: amountStr,
4863
+ estimated_output: quote.estimatedOutput?.toString(),
4864
+ provider: quote.provider,
4865
+ message_hashes: messageHashes
4866
+ };
4867
+ }
4868
+ async buildTx(params) {
4869
+ if (params.function_name && params.contract_address) {
4870
+ return this.buildContractCallTx(params);
4871
+ }
4872
+ if (params.data || params.calldata || params.hex_payload) {
4873
+ const txData = {
4874
+ to: params.to || params.address || params.contract,
4875
+ value: params.value || "0",
4876
+ data: params.data || params.calldata || params.hex_payload,
4877
+ chain: params.chain,
4878
+ chain_id: params.chain_id
4879
+ };
4880
+ this.storeServerTransaction({
4881
+ tx: txData,
4882
+ chain: params.chain,
4883
+ from_chain: params.chain
4884
+ });
4885
+ const chain = resolveChain(params.chain) || Chain9.Ethereum;
4886
+ const address = await this.vault.address(chain);
4887
+ return {
4888
+ status: "ready",
4889
+ chain: chain.toString(),
4890
+ from: address,
4891
+ to: txData.to,
4892
+ value: txData.value,
4893
+ has_calldata: true,
4894
+ message: "Transaction built. Ready to sign."
4895
+ };
4896
+ }
4897
+ return this.buildSendTx(params);
4898
+ }
4899
+ /**
4900
+ * Build, sign, and broadcast an EVM contract call transaction from structured params.
4901
+ * Encodes function_name + typed params into ABI calldata, then signs via signServerTx.
4902
+ */
4903
+ async buildContractCallTx(params) {
4904
+ const chainName = params.chain || "Ethereum";
4905
+ const chain = resolveChain(chainName);
4906
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
4907
+ const contractAddress = params.contract_address;
4908
+ const functionName = params.function_name;
4909
+ const typedParams = params.params;
4910
+ const value = params.value || "0";
4911
+ const calldata = await encodeContractCall(functionName, typedParams || []);
4912
+ if (this.verbose) process.stderr.write(`[build_contract_tx] ${functionName}(${(typedParams || []).map((p) => p.type).join(",")}) on ${contractAddress} chain=${chain}
4913
+ `);
4914
+ const serverTxData = {
4915
+ __serverTx: true,
4916
+ tx: {
4917
+ to: contractAddress,
4918
+ value,
4919
+ data: calldata
4920
+ },
4921
+ chain: chainName,
4922
+ from_chain: chainName
4923
+ };
4924
+ this.pendingPayloads.set("latest", {
4925
+ payload: serverTxData,
4926
+ coin: { chain, address: "", decimals: 18, ticker: "" },
4927
+ chain,
4928
+ timestamp: Date.now()
4929
+ });
4930
+ return this.signServerTx(serverTxData, chain, { chain: chainName });
4931
+ }
4932
+ // ============================================================================
4933
+ // Transaction Signing
4934
+ // ============================================================================
4935
+ async signTx(params) {
4936
+ if (this.verbose) process.stderr.write(`[sign_tx] params: ${JSON.stringify(params).slice(0, 500)}
4937
+ `);
4938
+ if (this.verbose) process.stderr.write(`[sign_tx] pendingPayloads keys: ${[...this.pendingPayloads.keys()].join(", ")}
4939
+ `);
4940
+ const payloadId = params.keysign_payload || params.payload_id || "latest";
4941
+ const stored = this.pendingPayloads.get(payloadId);
4942
+ if (!stored) {
4943
+ throw new Error("No pending transaction to sign. Build a transaction first.");
4944
+ }
4945
+ const { payload, chain } = stored;
4946
+ if (payload.__serverTx) {
4947
+ return this.signServerTx(payload, chain, params);
4948
+ }
4949
+ return this.signSdkTx(payload, chain, payloadId);
4950
+ }
4951
+ /**
4952
+ * Sign and broadcast an SDK-built transaction (keysign payload from local build methods).
4953
+ */
4954
+ async signSdkTx(payload, chain, payloadId) {
4955
+ if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
4956
+ if (this.password) {
4957
+ await this.vault.unlock?.(this.password);
4958
+ }
4959
+ }
4960
+ const messageHashes = await this.vault.extractMessageHashes(payload);
4961
+ const signature = await this.vault.sign(
4962
+ {
4963
+ transaction: payload,
4964
+ chain: payload.coin?.chain || chain,
4965
+ messageHashes
4966
+ },
4967
+ {}
4968
+ );
4969
+ const txHash = await this.vault.broadcastTx({
4970
+ chain,
4971
+ keysignPayload: payload,
4972
+ signature
4973
+ });
4974
+ this.pendingPayloads.delete(payloadId);
4975
+ if (payloadId !== "latest") {
4976
+ this.pendingPayloads.delete("latest");
4977
+ }
4978
+ const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
4979
+ return {
4980
+ tx_hash: txHash,
4981
+ chain: chain.toString(),
4982
+ status: "pending",
4983
+ explorer_url: explorerUrl
4984
+ };
4985
+ }
4986
+ /**
4987
+ * Sign and broadcast a server-built transaction (raw EVM tx from tx_ready SSE).
4988
+ * Uses vault.prepareSendTx with memo field to carry the calldata.
4989
+ */
4990
+ async signServerTx(serverTxData, defaultChain, params) {
4991
+ const swapTx = serverTxData.swap_tx || serverTxData.send_tx || serverTxData.tx;
4992
+ if (!swapTx?.to) {
4993
+ throw new Error("Server transaction missing required fields (to)");
4994
+ }
4995
+ const chainName = params.chain || serverTxData.chain || serverTxData.from_chain;
4996
+ const chainId = serverTxData.chain_id || swapTx.chainId;
4997
+ let chain = defaultChain;
4998
+ if (chainName) {
4999
+ chain = resolveChain(chainName) || defaultChain;
5000
+ } else if (chainId) {
5001
+ chain = resolveChainId(chainId) || defaultChain;
5002
+ }
5003
+ const address = await this.vault.address(chain);
5004
+ const balance = await this.vault.balance(chain);
5005
+ const coin = {
5006
+ chain,
5007
+ address,
5008
+ decimals: balance.decimals || 18,
5009
+ ticker: balance.symbol || chain.toString()
5010
+ };
5011
+ const amount = BigInt(swapTx.value || "0");
5012
+ const hasCalldata = !!(swapTx.data && swapTx.data !== "0x");
5013
+ if (this.verbose) process.stderr.write(`[sign_server_tx] chain=${chain}, to=${swapTx.to}, value=${swapTx.value}, amount=${amount}, hasCalldata=${hasCalldata}
5014
+ `);
5015
+ if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
5016
+ if (this.password) {
5017
+ await this.vault.unlock?.(this.password);
5018
+ }
5019
+ }
5020
+ const buildAmount = amount === 0n && hasCalldata ? 1n : amount;
5021
+ const keysignPayload = await this.vault.prepareSendTx({
5022
+ coin,
5023
+ receiver: swapTx.to,
5024
+ amount: buildAmount,
5025
+ memo: swapTx.data
5026
+ });
5027
+ if (amount === 0n && hasCalldata) {
5028
+ ;
5029
+ keysignPayload.toAmount = "0";
5030
+ }
5031
+ const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
5032
+ const signature = await this.vault.sign(
5033
+ {
5034
+ transaction: keysignPayload,
5035
+ chain,
5036
+ messageHashes
5037
+ },
5038
+ {}
5039
+ );
5040
+ const txHash = await this.vault.broadcastTx({
5041
+ chain,
5042
+ keysignPayload,
5043
+ signature
5044
+ });
5045
+ this.pendingPayloads.delete("latest");
5046
+ const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
5047
+ return {
5048
+ tx_hash: txHash,
5049
+ chain: chain.toString(),
5050
+ status: "pending",
5051
+ explorer_url: explorerUrl
5052
+ };
5053
+ }
5054
+ // ============================================================================
5055
+ // EIP-712 Typed Data Signing
5056
+ // ============================================================================
5057
+ /**
5058
+ * Sign EIP-712 typed data. Computes the EIP-712 hash and signs with vault.signBytes().
5059
+ * Supports two formats:
5060
+ * - Flat: { domain, types, message, primaryType } — single typed data
5061
+ * - Payloads array: { payloads: [{id, domain, types, message, primaryType, chain}, ...] }
5062
+ * Used by Polymarket which requires signing both an Order and a ClobAuth.
5063
+ */
5064
+ async signTypedData(params) {
5065
+ if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
5066
+ if (this.password) {
5067
+ await this.vault.unlock?.(this.password);
5068
+ }
5069
+ }
5070
+ const payloads = params.payloads;
5071
+ if (payloads && Array.isArray(payloads)) {
5072
+ if (this.verbose) process.stderr.write(`[sign_typed_data] payloads mode, ${payloads.length} items
5073
+ `);
5074
+ const signatures = [];
5075
+ for (let i = 0; i < payloads.length; i++) {
5076
+ const payload = payloads[i];
5077
+ const id = payload.id || payload.name || "default";
5078
+ if (i > 0) {
5079
+ if (this.verbose) process.stderr.write(`[sign_typed_data] waiting 5s between MPC sessions...
5080
+ `);
5081
+ await new Promise((r) => setTimeout(r, 5e3));
5082
+ }
5083
+ const sig = await this.signSingleTypedData(payload);
5084
+ signatures.push({ id, ...sig });
5085
+ if (this.verbose) process.stderr.write(`[sign_typed_data] signed payload "${id}"
5086
+ `);
5087
+ }
5088
+ return {
5089
+ signatures,
5090
+ pm_order_ref: params.pm_order_ref,
5091
+ auto_submit: !!(params.__pm_auto_submit || params.auto_submit)
5092
+ };
5093
+ }
5094
+ return this.signSingleTypedData(params);
5095
+ }
5096
+ /**
5097
+ * Sign a single EIP-712 typed data object.
5098
+ */
5099
+ async signSingleTypedData(params) {
5100
+ const domain = params.domain;
5101
+ const types = params.types;
5102
+ const message = params.message;
5103
+ const primaryType = params.primaryType || params.primary_type;
5104
+ if (!domain || !types || !message || !primaryType) {
5105
+ throw new Error("sign_typed_data requires domain, types, message, and primaryType");
5106
+ }
5107
+ if (this.verbose) process.stderr.write(`[sign_typed_data] primaryType=${primaryType} domain.name=${domain.name}
5108
+ `);
5109
+ const eip712Hash = await computeEIP712Hash(domain, types, primaryType, message);
5110
+ if (this.verbose) process.stderr.write(`[sign_typed_data] hash=${eip712Hash}
5111
+ `);
5112
+ const chainName = params.chain;
5113
+ const chainId = domain.chainId;
5114
+ let chain = Chain9.Ethereum;
5115
+ if (chainName) {
5116
+ chain = resolveChain(chainName) || Chain9.Ethereum;
5117
+ } else if (chainId) {
5118
+ chain = resolveChainId(chainId) || Chain9.Ethereum;
5119
+ }
5120
+ const sigResult = await this.vault.signBytes({
5121
+ data: eip712Hash,
5122
+ chain
5123
+ });
5124
+ if (this.verbose) process.stderr.write(`[sign_typed_data] signed, format=${sigResult.format}, recovery=${sigResult.recovery}
5125
+ `);
5126
+ const { r, s } = parseDERSignature(sigResult.signature);
5127
+ const v = (sigResult.recovery ?? 0) + 27;
5128
+ const ethSignature = "0x" + r + s + v.toString(16).padStart(2, "0");
5129
+ if (this.verbose) process.stderr.write(`[sign_typed_data] r=${r.slice(0, 16)}... s=${s.slice(0, 16)}... v=${v}
5130
+ `);
5131
+ return {
5132
+ signature: ethSignature,
5133
+ r: "0x" + r,
5134
+ s: "0x" + s,
5135
+ v,
5136
+ recovery: sigResult.recovery,
5137
+ hash: eip712Hash
5138
+ };
5139
+ }
5140
+ // ============================================================================
5141
+ // Address Book
5142
+ // ============================================================================
5143
+ async getAddressBook() {
5144
+ return { entries: [], message: "Address book retrieved" };
5145
+ }
5146
+ async addAddressBookEntry(params) {
5147
+ return { added: true, name: params.name, address: params.address, chain: params.chain };
5148
+ }
5149
+ async removeAddressBookEntry(params) {
5150
+ return { removed: true, address: params.address };
5151
+ }
5152
+ // ============================================================================
5153
+ // Token Search & Other
5154
+ // ============================================================================
5155
+ async searchToken(params) {
5156
+ return { message: `Token search for '${params.query || params.symbol}' - delegated to backend` };
5157
+ }
5158
+ async listVaults() {
5159
+ return {
5160
+ vaults: [{
5161
+ name: this.vault.name,
5162
+ id: this.vault.id,
5163
+ type: this.vault.type,
5164
+ chains: this.vault.chains.map((c) => c.toString())
5165
+ }]
5166
+ };
5167
+ }
5168
+ async scanTx(params) {
5169
+ return { message: `Transaction scan for ${params.tx_hash || "unknown"} - delegated to backend` };
5170
+ }
5171
+ async readEvmContract(params) {
5172
+ return { message: `EVM contract read for ${params.contract || "unknown"} - delegated to backend` };
5173
+ }
5174
+ };
5175
+ async function encodeContractCall(functionName, params) {
5176
+ const types = params.map((p) => p.type);
5177
+ const sig = `${functionName}(${types.join(",")})`;
5178
+ const selector = await keccak256Selector(sig);
5179
+ let encoded = "";
5180
+ for (const param of params) {
5181
+ encoded += abiEncodeParam(param.type, param.value);
5182
+ }
5183
+ return "0x" + selector + encoded;
5184
+ }
5185
+ async function keccak256Selector(sig) {
5186
+ const { keccak_256: keccak_2562 } = await Promise.resolve().then(() => (init_sha3(), sha3_exports));
5187
+ const hash = keccak_2562(new TextEncoder().encode(sig));
5188
+ return Buffer.from(hash).toString("hex").slice(0, 8);
5189
+ }
5190
+ function abiEncodeParam(type, value) {
5191
+ if (type === "address") {
5192
+ const addr = value.startsWith("0x") ? value.slice(2) : value;
5193
+ return addr.toLowerCase().padStart(64, "0");
5194
+ }
5195
+ if (type.startsWith("uint") || type.startsWith("int")) {
5196
+ const n = BigInt(value);
5197
+ const hex = n.toString(16);
5198
+ return hex.padStart(64, "0");
5199
+ }
5200
+ if (type === "bool") {
5201
+ return (value === "true" || value === "1" ? "1" : "0").padStart(64, "0");
5202
+ }
5203
+ if (type === "bytes32") {
5204
+ const b2 = value.startsWith("0x") ? value.slice(2) : value;
5205
+ return b2.padEnd(64, "0");
5206
+ }
5207
+ const b = value.startsWith("0x") ? value.slice(2) : Buffer.from(value).toString("hex");
5208
+ return b.padStart(64, "0");
5209
+ }
5210
+ function resolveChain(name) {
5211
+ if (!name) return null;
5212
+ if (Object.values(Chain9).includes(name)) {
5213
+ return name;
5214
+ }
5215
+ const lower = name.toLowerCase();
5216
+ for (const [, value] of Object.entries(Chain9)) {
5217
+ if (typeof value === "string" && value.toLowerCase() === lower) {
5218
+ return value;
5219
+ }
5220
+ }
5221
+ const aliases = {
5222
+ eth: "Ethereum",
5223
+ btc: "Bitcoin",
5224
+ sol: "Solana",
5225
+ bnb: "BSC",
5226
+ avax: "Avalanche",
5227
+ matic: "Polygon",
5228
+ arb: "Arbitrum",
5229
+ op: "Optimism",
5230
+ ltc: "Litecoin",
5231
+ doge: "Dogecoin",
5232
+ dot: "Polkadot",
5233
+ atom: "Cosmos",
5234
+ rune: "THORChain",
5235
+ thor: "THORChain",
5236
+ sui: "Sui",
5237
+ ton: "Ton",
5238
+ trx: "Tron",
5239
+ xrp: "Ripple"
5240
+ };
5241
+ const aliased = aliases[lower];
5242
+ if (aliased && Object.values(Chain9).includes(aliased)) {
5243
+ return aliased;
5244
+ }
5245
+ return null;
5246
+ }
5247
+ function parseAmount(amountStr, decimals) {
5248
+ const [whole, frac = ""] = amountStr.split(".");
5249
+ const paddedFrac = frac.slice(0, decimals).padEnd(decimals, "0");
5250
+ return BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(paddedFrac || "0");
5251
+ }
5252
+ function resolveChainFromTxReady(txReadyData) {
5253
+ if (txReadyData.chain) {
5254
+ const chain = resolveChain(txReadyData.chain);
5255
+ if (chain) return chain;
5256
+ }
5257
+ if (txReadyData.from_chain) {
5258
+ const chain = resolveChain(txReadyData.from_chain);
5259
+ if (chain) return chain;
5260
+ }
5261
+ if (txReadyData.chain_id) {
5262
+ const chain = resolveChainId(txReadyData.chain_id);
5263
+ if (chain) return chain;
5264
+ }
5265
+ const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
5266
+ if (swapTx?.chainId) {
5267
+ const chain = resolveChainId(swapTx.chainId);
5268
+ if (chain) return chain;
5269
+ }
5270
+ return null;
5271
+ }
5272
+ function resolveChainId(chainId) {
5273
+ const id = typeof chainId === "string" ? parseInt(chainId, 10) : chainId;
5274
+ if (isNaN(id)) return null;
5275
+ const chainIdMap = {
5276
+ 1: Chain9.Ethereum,
5277
+ 56: Chain9.BSC,
5278
+ 137: Chain9.Polygon,
5279
+ 43114: Chain9.Avalanche,
5280
+ 42161: Chain9.Arbitrum,
5281
+ 10: Chain9.Optimism,
5282
+ 8453: Chain9.Base,
5283
+ 81457: Chain9.Blast,
5284
+ 324: Chain9.Zksync,
5285
+ 25: Chain9.CronosChain
5286
+ };
5287
+ return chainIdMap[id] || null;
5288
+ }
5289
+ async function computeEIP712Hash(domain, types, primaryType, message) {
5290
+ const { keccak_256: keccak_2562 } = await Promise.resolve().then(() => (init_sha3(), sha3_exports));
5291
+ const domainSeparator = hashStruct("EIP712Domain", domain, types, keccak_2562);
5292
+ const messageHash = hashStruct(primaryType, message, types, keccak_2562);
5293
+ const prefix = new Uint8Array([25, 1]);
5294
+ const combined = new Uint8Array(2 + 32 + 32);
5295
+ combined.set(prefix, 0);
5296
+ combined.set(domainSeparator, 2);
5297
+ combined.set(messageHash, 34);
5298
+ const finalHash = keccak_2562(combined);
5299
+ return "0x" + Buffer.from(finalHash).toString("hex");
5300
+ }
5301
+ function hashStruct(typeName, data, types, keccak) {
5302
+ const typeHash = hashType(typeName, types, keccak);
5303
+ const encodedData = encodeData(typeName, data, types, keccak);
5304
+ const combined = new Uint8Array(32 + encodedData.length);
5305
+ combined.set(typeHash, 0);
5306
+ combined.set(encodedData, 32);
5307
+ return keccak(combined);
5308
+ }
5309
+ function hashType(typeName, types, keccak) {
5310
+ const encoded = encodeType(typeName, types);
5311
+ return keccak(new TextEncoder().encode(encoded));
5312
+ }
5313
+ function encodeType(typeName, types) {
5314
+ const fields = getTypeFields(typeName, types);
5315
+ if (!fields) return "";
5316
+ const refs = /* @__PURE__ */ new Set();
5317
+ findReferencedTypes(typeName, types, refs);
5318
+ refs.delete(typeName);
5319
+ const sortedRefs = [...refs].sort();
5320
+ let result = `${typeName}(${fields.map((f) => `${f.type} ${f.name}`).join(",")})`;
5321
+ for (const ref of sortedRefs) {
5322
+ const refFields = getTypeFields(ref, types);
5323
+ if (refFields) {
5324
+ result += `${ref}(${refFields.map((f) => `${f.type} ${f.name}`).join(",")})`;
5325
+ }
5326
+ }
5327
+ return result;
5328
+ }
5329
+ function findReferencedTypes(typeName, types, refs) {
5330
+ if (refs.has(typeName)) return;
5331
+ const fields = getTypeFields(typeName, types);
5332
+ if (!fields) return;
5333
+ refs.add(typeName);
5334
+ for (const field of fields) {
5335
+ const baseType = field.type.replace(/\[\d*\]$/, "");
5336
+ if (types[baseType]) {
5337
+ findReferencedTypes(baseType, types, refs);
5338
+ }
5339
+ }
5340
+ }
5341
+ function getTypeFields(typeName, types) {
5342
+ if (types[typeName]) return types[typeName];
5343
+ if (typeName === "EIP712Domain") {
5344
+ return [
5345
+ { name: "name", type: "string" },
5346
+ { name: "version", type: "string" },
5347
+ { name: "chainId", type: "uint256" },
5348
+ { name: "verifyingContract", type: "address" }
5349
+ ];
5350
+ }
5351
+ return void 0;
5352
+ }
5353
+ function encodeData(typeName, data, types, keccak) {
5354
+ const fields = getTypeFields(typeName, types);
5355
+ if (!fields) return new Uint8Array(0);
5356
+ const chunks = [];
5357
+ for (const field of fields) {
5358
+ const value = data[field.name];
5359
+ if (value === void 0 || value === null) continue;
5360
+ chunks.push(encodeField(field.type, value, types, keccak));
5361
+ }
5362
+ const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
5363
+ const result = new Uint8Array(totalLen);
5364
+ let offset = 0;
5365
+ for (const chunk of chunks) {
5366
+ result.set(chunk, offset);
5367
+ offset += chunk.length;
5368
+ }
5369
+ return result;
5370
+ }
5371
+ function encodeField(type, value, types, keccak) {
5372
+ if (type === "string") {
5373
+ return keccak(new TextEncoder().encode(value));
5374
+ }
5375
+ if (type === "bytes") {
5376
+ const hex2 = value.startsWith("0x") ? value.slice(2) : value;
5377
+ const bytes2 = hexToBytes(hex2);
5378
+ return keccak(bytes2);
5379
+ }
5380
+ const baseType = type.replace(/\[\d*\]$/, "");
5381
+ if (types[baseType] && !type.endsWith("]")) {
5382
+ return hashStruct(baseType, value, types, keccak);
5383
+ }
5384
+ if (type.endsWith("]")) {
5385
+ const arr = value;
5386
+ const elementType = type.replace(/\[\d*\]$/, "");
5387
+ const encodedElements = arr.map((el) => encodeField(elementType, el, types, keccak));
5388
+ const totalLen = encodedElements.reduce((sum, e) => sum + e.length, 0);
5389
+ const concat = new Uint8Array(totalLen);
5390
+ let off = 0;
5391
+ for (const el of encodedElements) {
5392
+ concat.set(el, off);
5393
+ off += el.length;
5394
+ }
5395
+ return keccak(concat);
5396
+ }
5397
+ const result = new Uint8Array(32);
5398
+ if (type === "address") {
5399
+ const addr = value.startsWith("0x") ? value.slice(2) : value;
5400
+ const bytes2 = hexToBytes(addr.toLowerCase());
5401
+ result.set(bytes2, 32 - bytes2.length);
5402
+ return result;
5403
+ }
5404
+ if (type === "bool") {
5405
+ if (value === true || value === "true" || value === 1 || value === "1") {
5406
+ result[31] = 1;
5407
+ }
5408
+ return result;
5409
+ }
5410
+ if (type.startsWith("uint") || type.startsWith("int")) {
5411
+ const n2 = BigInt(value);
5412
+ const hex2 = n2.toString(16).padStart(64, "0");
5413
+ const bytes2 = hexToBytes(hex2);
5414
+ result.set(bytes2, 32 - bytes2.length);
5415
+ return result;
5416
+ }
5417
+ if (type.startsWith("bytes")) {
5418
+ const hex2 = value.startsWith("0x") ? value.slice(2) : value;
5419
+ const bytes2 = hexToBytes(hex2);
5420
+ result.set(bytes2, 0);
5421
+ return result;
5422
+ }
5423
+ const n = BigInt(value);
5424
+ const hex = n.toString(16).padStart(64, "0");
5425
+ const bytes = hexToBytes(hex);
5426
+ result.set(bytes, 32 - bytes.length);
5427
+ return result;
5428
+ }
5429
+ function hexToBytes(hex) {
5430
+ const clean2 = hex.startsWith("0x") ? hex.slice(2) : hex;
5431
+ const padded = clean2.length % 2 === 1 ? "0" + clean2 : clean2;
5432
+ const bytes = new Uint8Array(padded.length / 2);
5433
+ for (let i = 0; i < bytes.length; i++) {
5434
+ bytes[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
5435
+ }
5436
+ return bytes;
5437
+ }
5438
+ function parseDERSignature(sigHex) {
5439
+ const raw = sigHex.startsWith("0x") ? sigHex.slice(2) : sigHex;
5440
+ if (raw.length === 128) {
5441
+ return { r: raw.slice(0, 64), s: raw.slice(64) };
5442
+ }
5443
+ let offset = 0;
5444
+ if (raw.slice(offset, offset + 2) !== "30") {
5445
+ return { r: raw.slice(0, 64).padStart(64, "0"), s: raw.slice(64).padStart(64, "0") };
5446
+ }
5447
+ offset += 2;
5448
+ offset += 2;
5449
+ if (raw.slice(offset, offset + 2) !== "02") throw new Error("Invalid DER: expected 02 for R");
5450
+ offset += 2;
5451
+ const rLen = parseInt(raw.slice(offset, offset + 2), 16);
5452
+ offset += 2;
5453
+ let rHex = raw.slice(offset, offset + rLen * 2);
5454
+ offset += rLen * 2;
5455
+ if (rHex.length > 64 && rHex.startsWith("00")) {
5456
+ rHex = rHex.slice(rHex.length - 64);
5457
+ }
5458
+ rHex = rHex.padStart(64, "0");
5459
+ if (raw.slice(offset, offset + 2) !== "02") throw new Error("Invalid DER: expected 02 for S");
5460
+ offset += 2;
5461
+ const sLen = parseInt(raw.slice(offset, offset + 2), 16);
5462
+ offset += 2;
5463
+ let sHex = raw.slice(offset, offset + sLen * 2);
5464
+ if (sHex.length > 64 && sHex.startsWith("00")) {
5465
+ sHex = sHex.slice(sHex.length - 64);
5466
+ }
5467
+ sHex = sHex.padStart(64, "0");
5468
+ return { r: rHex, s: sHex };
5469
+ }
5470
+
5471
+ // src/agent/pipe.ts
5472
+ import * as readline from "node:readline";
5473
+ var PipeInterface = class {
5474
+ session;
5475
+ rl = null;
5476
+ stopped = false;
5477
+ pendingPasswordResolve = null;
5478
+ pendingConfirmResolve = null;
5479
+ constructor(session) {
5480
+ this.session = session;
5481
+ process.stdin.pause();
5482
+ }
5483
+ /**
5484
+ * Start the pipe interface.
5485
+ */
5486
+ async start(vaultName, addresses) {
5487
+ this.rl = readline.createInterface({
5488
+ input: process.stdin,
5489
+ output: void 0,
5490
+ // Don't write prompts to stdout
5491
+ terminal: false
5492
+ });
5493
+ this.emit({ type: "ready", vault: vaultName, addresses });
5494
+ const lines = [];
5495
+ let inputDone = false;
5496
+ let processing = false;
5497
+ this.rl.on("line", async (line) => {
5498
+ const trimmed = line.trim();
5499
+ if (!trimmed) return;
5500
+ lines.push(trimmed);
5501
+ if (!processing) {
5502
+ processing = true;
5503
+ while (lines.length > 0) {
5504
+ const nextLine = lines.shift();
5505
+ try {
5506
+ const cmd = JSON.parse(nextLine);
5507
+ await this.handleCommand(cmd);
5508
+ } catch (err) {
5509
+ this.emit({ type: "error", message: `Invalid input: ${err.message}` });
5510
+ }
5511
+ }
5512
+ processing = false;
5513
+ if (inputDone && lines.length === 0) {
5514
+ this.stop();
5515
+ }
5516
+ }
5517
+ });
5518
+ this.rl.on("close", () => {
5519
+ inputDone = true;
5520
+ if (!processing && lines.length === 0) {
5521
+ this.stop();
5522
+ }
5523
+ });
5524
+ await new Promise((resolve) => {
5525
+ const check = setInterval(() => {
5526
+ if (this.stopped) {
5527
+ clearInterval(check);
5528
+ resolve();
5529
+ }
5530
+ }, 100);
5531
+ });
5532
+ }
5533
+ stop() {
5534
+ if (this.stopped) return;
5535
+ this.stopped = true;
5536
+ this.rl?.close();
5537
+ this.session.dispose();
5538
+ }
5539
+ /**
5540
+ * Get UI callbacks for the session.
5541
+ */
5542
+ getCallbacks() {
5543
+ return {
5544
+ onTextDelta: (delta) => {
5545
+ this.emit({ type: "text_delta", delta });
5546
+ },
5547
+ onToolCall: (id, action, params) => {
5548
+ this.emit({ type: "tool_call", id, action, params, status: "running" });
5549
+ },
5550
+ onToolResult: (id, action, success2, data, error2) => {
5551
+ this.emit({ type: "tool_result", id, action, success: success2, data, error: error2 });
5552
+ },
5553
+ onAssistantMessage: (content) => {
5554
+ this.emit({ type: "assistant", content });
5555
+ },
5556
+ onSuggestions: (suggestions) => {
5557
+ this.emit({ type: "suggestions", suggestions });
5558
+ },
5559
+ onTxStatus: (txHash, chain, status, explorerUrl) => {
5560
+ this.emit({
5561
+ type: "tx_status",
5562
+ tx_hash: txHash,
5563
+ chain,
5564
+ status,
5565
+ explorer_url: explorerUrl
5566
+ });
5567
+ },
5568
+ onError: (message) => {
5569
+ this.emit({ type: "error", message });
5570
+ },
5571
+ onDone: () => {
5572
+ this.emit({ type: "done" });
5573
+ },
5574
+ requestPassword: async () => {
5575
+ return new Promise((resolve) => {
5576
+ this.pendingPasswordResolve = resolve;
5577
+ this.emit({ type: "error", message: "PASSWORD_REQUIRED" });
5578
+ });
5579
+ },
5580
+ requestConfirmation: async (message) => {
5581
+ return new Promise((resolve) => {
5582
+ this.pendingConfirmResolve = resolve;
5583
+ this.emit({ type: "error", message: `CONFIRMATION_REQUIRED: ${message}` });
5584
+ });
5585
+ }
5586
+ };
5587
+ }
5588
+ async handleCommand(cmd) {
5589
+ switch (cmd.type) {
5590
+ case "message": {
5591
+ const callbacks = this.getCallbacks();
5592
+ try {
5593
+ await this.session.sendMessage(cmd.content, callbacks);
5594
+ } catch (err) {
5595
+ this.emit({ type: "error", message: err.message });
5596
+ this.emit({ type: "done" });
5597
+ }
5598
+ break;
5599
+ }
5600
+ case "password": {
5601
+ if (this.pendingPasswordResolve) {
5602
+ this.pendingPasswordResolve(cmd.password);
5603
+ this.pendingPasswordResolve = null;
5604
+ }
5605
+ break;
5606
+ }
5607
+ case "confirm": {
5608
+ if (this.pendingConfirmResolve) {
5609
+ this.pendingConfirmResolve(cmd.confirmed);
5610
+ this.pendingConfirmResolve = null;
5611
+ }
5612
+ break;
5613
+ }
5614
+ default:
5615
+ this.emit({ type: "error", message: `Unknown command type: ${cmd.type}` });
5616
+ }
5617
+ }
5618
+ emit(event) {
5619
+ process.stdout.write(JSON.stringify(event) + "\n");
5620
+ }
5621
+ };
5622
+
5623
+ // src/agent/session.ts
5624
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5625
+ import { homedir } from "node:os";
5626
+ import { join } from "node:path";
5627
+ var AgentSession = class {
5628
+ client;
5629
+ vault;
5630
+ executor;
5631
+ config;
5632
+ conversationId = null;
5633
+ publicKey;
5634
+ cachedContext = null;
5635
+ abortController = null;
5636
+ constructor(vault, config) {
5637
+ this.vault = vault;
5638
+ this.config = config;
5639
+ this.client = new AgentClient(config.backendUrl);
5640
+ this.client.verbose = !!config.verbose;
5641
+ this.executor = new AgentExecutor(vault, !!config.verbose);
5642
+ this.publicKey = vault.publicKeys.ecdsa;
5643
+ if (config.password) {
5644
+ this.executor.setPassword(config.password);
5645
+ }
5646
+ }
5647
+ /**
5648
+ * Initialize the session: health check, authenticate, create conversation.
5649
+ */
5650
+ async initialize(ui) {
5651
+ const healthy = await this.client.healthCheck();
5652
+ if (!healthy) {
5653
+ throw new Error(`Agent backend unreachable at ${this.config.backendUrl}`);
5654
+ }
5655
+ try {
5656
+ if (this.vault.isEncrypted) {
5657
+ const password = this.config.password || await ui.requestPassword();
5658
+ await this.vault.unlock?.(password);
5659
+ this.executor.setPassword(password);
5660
+ }
5661
+ const cached = loadCachedToken(this.publicKey);
5662
+ if (cached) {
5663
+ this.client.setAuthToken(cached);
5664
+ } else {
5665
+ const auth = await authenticateVault(this.client, this.vault, this.config.password);
5666
+ this.client.setAuthToken(auth.token);
5667
+ saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
5668
+ }
5669
+ } catch (err) {
5670
+ throw new Error(`Authentication failed: ${err.message}`);
5671
+ }
5672
+ if (this.config.conversationId) {
5673
+ this.conversationId = this.config.conversationId;
5674
+ } else {
5675
+ const conv = await this.client.createConversation(this.publicKey);
5676
+ this.conversationId = conv.id;
5677
+ }
5678
+ this.cachedContext = await buildMessageContext(this.vault);
5679
+ }
5680
+ getConversationId() {
5681
+ return this.conversationId;
5682
+ }
5683
+ getVaultAddresses() {
5684
+ return this.cachedContext?.addresses || {};
5685
+ }
5686
+ /**
5687
+ * Send a user message and process the full response cycle.
5688
+ *
5689
+ * Flow:
5690
+ * 1. Send message to backend via SSE stream
5691
+ * 2. Collect text deltas and actions
5692
+ * 3. Execute auto-execute actions locally
5693
+ * 4. Report results back to backend
5694
+ * 5. Repeat if backend sends more actions
5695
+ */
5696
+ async sendMessage(content, ui) {
5697
+ if (!this.conversationId) {
5698
+ throw new Error("Session not initialized");
5699
+ }
5700
+ this.abortController = new AbortController();
5701
+ try {
5702
+ this.cachedContext = await buildMessageContext(this.vault);
5703
+ } catch {
5704
+ }
5705
+ try {
5706
+ await this.processMessageLoop(content, null, ui);
5707
+ } catch (err) {
5708
+ if (err.message?.includes("401") || err.message?.includes("403")) {
5709
+ clearCachedToken(this.publicKey);
5710
+ const auth = await authenticateVault(this.client, this.vault, this.config.password);
5711
+ this.client.setAuthToken(auth.token);
5712
+ saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
5713
+ await this.processMessageLoop(content, null, ui);
5714
+ } else {
5715
+ throw err;
5716
+ }
5717
+ } finally {
5718
+ this.abortController = null;
5719
+ }
5720
+ }
5721
+ /**
5722
+ * Core message processing loop.
5723
+ * Sends content or action results, executes returned actions, repeats.
5724
+ */
5725
+ async processMessageLoop(content, actionResults, ui) {
5726
+ if (!this.conversationId) return;
5727
+ const request = {
5728
+ public_key: this.publicKey,
5729
+ context: this.cachedContext
5730
+ };
5731
+ if (content) {
5732
+ request.content = content;
5733
+ }
5734
+ if (actionResults && actionResults.length > 0) {
5735
+ const result = actionResults[0];
5736
+ request.action_result = {
5737
+ action: result.action,
5738
+ action_id: result.action_id,
5739
+ success: result.success,
5740
+ data: result.data || {},
5741
+ error: result.error || ""
5742
+ };
5743
+ }
5744
+ const streamResult = await this.client.sendMessageStream(
5745
+ this.conversationId,
5746
+ request,
5747
+ {
5748
+ onTextDelta: (delta) => ui.onTextDelta(delta),
5749
+ onToolProgress: (tool, status, label) => {
5750
+ if (status === "running") {
5751
+ ui.onToolCall(`mcp-${tool}`, tool);
5752
+ } else {
5753
+ ui.onToolResult(`mcp-${tool}`, tool, true, { label });
5754
+ }
5755
+ },
5756
+ onTitle: (_title) => {
5757
+ },
5758
+ onActions: (_actions) => {
5759
+ },
5760
+ onSuggestions: (suggestions) => {
5761
+ ui.onSuggestions(suggestions);
5762
+ },
5763
+ onTxReady: (tx) => {
5764
+ this.executor.storeServerTransaction(tx);
5765
+ if (this.config.password) {
5766
+ this.executor.setPassword(this.config.password);
5767
+ }
5768
+ },
5769
+ onMessage: (_msg) => {
5770
+ },
5771
+ onError: (error2) => {
5772
+ ui.onError(error2);
5773
+ }
5774
+ },
5775
+ this.abortController?.signal
5776
+ );
5777
+ const responseText = streamResult.fullText || streamResult.message?.content || "";
5778
+ const inlineActions = parseInlineToolCalls(responseText);
5779
+ if (inlineActions.length > 0) {
5780
+ const cleanText = responseText.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").trim();
5781
+ if (cleanText) {
5782
+ ui.onAssistantMessage(cleanText);
5783
+ }
5784
+ streamResult.actions.push(...inlineActions);
5785
+ } else if (responseText) {
5786
+ ui.onAssistantMessage(responseText);
5787
+ }
5788
+ const nonSignActions = streamResult.actions.filter((a) => a.type !== "sign_tx");
5789
+ const backendSignActions = streamResult.actions.filter((a) => a.type === "sign_tx");
5790
+ if (nonSignActions.length > 0) {
5791
+ const results = await this.executeActions(nonSignActions, ui);
5792
+ const hasBuildSuccess = results.some(
5793
+ (r) => r.success && r.action.startsWith("build_")
5794
+ );
5795
+ if (hasBuildSuccess && this.executor.hasPendingTransaction()) {
5796
+ if (this.config.verbose) process.stderr.write(`[session] build_* action produced pending tx, auto-chaining sign_tx
5797
+ `);
5798
+ const signAction = {
5799
+ id: `tx_sign_${Date.now()}`,
5800
+ type: "sign_tx",
5801
+ title: "Sign transaction",
5802
+ params: {},
5803
+ auto_execute: true
5804
+ };
5805
+ const signResults = await this.executeActions([signAction], ui);
5806
+ const allResults = [...results, ...signResults];
5807
+ for (const result of allResults) {
5808
+ await this.processMessageLoop(null, [result], ui);
5809
+ }
5810
+ return;
5811
+ }
5812
+ if (results.length > 0) {
5813
+ for (const result of results) {
5814
+ await this.processMessageLoop(null, [result], ui);
5815
+ }
5816
+ return;
5817
+ }
5818
+ }
5819
+ if (streamResult.transactions.length > 0 && this.executor.hasPendingTransaction()) {
5820
+ if (this.config.verbose) process.stderr.write(`[session] ${streamResult.transactions.length} tx_ready events, signing client-side
5821
+ `);
5822
+ const signAction = {
5823
+ id: `tx_sign_${Date.now()}`,
5824
+ type: "sign_tx",
5825
+ title: "Sign transaction",
5826
+ params: {},
5827
+ auto_execute: true
5828
+ };
5829
+ const results = await this.executeActions([signAction], ui);
5830
+ if (results.length > 0) {
5831
+ for (const result of results) {
5832
+ await this.processMessageLoop(null, [result], ui);
5833
+ }
5834
+ return;
5835
+ }
5836
+ } else if (backendSignActions.length > 0 && this.executor.hasPendingTransaction()) {
5837
+ if (this.config.verbose) process.stderr.write(`[session] Backend sent sign_tx action, using it
5838
+ `);
5839
+ const results = await this.executeActions(backendSignActions, ui);
5840
+ if (results.length > 0) {
5841
+ for (const result of results) {
5842
+ await this.processMessageLoop(null, [result], ui);
5843
+ }
5844
+ return;
5845
+ }
5846
+ } else if (backendSignActions.length > 0 && !this.executor.hasPendingTransaction()) {
5847
+ if (this.config.verbose) process.stderr.write(`[session] Backend sent sign_tx but no pending tx, reporting error
5848
+ `);
5849
+ const errorResult = {
5850
+ action: "sign_tx",
5851
+ action_id: backendSignActions[0].id,
5852
+ success: false,
5853
+ error: "No pending transaction. The swap transaction data was not received."
5854
+ };
5855
+ await this.processMessageLoop(null, [errorResult], ui);
5856
+ return;
5857
+ }
5858
+ ui.onDone();
5859
+ }
5860
+ /**
5861
+ * Execute a list of actions, handling password requirements.
5862
+ */
5863
+ async executeActions(actions, ui) {
5864
+ const results = [];
5865
+ for (const action of actions) {
5866
+ if (!this.executor.shouldAutoExecute(action)) {
5867
+ continue;
5868
+ }
5869
+ if (PASSWORD_REQUIRED_ACTIONS.has(action.type)) {
5870
+ if (!this.config.password) {
5871
+ try {
5872
+ const password = await ui.requestPassword();
5873
+ this.executor.setPassword(password);
5874
+ this.config.password = password;
5875
+ } catch {
5876
+ results.push({
5877
+ action: action.type,
5878
+ action_id: action.id,
5879
+ success: false,
5880
+ error: "Password not provided"
5881
+ });
5882
+ continue;
5883
+ }
5884
+ }
5885
+ }
5886
+ ui.onToolCall(action.id, action.type, action.params);
5887
+ const result = await this.executor.executeAction(action);
5888
+ results.push(result);
5889
+ ui.onToolResult(action.id, action.type, result.success, result.data, result.error);
5890
+ if (action.type === "sign_tx" && result.success && result.data) {
5891
+ const txHash = result.data.tx_hash;
5892
+ const chain = result.data.chain;
5893
+ const explorerUrl = result.data.explorer_url;
5894
+ if (txHash) {
5895
+ ui.onTxStatus(txHash, chain, "pending", explorerUrl);
5896
+ }
5897
+ }
5898
+ }
5899
+ return results;
5900
+ }
5901
+ /**
5902
+ * Cancel the current operation.
5903
+ */
5904
+ cancel() {
5905
+ this.abortController?.abort();
5906
+ }
5907
+ /**
5908
+ * Clean up session resources.
5909
+ */
5910
+ dispose() {
5911
+ this.cancel();
5912
+ this.cachedContext = null;
5913
+ this.conversationId = null;
5914
+ }
5915
+ };
5916
+ function parseInlineToolCalls(text) {
5917
+ const actions = [];
5918
+ const invokeRegex = /<invoke\s+name="([^"]+)">([\s\S]*?)<\/invoke>/g;
5919
+ let match;
5920
+ while ((match = invokeRegex.exec(text)) !== null) {
5921
+ const actionType = match[1];
5922
+ const body = match[2];
5923
+ const params = {};
5924
+ const paramRegex = /<parameter\s+name="([^"]+)">([\s\S]*?)<\/parameter>/g;
5925
+ let paramMatch;
5926
+ while ((paramMatch = paramRegex.exec(body)) !== null) {
5927
+ const key = paramMatch[1];
5928
+ const value = paramMatch[2];
5929
+ try {
5930
+ params[key] = JSON.parse(value);
5931
+ } catch {
5932
+ params[key] = value;
5933
+ }
5934
+ }
5935
+ actions.push({
5936
+ id: `inline_${actionType}_${Date.now()}`,
5937
+ type: actionType,
5938
+ title: actionType,
5939
+ params,
5940
+ auto_execute: true
5941
+ });
5942
+ }
5943
+ return actions;
5944
+ }
5945
+ function getTokenCachePath() {
5946
+ const dir = process.env.VULTISIG_CONFIG_DIR ?? join(homedir(), ".vultisig");
5947
+ return join(dir, "agent-tokens.json");
5948
+ }
5949
+ function readTokenStore() {
5950
+ try {
5951
+ const path3 = getTokenCachePath();
5952
+ if (!existsSync(path3)) return {};
5953
+ return JSON.parse(readFileSync(path3, "utf-8"));
5954
+ } catch {
5955
+ return {};
5956
+ }
5957
+ }
5958
+ function writeTokenStore(store) {
5959
+ const path3 = getTokenCachePath();
5960
+ const dir = join(path3, "..");
5961
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
5962
+ writeFileSync(path3, JSON.stringify(store, null, 2), { mode: 384 });
5963
+ }
5964
+ function loadCachedToken(publicKey) {
5965
+ const store = readTokenStore();
5966
+ const entry = store[publicKey];
5967
+ if (!entry) return null;
5968
+ const now = Date.now();
5969
+ const expiresMs = entry.expiresAt * (entry.expiresAt < 1e12 ? 1e3 : 1);
5970
+ if (now >= expiresMs - 6e4) {
5971
+ delete store[publicKey];
5972
+ try {
5973
+ writeTokenStore(store);
5974
+ } catch {
5975
+ }
5976
+ return null;
5977
+ }
5978
+ return entry.token;
5979
+ }
5980
+ function saveCachedToken(publicKey, token, expiresAt) {
5981
+ const store = readTokenStore();
5982
+ store[publicKey] = { token, expiresAt };
5983
+ try {
5984
+ writeTokenStore(store);
5985
+ } catch {
5986
+ }
5987
+ }
5988
+ function clearCachedToken(publicKey) {
5989
+ const store = readTokenStore();
5990
+ delete store[publicKey];
5991
+ try {
5992
+ writeTokenStore(store);
5993
+ } catch {
5994
+ }
5995
+ }
5996
+
5997
+ // src/agent/tui.ts
5998
+ import * as readline2 from "node:readline";
5999
+ import chalk8 from "chalk";
6000
+ var ChatTUI = class {
6001
+ rl;
6002
+ session;
6003
+ isStreaming = false;
6004
+ currentStreamText = "";
6005
+ vaultName;
6006
+ stopped = false;
6007
+ verbose;
6008
+ constructor(session, vaultName, verbose = false) {
6009
+ this.session = session;
6010
+ this.vaultName = vaultName;
6011
+ this.verbose = verbose;
6012
+ this.rl = readline2.createInterface({
6013
+ input: process.stdin,
6014
+ output: process.stdout,
6015
+ prompt: "",
6016
+ terminal: true
6017
+ });
6018
+ }
6019
+ /**
6020
+ * Start the interactive chat loop.
6021
+ */
6022
+ async start() {
6023
+ this.printHeader();
6024
+ this.printHelp();
6025
+ this.showPrompt();
6026
+ this.rl.on("line", async (line) => {
6027
+ const input = line.trim();
6028
+ readline2.moveCursor(process.stdout, 0, -1);
6029
+ readline2.clearLine(process.stdout, 0);
6030
+ if (!input) {
6031
+ this.showPrompt();
6032
+ return;
6033
+ }
6034
+ if (input === "/quit" || input === "/exit" || input === "/q") {
6035
+ this.stop();
6036
+ return;
6037
+ }
6038
+ if (input === "/help" || input === "/h") {
6039
+ this.printHelp();
6040
+ this.showPrompt();
6041
+ return;
6042
+ }
6043
+ if (input === "/clear") {
6044
+ console.clear();
6045
+ this.printHeader();
6046
+ this.showPrompt();
6047
+ return;
6048
+ }
6049
+ this.printUserMessage(input);
6050
+ await this.handleMessage(input);
6051
+ this.showPrompt();
6052
+ });
6053
+ this.rl.on("close", () => {
6054
+ this.stop();
6055
+ });
6056
+ process.on("SIGINT", () => {
6057
+ if (this.isStreaming) {
6058
+ this.session.cancel();
6059
+ this.isStreaming = false;
6060
+ console.log(chalk8.yellow("\n [cancelled]"));
6061
+ this.showPrompt();
6062
+ } else {
6063
+ this.stop();
6064
+ }
6065
+ });
6066
+ await new Promise((resolve) => {
6067
+ const check = setInterval(() => {
6068
+ if (this.stopped) {
6069
+ clearInterval(check);
6070
+ resolve();
6071
+ }
6072
+ }, 100);
6073
+ });
6074
+ }
6075
+ stop() {
6076
+ if (this.stopped) return;
6077
+ this.stopped = true;
6078
+ console.log(chalk8.gray("\n Goodbye!\n"));
6079
+ this.rl.close();
6080
+ this.session.dispose();
6081
+ }
6082
+ /**
6083
+ * Get UI callbacks for the session.
6084
+ */
6085
+ getCallbacks() {
6086
+ return {
6087
+ onTextDelta: (delta) => {
6088
+ if (!this.isStreaming) {
6089
+ this.isStreaming = true;
6090
+ this.currentStreamText = "";
6091
+ const ts = this.timestamp();
6092
+ process.stdout.write(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: `);
6093
+ }
6094
+ this.currentStreamText += delta;
6095
+ },
6096
+ onToolCall: (_id, action, params) => {
6097
+ if (this.isStreaming) {
6098
+ process.stdout.write("\n");
6099
+ this.isStreaming = false;
6100
+ }
6101
+ if (this.verbose) {
6102
+ const paramStr = params ? chalk8.gray(` ${JSON.stringify(params).slice(0, 80)}`) : "";
6103
+ console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)}${paramStr} ${chalk8.gray("...")}`);
6104
+ } else {
6105
+ console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)} ${chalk8.gray("...")}`);
6106
+ }
6107
+ },
6108
+ onToolResult: (_id, action, success2, data, error2) => {
6109
+ if (success2) {
6110
+ if (this.verbose) {
6111
+ const summary = data ? summarizeData(data) : "";
6112
+ console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}${summary ? chalk8.gray(` \u2192 ${summary}`) : ""}`);
6113
+ } else {
6114
+ console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}`);
6115
+ }
6116
+ } else {
6117
+ console.log(` ${chalk8.red("\u2717")} ${chalk8.red(action)}: ${chalk8.red(error2 || "failed")}`);
6118
+ }
6119
+ },
6120
+ onAssistantMessage: (content) => {
6121
+ if (this.isStreaming) {
6122
+ process.stdout.write(renderMarkdown(this.currentStreamText) + "\n");
6123
+ this.isStreaming = false;
6124
+ } else if (content && content !== this.currentStreamText) {
6125
+ const ts = this.timestamp();
6126
+ console.log(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: ${renderMarkdown(content)}`);
6127
+ }
6128
+ this.currentStreamText = "";
6129
+ },
6130
+ onSuggestions: (suggestions) => {
6131
+ if (suggestions.length > 0) {
6132
+ console.log(chalk8.gray(" Suggestions:"));
6133
+ for (const s of suggestions) {
6134
+ console.log(chalk8.gray(` \u2022 ${s.title}`));
6135
+ }
6136
+ }
6137
+ },
6138
+ onTxStatus: (txHash, chain, status, explorerUrl) => {
6139
+ const statusIcon = status === "confirmed" ? chalk8.green("\u2713") : status === "failed" ? chalk8.red("\u2717") : chalk8.yellow("\u23F3");
6140
+ console.log(` ${statusIcon} ${chalk8.bold("TX")} [${chain}]: ${txHash.slice(0, 12)}...${txHash.slice(-8)}`);
6141
+ if (explorerUrl) {
6142
+ console.log(` ${chalk8.blue.underline(explorerUrl)}`);
6143
+ }
6144
+ },
6145
+ onError: (message) => {
6146
+ if (this.isStreaming) {
6147
+ process.stdout.write("\n");
6148
+ this.isStreaming = false;
6149
+ }
6150
+ console.log(` ${chalk8.red("Error")}: ${message}`);
6151
+ },
6152
+ onDone: () => {
6153
+ if (this.isStreaming) {
6154
+ process.stdout.write(renderMarkdown(this.currentStreamText) + "\n");
6155
+ this.isStreaming = false;
6156
+ this.currentStreamText = "";
6157
+ }
6158
+ },
6159
+ requestPassword: async () => {
6160
+ return new Promise((resolve, reject) => {
6161
+ const rl2 = readline2.createInterface({
6162
+ input: process.stdin,
6163
+ output: process.stdout,
6164
+ terminal: true
6165
+ });
6166
+ if (process.stdin.isTTY) {
6167
+ process.stdout.write(chalk8.yellow(" \u{1F510} Enter vault password: "));
6168
+ const wasRaw = process.stdin.isRaw;
6169
+ process.stdin.setRawMode(true);
6170
+ let password = "";
6171
+ const onData = (key) => {
6172
+ const ch = key.toString();
6173
+ if (ch === "\r" || ch === "\n") {
6174
+ process.stdin.setRawMode(wasRaw || false);
6175
+ process.stdin.removeListener("data", onData);
6176
+ process.stdout.write("\n");
6177
+ rl2.close();
6178
+ resolve(password);
6179
+ } else if (ch === "") {
6180
+ process.stdin.setRawMode(wasRaw || false);
6181
+ process.stdin.removeListener("data", onData);
6182
+ rl2.close();
6183
+ reject(new Error("Password input cancelled"));
6184
+ } else if (ch === "\x7F" || ch === "\b") {
6185
+ if (password.length > 0) {
6186
+ password = password.slice(0, -1);
6187
+ process.stdout.write("\b \b");
6188
+ }
6189
+ } else if (ch.charCodeAt(0) >= 32) {
6190
+ password += ch;
6191
+ process.stdout.write("*");
6192
+ }
6193
+ };
6194
+ process.stdin.on("data", onData);
6195
+ } else {
6196
+ rl2.question("Password: ", (answer) => {
6197
+ rl2.close();
6198
+ resolve(answer.trim());
6199
+ });
6200
+ }
6201
+ });
6202
+ },
6203
+ requestConfirmation: async (message) => {
6204
+ return new Promise((resolve) => {
6205
+ this.rl.question(chalk8.yellow(` ${message} (y/N): `), (answer) => {
6206
+ resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
6207
+ });
6208
+ });
6209
+ }
6210
+ };
6211
+ }
6212
+ async handleMessage(content) {
6213
+ const callbacks = this.getCallbacks();
6214
+ this.isStreaming = false;
6215
+ try {
6216
+ await this.session.sendMessage(content, callbacks);
6217
+ } catch (err) {
6218
+ if (err.name === "AbortError") {
6219
+ console.log(chalk8.yellow(" [cancelled]"));
6220
+ } else {
6221
+ console.log(chalk8.red(` Error: ${err.message}`));
6222
+ }
6223
+ }
6224
+ }
6225
+ printHeader() {
6226
+ console.log("");
6227
+ console.log(chalk8.bold.cyan(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
6228
+ console.log(chalk8.bold.cyan(` \u2551`) + chalk8.bold(` Vultisig Agent - ${this.vaultName}`.padEnd(38).slice(0, 38)) + chalk8.bold.cyan(`\u2551`));
6229
+ console.log(chalk8.bold.cyan(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
6230
+ console.log("");
6231
+ }
6232
+ printHelp() {
6233
+ console.log(chalk8.gray(" Commands: /help, /clear, /quit"));
6234
+ console.log(chalk8.gray(" Press Ctrl+C to cancel a response, or to exit"));
6235
+ console.log("");
6236
+ }
6237
+ printUserMessage(content) {
6238
+ const ts = this.timestamp();
6239
+ console.log(`${chalk8.gray(ts)} ${chalk8.green.bold("You")}: ${content}`);
6240
+ }
6241
+ showPrompt() {
6242
+ if (this.stopped) return;
6243
+ const prompt = chalk8.gray(`${this.timestamp()} `) + chalk8.green.bold("You") + ": ";
6244
+ this.rl.setPrompt(prompt);
6245
+ this.rl.prompt();
6246
+ }
6247
+ timestamp() {
6248
+ const now = /* @__PURE__ */ new Date();
6249
+ return `[${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}]`;
6250
+ }
6251
+ };
6252
+ function renderMarkdown(text) {
6253
+ return text.replace(/\*\*(.+?)\*\*/g, (_m, p1) => chalk8.bold(p1)).replace(/__(.+?)__/g, (_m, p1) => chalk8.bold(p1)).replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, (_m, p1) => chalk8.italic(p1)).replace(/(?<!\w)_([^_]+?)_(?!\w)/g, (_m, p1) => chalk8.italic(p1)).replace(/`([^`]+?)`/g, (_m, p1) => chalk8.cyan(p1)).replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, p1, p2) => `${p1} ${chalk8.blue.underline(`(${p2})`)}`);
6254
+ }
6255
+ function summarizeData(data) {
6256
+ if (data.balances && Array.isArray(data.balances)) {
6257
+ const balances = data.balances;
6258
+ if (balances.length === 1) {
6259
+ return `${balances[0].amount} ${balances[0].symbol}`;
6260
+ }
6261
+ return `${balances.length} balances`;
6262
+ }
6263
+ if (data.tx_hash) {
6264
+ return `tx: ${data.tx_hash.slice(0, 12)}...`;
6265
+ }
6266
+ if (data.added) return "added";
6267
+ if (data.removed) return "removed";
6268
+ if (data.message) return data.message;
6269
+ return "";
6270
+ }
6271
+
6272
+ // src/commands/agent.ts
6273
+ async function executeAgent(ctx2, options) {
6274
+ const vault = await ctx2.ensureActiveVault();
6275
+ const config = {
6276
+ backendUrl: options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998",
6277
+ vaultName: vault.name,
6278
+ password: options.password,
6279
+ viaAgent: options.viaAgent,
6280
+ conversationId: options.conversationId,
6281
+ verbose: options.verbose
6282
+ };
6283
+ const session = new AgentSession(vault, config);
6284
+ if (options.viaAgent) {
6285
+ const pipe = new PipeInterface(session);
6286
+ const callbacks = pipe.getCallbacks();
6287
+ try {
6288
+ await session.initialize(callbacks);
6289
+ const addresses = session.getVaultAddresses();
6290
+ await pipe.start(vault.name, addresses);
6291
+ } catch (err) {
6292
+ process.stdout.write(JSON.stringify({ type: "error", message: err.message }) + "\n");
6293
+ process.exit(1);
6294
+ }
6295
+ } else {
6296
+ const tui = new ChatTUI(session, vault.name, config.verbose);
6297
+ const callbacks = tui.getCallbacks();
6298
+ try {
6299
+ await session.initialize(callbacks);
6300
+ await tui.start();
6301
+ } catch (err) {
6302
+ console.error(`Agent error: ${err.message}`);
6303
+ process.exit(1);
6304
+ }
6305
+ }
6306
+ }
6307
+
6308
+ // src/interactive/completer.ts
6309
+ import { Chain as Chain10 } from "@vultisig/sdk";
6310
+ import fs2 from "fs";
6311
+ import path2 from "path";
6312
+ var COMMANDS = [
6313
+ // Vault management
6314
+ "vaults",
6315
+ "vault",
6316
+ "import",
6317
+ "delete",
6318
+ "create-from-seedphrase",
6319
+ "create",
6320
+ "join",
6321
+ "info",
6322
+ "export",
3330
6323
  // Wallet operations
3331
6324
  "balance",
3332
6325
  "bal",
3333
6326
  "send",
6327
+ "tx-status",
3334
6328
  "portfolio",
3335
6329
  "addresses",
3336
6330
  "chains",
@@ -3381,7 +6375,7 @@ function createCompleter(ctx2) {
3381
6375
  return completeChainName(lastPart);
3382
6376
  }
3383
6377
  }
3384
- if (["balance", "bal", "tokens", "send", "swap", "swap-quote"].includes(command) && parts.length === 2) {
6378
+ if (["balance", "bal", "tokens", "send", "swap", "swap-quote", "tx-status"].includes(command) && parts.length === 2) {
3385
6379
  const partial = parts[1] || "";
3386
6380
  return completeChainName(partial);
3387
6381
  }
@@ -3456,7 +6450,7 @@ function completeVaultName(ctx2, partial) {
3456
6450
  return [show, partial];
3457
6451
  }
3458
6452
  function completeChainName(partial) {
3459
- const allChains = Object.values(Chain5);
6453
+ const allChains = Object.values(Chain10);
3460
6454
  const partialLower = partial.toLowerCase();
3461
6455
  const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
3462
6456
  matches.sort();
@@ -3464,14 +6458,14 @@ function completeChainName(partial) {
3464
6458
  return [show, partial];
3465
6459
  }
3466
6460
  function findChainByName(name) {
3467
- const allChains = Object.values(Chain5);
6461
+ const allChains = Object.values(Chain10);
3468
6462
  const nameLower = name.toLowerCase();
3469
6463
  const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
3470
6464
  return found ? found : null;
3471
6465
  }
3472
6466
 
3473
6467
  // src/interactive/event-buffer.ts
3474
- import chalk8 from "chalk";
6468
+ import chalk9 from "chalk";
3475
6469
  var EventBuffer = class {
3476
6470
  eventBuffer = [];
3477
6471
  isCommandRunning = false;
@@ -3511,17 +6505,17 @@ var EventBuffer = class {
3511
6505
  displayEvent(message, type) {
3512
6506
  switch (type) {
3513
6507
  case "success":
3514
- console.log(chalk8.green(message));
6508
+ console.log(chalk9.green(message));
3515
6509
  break;
3516
6510
  case "warning":
3517
- console.log(chalk8.yellow(message));
6511
+ console.log(chalk9.yellow(message));
3518
6512
  break;
3519
6513
  case "error":
3520
- console.error(chalk8.red(message));
6514
+ console.error(chalk9.red(message));
3521
6515
  break;
3522
6516
  case "info":
3523
6517
  default:
3524
- console.log(chalk8.blue(message));
6518
+ console.log(chalk9.blue(message));
3525
6519
  break;
3526
6520
  }
3527
6521
  }
@@ -3532,13 +6526,13 @@ var EventBuffer = class {
3532
6526
  if (this.eventBuffer.length === 0) {
3533
6527
  return;
3534
6528
  }
3535
- console.log(chalk8.gray("\n--- Background Events ---"));
6529
+ console.log(chalk9.gray("\n--- Background Events ---"));
3536
6530
  this.eventBuffer.forEach((event) => {
3537
6531
  const timeStr = event.timestamp.toLocaleTimeString();
3538
6532
  const message = `[${timeStr}] ${event.message}`;
3539
6533
  this.displayEvent(message, event.type);
3540
6534
  });
3541
- console.log(chalk8.gray("--- End Events ---\n"));
6535
+ console.log(chalk9.gray("--- End Events ---\n"));
3542
6536
  }
3543
6537
  /**
3544
6538
  * Setup all vault event listeners
@@ -3555,6 +6549,12 @@ var EventBuffer = class {
3555
6549
  this.handleEvent(`+ Transaction broadcast on ${chain}`, "success");
3556
6550
  this.handleEvent(` TX Hash: ${txHash}`, "info");
3557
6551
  });
6552
+ vault.on("transactionConfirmed", ({ chain, txHash }) => {
6553
+ this.handleEvent(`+ Transaction confirmed on ${chain}: ${txHash}`, "success");
6554
+ });
6555
+ vault.on("transactionFailed", ({ chain, txHash }) => {
6556
+ this.handleEvent(`x Transaction failed on ${chain}: ${txHash}`, "error");
6557
+ });
3558
6558
  vault.on("signingProgress", ({ step }) => {
3559
6559
  this.handleEvent(`i Signing: ${step}`, "info");
3560
6560
  });
@@ -3618,6 +6618,8 @@ var EventBuffer = class {
3618
6618
  vault.removeAllListeners("balanceUpdated");
3619
6619
  vault.removeAllListeners("transactionSigned");
3620
6620
  vault.removeAllListeners("transactionBroadcast");
6621
+ vault.removeAllListeners("transactionConfirmed");
6622
+ vault.removeAllListeners("transactionFailed");
3621
6623
  vault.removeAllListeners("signingProgress");
3622
6624
  vault.removeAllListeners("chainAdded");
3623
6625
  vault.removeAllListeners("chainRemoved");
@@ -3640,12 +6642,12 @@ var EventBuffer = class {
3640
6642
 
3641
6643
  // src/interactive/session.ts
3642
6644
  import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
3643
- import chalk10 from "chalk";
6645
+ import chalk11 from "chalk";
3644
6646
  import ora3 from "ora";
3645
- import * as readline from "readline";
6647
+ import * as readline3 from "readline";
3646
6648
 
3647
6649
  // src/interactive/shell-commands.ts
3648
- import chalk9 from "chalk";
6650
+ import chalk10 from "chalk";
3649
6651
  import Table from "cli-table3";
3650
6652
  import inquirer6 from "inquirer";
3651
6653
  import ora2 from "ora";
@@ -3662,25 +6664,25 @@ function formatTimeRemaining(ms) {
3662
6664
  async function executeLock(ctx2) {
3663
6665
  const vault = ctx2.getActiveVault();
3664
6666
  if (!vault) {
3665
- console.log(chalk9.red("No active vault."));
3666
- console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
6667
+ console.log(chalk10.red("No active vault."));
6668
+ console.log(chalk10.yellow('Use "vault <name>" to switch to a vault first.'));
3667
6669
  return;
3668
6670
  }
3669
6671
  ctx2.lockVault(vault.id);
3670
- console.log(chalk9.green("\n+ Vault locked"));
3671
- console.log(chalk9.gray("Password cache cleared. You will need to enter the password again."));
6672
+ console.log(chalk10.green("\n+ Vault locked"));
6673
+ console.log(chalk10.gray("Password cache cleared. You will need to enter the password again."));
3672
6674
  }
3673
6675
  async function executeUnlock(ctx2) {
3674
6676
  const vault = ctx2.getActiveVault();
3675
6677
  if (!vault) {
3676
- console.log(chalk9.red("No active vault."));
3677
- console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
6678
+ console.log(chalk10.red("No active vault."));
6679
+ console.log(chalk10.yellow('Use "vault <name>" to switch to a vault first.'));
3678
6680
  return;
3679
6681
  }
3680
6682
  if (ctx2.isVaultUnlocked(vault.id)) {
3681
6683
  const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
3682
- console.log(chalk9.yellow("\nVault is already unlocked."));
3683
- console.log(chalk9.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
6684
+ console.log(chalk10.yellow("\nVault is already unlocked."));
6685
+ console.log(chalk10.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
3684
6686
  return;
3685
6687
  }
3686
6688
  const { password } = await inquirer6.prompt([
@@ -3697,19 +6699,19 @@ async function executeUnlock(ctx2) {
3697
6699
  ctx2.cachePassword(vault.id, password);
3698
6700
  const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
3699
6701
  spinner.succeed("Vault unlocked");
3700
- console.log(chalk9.green(`
6702
+ console.log(chalk10.green(`
3701
6703
  + Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
3702
6704
  } catch (err) {
3703
6705
  spinner.fail("Failed to unlock vault");
3704
- console.error(chalk9.red(`
6706
+ console.error(chalk10.red(`
3705
6707
  x ${err.message}`));
3706
6708
  }
3707
6709
  }
3708
6710
  async function executeStatus(ctx2) {
3709
6711
  const vault = ctx2.getActiveVault();
3710
6712
  if (!vault) {
3711
- console.log(chalk9.red("No active vault."));
3712
- console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
6713
+ console.log(chalk10.red("No active vault."));
6714
+ console.log(chalk10.yellow('Use "vault <name>" to switch to a vault first.'));
3713
6715
  return;
3714
6716
  }
3715
6717
  const isUnlocked = ctx2.isVaultUnlocked(vault.id);
@@ -3740,30 +6742,30 @@ async function executeStatus(ctx2) {
3740
6742
  displayStatus(status);
3741
6743
  }
3742
6744
  function displayStatus(status) {
3743
- console.log(chalk9.cyan("\n+----------------------------------------+"));
3744
- console.log(chalk9.cyan("| Vault Status |"));
3745
- console.log(chalk9.cyan("+----------------------------------------+\n"));
3746
- console.log(chalk9.bold("Vault:"));
3747
- console.log(` Name: ${chalk9.green(status.name)}`);
6745
+ console.log(chalk10.cyan("\n+----------------------------------------+"));
6746
+ console.log(chalk10.cyan("| Vault Status |"));
6747
+ console.log(chalk10.cyan("+----------------------------------------+\n"));
6748
+ console.log(chalk10.bold("Vault:"));
6749
+ console.log(` Name: ${chalk10.green(status.name)}`);
3748
6750
  console.log(` ID: ${status.id}`);
3749
- console.log(` Type: ${chalk9.yellow(status.type)}`);
3750
- console.log(chalk9.bold("\nSecurity:"));
6751
+ console.log(` Type: ${chalk10.yellow(status.type)}`);
6752
+ console.log(chalk10.bold("\nSecurity:"));
3751
6753
  if (status.isUnlocked) {
3752
- console.log(` Status: ${chalk9.green("Unlocked")} ${chalk9.green("\u{1F513}")}`);
6754
+ console.log(` Status: ${chalk10.green("Unlocked")} ${chalk10.green("\u{1F513}")}`);
3753
6755
  console.log(` Expires: ${status.timeRemainingFormatted}`);
3754
6756
  } else {
3755
- console.log(` Status: ${chalk9.yellow("Locked")} ${chalk9.yellow("\u{1F512}")}`);
6757
+ console.log(` Status: ${chalk10.yellow("Locked")} ${chalk10.yellow("\u{1F512}")}`);
3756
6758
  }
3757
- console.log(` Encrypted: ${status.isEncrypted ? chalk9.green("Yes") : chalk9.gray("No")}`);
3758
- console.log(` Backed Up: ${status.isBackedUp ? chalk9.green("Yes") : chalk9.yellow("No")}`);
3759
- console.log(chalk9.bold("\nMPC Configuration:"));
6759
+ console.log(` Encrypted: ${status.isEncrypted ? chalk10.green("Yes") : chalk10.gray("No")}`);
6760
+ console.log(` Backed Up: ${status.isBackedUp ? chalk10.green("Yes") : chalk10.yellow("No")}`);
6761
+ console.log(chalk10.bold("\nMPC Configuration:"));
3760
6762
  console.log(` Library: ${status.libType}`);
3761
- console.log(` Threshold: ${chalk9.cyan(status.threshold)} of ${chalk9.cyan(status.totalSigners)}`);
3762
- console.log(chalk9.bold("\nSigning Modes:"));
6763
+ console.log(` Threshold: ${chalk10.cyan(status.threshold)} of ${chalk10.cyan(status.totalSigners)}`);
6764
+ console.log(chalk10.bold("\nSigning Modes:"));
3763
6765
  status.availableSigningModes.forEach((mode) => {
3764
6766
  console.log(` - ${mode}`);
3765
6767
  });
3766
- console.log(chalk9.bold("\nDetails:"));
6768
+ console.log(chalk10.bold("\nDetails:"));
3767
6769
  console.log(` Chains: ${status.chains}`);
3768
6770
  console.log(` Currency: ${status.currency.toUpperCase()}`);
3769
6771
  console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
@@ -3772,7 +6774,7 @@ function displayStatus(status) {
3772
6774
  }
3773
6775
  function showHelp() {
3774
6776
  const table = new Table({
3775
- head: [chalk9.bold("Available Commands")],
6777
+ head: [chalk10.bold("Available Commands")],
3776
6778
  colWidths: [50],
3777
6779
  chars: {
3778
6780
  mid: "",
@@ -3786,7 +6788,7 @@ function showHelp() {
3786
6788
  }
3787
6789
  });
3788
6790
  table.push(
3789
- [chalk9.bold("Vault Management:")],
6791
+ [chalk10.bold("Vault Management:")],
3790
6792
  [" vaults - List all vaults"],
3791
6793
  [" vault <name> - Switch to vault"],
3792
6794
  [" import <file> - Import vault from file"],
@@ -3795,30 +6797,31 @@ function showHelp() {
3795
6797
  [" info - Show vault details"],
3796
6798
  [" export [path] - Export vault to file"],
3797
6799
  [""],
3798
- [chalk9.bold("Wallet Operations:")],
6800
+ [chalk10.bold("Wallet Operations:")],
3799
6801
  [" balance [chain] - Show balances"],
3800
6802
  [" send <chain> <to> <amount> - Send transaction"],
6803
+ [" tx-status <chain> <txHash> - Check transaction status"],
3801
6804
  [" portfolio [-c usd] - Show portfolio value"],
3802
6805
  [" addresses - Show all addresses"],
3803
6806
  [" chains [--add/--remove/--add-all] - Manage chains"],
3804
6807
  [" tokens <chain> - Manage tokens"],
3805
6808
  [""],
3806
- [chalk9.bold("Swap Operations:")],
6809
+ [chalk10.bold("Swap Operations:")],
3807
6810
  [" swap-chains - List swap-enabled chains"],
3808
6811
  [" swap-quote <from> <to> <amount> - Get quote"],
3809
6812
  [" swap <from> <to> <amount> - Execute swap"],
3810
6813
  [""],
3811
- [chalk9.bold("Session Commands (shell only):")],
6814
+ [chalk10.bold("Session Commands (shell only):")],
3812
6815
  [" lock - Lock vault"],
3813
6816
  [" unlock - Unlock vault"],
3814
6817
  [" status - Show vault status"],
3815
6818
  [""],
3816
- [chalk9.bold("Settings:")],
6819
+ [chalk10.bold("Settings:")],
3817
6820
  [" currency [code] - View/set currency"],
3818
6821
  [" server - Check server status"],
3819
6822
  [" address-book - Manage saved addresses"],
3820
6823
  [""],
3821
- [chalk9.bold("Help & Navigation:")],
6824
+ [chalk10.bold("Help & Navigation:")],
3822
6825
  [" help, ? - Show this help"],
3823
6826
  [" .clear - Clear screen"],
3824
6827
  [" .exit - Exit shell"]
@@ -3956,12 +6959,12 @@ var ShellSession = class {
3956
6959
  */
3957
6960
  async start() {
3958
6961
  console.clear();
3959
- console.log(chalk10.cyan.bold("\n=============================================="));
3960
- console.log(chalk10.cyan.bold(" Vultisig Interactive Shell"));
3961
- console.log(chalk10.cyan.bold("==============================================\n"));
6962
+ console.log(chalk11.cyan.bold("\n=============================================="));
6963
+ console.log(chalk11.cyan.bold(" Vultisig Interactive Shell"));
6964
+ console.log(chalk11.cyan.bold("==============================================\n"));
3962
6965
  await this.loadAllVaults();
3963
6966
  this.displayVaultList();
3964
- console.log(chalk10.gray('Type "help" for available commands, "exit" to quit\n'));
6967
+ console.log(chalk11.gray('Type "help" for available commands, "exit" to quit\n'));
3965
6968
  this.promptLoop().catch(() => {
3966
6969
  });
3967
6970
  }
@@ -3979,7 +6982,7 @@ var ShellSession = class {
3979
6982
  */
3980
6983
  readLine(prompt) {
3981
6984
  return new Promise((resolve) => {
3982
- const rl = readline.createInterface({
6985
+ const rl = readline3.createInterface({
3983
6986
  input: process.stdin,
3984
6987
  output: process.stdout,
3985
6988
  completer: (line, cb) => {
@@ -3995,12 +6998,12 @@ var ShellSession = class {
3995
6998
  const now = Date.now();
3996
6999
  if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
3997
7000
  rl.close();
3998
- console.log(chalk10.yellow("\nGoodbye!"));
7001
+ console.log(chalk11.yellow("\nGoodbye!"));
3999
7002
  this.ctx.dispose();
4000
7003
  process.exit(0);
4001
7004
  }
4002
7005
  this.lastSigintTime = now;
4003
- console.log(chalk10.yellow("\n(Press Ctrl+C again to exit)"));
7006
+ console.log(chalk11.yellow("\n(Press Ctrl+C again to exit)"));
4004
7007
  rl.close();
4005
7008
  resolve("");
4006
7009
  });
@@ -4012,7 +7015,7 @@ var ShellSession = class {
4012
7015
  prompt(message, defaultValue) {
4013
7016
  return new Promise((resolve, reject) => {
4014
7017
  const displayPrompt = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
4015
- const rl = readline.createInterface({
7018
+ const rl = readline3.createInterface({
4016
7019
  input: process.stdin,
4017
7020
  output: process.stdout,
4018
7021
  terminal: true
@@ -4032,7 +7035,7 @@ var ShellSession = class {
4032
7035
  */
4033
7036
  promptPassword(message) {
4034
7037
  return new Promise((resolve, reject) => {
4035
- const rl = readline.createInterface({
7038
+ const rl = readline3.createInterface({
4036
7039
  input: process.stdin,
4037
7040
  output: process.stdout,
4038
7041
  terminal: true
@@ -4095,7 +7098,7 @@ var ShellSession = class {
4095
7098
  stopAllSpinners();
4096
7099
  process.stdout.write("\x1B[?25h");
4097
7100
  process.stdout.write("\r\x1B[K");
4098
- console.log(chalk10.yellow("\nCancelling operation..."));
7101
+ console.log(chalk11.yellow("\nCancelling operation..."));
4099
7102
  };
4100
7103
  const cleanup = () => {
4101
7104
  process.removeListener("SIGINT", onSigint);
@@ -4132,10 +7135,10 @@ var ShellSession = class {
4132
7135
  stopAllSpinners();
4133
7136
  process.stdout.write("\x1B[?25h");
4134
7137
  process.stdout.write("\r\x1B[K");
4135
- console.log(chalk10.yellow("Operation cancelled"));
7138
+ console.log(chalk11.yellow("Operation cancelled"));
4136
7139
  return;
4137
7140
  }
4138
- console.error(chalk10.red(`
7141
+ console.error(chalk11.red(`
4139
7142
  Error: ${error2.message}`));
4140
7143
  }
4141
7144
  }
@@ -4168,7 +7171,7 @@ Error: ${error2.message}`));
4168
7171
  break;
4169
7172
  case "rename":
4170
7173
  if (args.length === 0) {
4171
- console.log(chalk10.yellow("Usage: rename <newName>"));
7174
+ console.log(chalk11.yellow("Usage: rename <newName>"));
4172
7175
  return;
4173
7176
  }
4174
7177
  await executeRename(this.ctx, args.join(" "));
@@ -4188,6 +7191,9 @@ Error: ${error2.message}`));
4188
7191
  case "send":
4189
7192
  await this.runSend(args);
4190
7193
  break;
7194
+ case "tx-status":
7195
+ await this.runTxStatus(args);
7196
+ break;
4191
7197
  // Chain management
4192
7198
  case "addresses":
4193
7199
  await executeAddresses(this.ctx);
@@ -4241,41 +7247,41 @@ Error: ${error2.message}`));
4241
7247
  // Exit
4242
7248
  case "exit":
4243
7249
  case "quit":
4244
- console.log(chalk10.yellow("\nGoodbye!"));
7250
+ console.log(chalk11.yellow("\nGoodbye!"));
4245
7251
  this.ctx.dispose();
4246
7252
  process.exit(0);
4247
7253
  break;
4248
7254
  // eslint requires break even after process.exit
4249
7255
  default:
4250
- console.log(chalk10.yellow(`Unknown command: ${command}`));
4251
- console.log(chalk10.gray('Type "help" for available commands'));
7256
+ console.log(chalk11.yellow(`Unknown command: ${command}`));
7257
+ console.log(chalk11.gray('Type "help" for available commands'));
4252
7258
  break;
4253
7259
  }
4254
7260
  }
4255
7261
  // ===== Command Helpers =====
4256
7262
  async switchVault(args) {
4257
7263
  if (args.length === 0) {
4258
- console.log(chalk10.yellow("Usage: vault <name>"));
4259
- console.log(chalk10.gray('Run "vaults" to see available vaults'));
7264
+ console.log(chalk11.yellow("Usage: vault <name>"));
7265
+ console.log(chalk11.gray('Run "vaults" to see available vaults'));
4260
7266
  return;
4261
7267
  }
4262
7268
  const vaultName = args.join(" ");
4263
7269
  const vault = this.ctx.findVaultByName(vaultName);
4264
7270
  if (!vault) {
4265
- console.log(chalk10.red(`Vault not found: ${vaultName}`));
4266
- console.log(chalk10.gray('Run "vaults" to see available vaults'));
7271
+ console.log(chalk11.red(`Vault not found: ${vaultName}`));
7272
+ console.log(chalk11.gray('Run "vaults" to see available vaults'));
4267
7273
  return;
4268
7274
  }
4269
7275
  await this.ctx.setActiveVault(vault);
4270
- console.log(chalk10.green(`
7276
+ console.log(chalk11.green(`
4271
7277
  + Switched to: ${vault.name}`));
4272
7278
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
4273
- const status = isUnlocked ? chalk10.green("Unlocked") : chalk10.yellow("Locked");
7279
+ const status = isUnlocked ? chalk11.green("Unlocked") : chalk11.yellow("Locked");
4274
7280
  console.log(`Status: ${status}`);
4275
7281
  }
4276
7282
  async importVault(args) {
4277
7283
  if (args.length === 0) {
4278
- console.log(chalk10.yellow("Usage: import <file>"));
7284
+ console.log(chalk11.yellow("Usage: import <file>"));
4279
7285
  return;
4280
7286
  }
4281
7287
  const filePath = args.join(" ");
@@ -4290,45 +7296,45 @@ Error: ${error2.message}`));
4290
7296
  async createVault(args) {
4291
7297
  const type = args[0]?.toLowerCase();
4292
7298
  if (!type || type !== "fast" && type !== "secure") {
4293
- console.log(chalk10.yellow("Usage: create <fast|secure>"));
4294
- console.log(chalk10.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
4295
- console.log(chalk10.gray(" create secure - Create a secure vault (multi-device MPC)"));
7299
+ console.log(chalk11.yellow("Usage: create <fast|secure>"));
7300
+ console.log(chalk11.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
7301
+ console.log(chalk11.gray(" create secure - Create a secure vault (multi-device MPC)"));
4296
7302
  return;
4297
7303
  }
4298
7304
  let vault;
4299
7305
  if (type === "fast") {
4300
7306
  const name = await this.prompt("Vault name");
4301
7307
  if (!name) {
4302
- console.log(chalk10.red("Name is required"));
7308
+ console.log(chalk11.red("Name is required"));
4303
7309
  return;
4304
7310
  }
4305
7311
  const password = await this.promptPassword("Vault password");
4306
7312
  if (!password) {
4307
- console.log(chalk10.red("Password is required"));
7313
+ console.log(chalk11.red("Password is required"));
4308
7314
  return;
4309
7315
  }
4310
7316
  const email = await this.prompt("Email for verification");
4311
7317
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
4312
- console.log(chalk10.red("Valid email is required"));
7318
+ console.log(chalk11.red("Valid email is required"));
4313
7319
  return;
4314
7320
  }
4315
7321
  vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
4316
7322
  } else {
4317
7323
  const name = await this.prompt("Vault name");
4318
7324
  if (!name) {
4319
- console.log(chalk10.red("Name is required"));
7325
+ console.log(chalk11.red("Name is required"));
4320
7326
  return;
4321
7327
  }
4322
7328
  const sharesStr = await this.prompt("Total shares (devices)", "3");
4323
7329
  const shares = parseInt(sharesStr, 10);
4324
7330
  if (isNaN(shares) || shares < 2) {
4325
- console.log(chalk10.red("Must have at least 2 shares"));
7331
+ console.log(chalk11.red("Must have at least 2 shares"));
4326
7332
  return;
4327
7333
  }
4328
7334
  const thresholdStr = await this.prompt("Signing threshold", "2");
4329
7335
  const threshold = parseInt(thresholdStr, 10);
4330
7336
  if (isNaN(threshold) || threshold < 1 || threshold > shares) {
4331
- console.log(chalk10.red(`Threshold must be between 1 and ${shares}`));
7337
+ console.log(chalk11.red(`Threshold must be between 1 and ${shares}`));
4332
7338
  return;
4333
7339
  }
4334
7340
  const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
@@ -4350,37 +7356,37 @@ Error: ${error2.message}`));
4350
7356
  async importSeedphrase(args) {
4351
7357
  const type = args[0]?.toLowerCase();
4352
7358
  if (!type || type !== "fast" && type !== "secure") {
4353
- console.log(chalk10.cyan("Usage: create-from-seedphrase <fast|secure>"));
4354
- console.log(chalk10.gray(" fast - Import with VultiServer (2-of-2)"));
4355
- console.log(chalk10.gray(" secure - Import with device coordination (N-of-M)"));
7359
+ console.log(chalk11.cyan("Usage: create-from-seedphrase <fast|secure>"));
7360
+ console.log(chalk11.gray(" fast - Import with VultiServer (2-of-2)"));
7361
+ console.log(chalk11.gray(" secure - Import with device coordination (N-of-M)"));
4356
7362
  return;
4357
7363
  }
4358
- console.log(chalk10.cyan("\nEnter your recovery phrase (words separated by spaces):"));
7364
+ console.log(chalk11.cyan("\nEnter your recovery phrase (words separated by spaces):"));
4359
7365
  const mnemonic = await this.promptPassword("Seedphrase");
4360
7366
  const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
4361
7367
  if (!validation.valid) {
4362
- console.log(chalk10.red(`Invalid seedphrase: ${validation.error}`));
7368
+ console.log(chalk11.red(`Invalid seedphrase: ${validation.error}`));
4363
7369
  if (validation.invalidWords?.length) {
4364
- console.log(chalk10.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
7370
+ console.log(chalk11.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
4365
7371
  }
4366
7372
  return;
4367
7373
  }
4368
- console.log(chalk10.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
7374
+ console.log(chalk11.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
4369
7375
  let vault;
4370
7376
  if (type === "fast") {
4371
7377
  const name = await this.prompt("Vault name");
4372
7378
  if (!name) {
4373
- console.log(chalk10.red("Name is required"));
7379
+ console.log(chalk11.red("Name is required"));
4374
7380
  return;
4375
7381
  }
4376
7382
  const password = await this.promptPassword("Vault password");
4377
7383
  if (!password) {
4378
- console.log(chalk10.red("Password is required"));
7384
+ console.log(chalk11.red("Password is required"));
4379
7385
  return;
4380
7386
  }
4381
7387
  const email = await this.prompt("Email for verification");
4382
7388
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
4383
- console.log(chalk10.red("Valid email is required"));
7389
+ console.log(chalk11.red("Valid email is required"));
4384
7390
  return;
4385
7391
  }
4386
7392
  const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
@@ -4398,19 +7404,19 @@ Error: ${error2.message}`));
4398
7404
  } else {
4399
7405
  const name = await this.prompt("Vault name");
4400
7406
  if (!name) {
4401
- console.log(chalk10.red("Name is required"));
7407
+ console.log(chalk11.red("Name is required"));
4402
7408
  return;
4403
7409
  }
4404
7410
  const sharesStr = await this.prompt("Total shares (devices)", "3");
4405
7411
  const shares = parseInt(sharesStr, 10);
4406
7412
  if (isNaN(shares) || shares < 2) {
4407
- console.log(chalk10.red("Must have at least 2 shares"));
7413
+ console.log(chalk11.red("Must have at least 2 shares"));
4408
7414
  return;
4409
7415
  }
4410
7416
  const thresholdStr = await this.prompt("Signing threshold", "2");
4411
7417
  const threshold = parseInt(thresholdStr, 10);
4412
7418
  if (isNaN(threshold) || threshold < 1 || threshold > shares) {
4413
- console.log(chalk10.red(`Threshold must be between 1 and ${shares}`));
7419
+ console.log(chalk11.red(`Threshold must be between 1 and ${shares}`));
4414
7420
  return;
4415
7421
  }
4416
7422
  const password = await this.promptPassword("Vault password (optional, Enter to skip)");
@@ -4454,8 +7460,8 @@ Error: ${error2.message}`));
4454
7460
  }
4455
7461
  }
4456
7462
  if (!fiatCurrencies3.includes(currency)) {
4457
- console.log(chalk10.red(`Invalid currency: ${currency}`));
4458
- console.log(chalk10.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
7463
+ console.log(chalk11.red(`Invalid currency: ${currency}`));
7464
+ console.log(chalk11.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
4459
7465
  return;
4460
7466
  }
4461
7467
  const raw = args.includes("--raw");
@@ -4463,7 +7469,7 @@ Error: ${error2.message}`));
4463
7469
  }
4464
7470
  async runSend(args) {
4465
7471
  if (args.length < 3) {
4466
- console.log(chalk10.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
7472
+ console.log(chalk11.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
4467
7473
  return;
4468
7474
  }
4469
7475
  const [chainStr, to, amount, ...rest] = args;
@@ -4483,12 +7489,22 @@ Error: ${error2.message}`));
4483
7489
  await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
4484
7490
  } catch (err) {
4485
7491
  if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
4486
- console.log(chalk10.yellow("\nTransaction cancelled"));
7492
+ console.log(chalk11.yellow("\nTransaction cancelled"));
4487
7493
  return;
4488
7494
  }
4489
7495
  throw err;
4490
7496
  }
4491
7497
  }
7498
+ async runTxStatus(args) {
7499
+ if (args.length < 2) {
7500
+ console.log(chalk11.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
7501
+ return;
7502
+ }
7503
+ const [chainStr, txHash, ...rest] = args;
7504
+ const chain = findChainByName(chainStr) || chainStr;
7505
+ const noWait = rest.includes("--no-wait");
7506
+ await this.withCancellation(() => executeTxStatus(this.ctx, { chain, txHash, noWait }));
7507
+ }
4492
7508
  async runChains(args) {
4493
7509
  let addChain;
4494
7510
  let removeChain;
@@ -4499,8 +7515,8 @@ Error: ${error2.message}`));
4499
7515
  } else if (args[i] === "--add" && i + 1 < args.length) {
4500
7516
  const chain = findChainByName(args[i + 1]);
4501
7517
  if (!chain) {
4502
- console.log(chalk10.red(`Unknown chain: ${args[i + 1]}`));
4503
- console.log(chalk10.gray("Use tab completion to see available chains"));
7518
+ console.log(chalk11.red(`Unknown chain: ${args[i + 1]}`));
7519
+ console.log(chalk11.gray("Use tab completion to see available chains"));
4504
7520
  return;
4505
7521
  }
4506
7522
  addChain = chain;
@@ -4508,8 +7524,8 @@ Error: ${error2.message}`));
4508
7524
  } else if (args[i] === "--remove" && i + 1 < args.length) {
4509
7525
  const chain = findChainByName(args[i + 1]);
4510
7526
  if (!chain) {
4511
- console.log(chalk10.red(`Unknown chain: ${args[i + 1]}`));
4512
- console.log(chalk10.gray("Use tab completion to see available chains"));
7527
+ console.log(chalk11.red(`Unknown chain: ${args[i + 1]}`));
7528
+ console.log(chalk11.gray("Use tab completion to see available chains"));
4513
7529
  return;
4514
7530
  }
4515
7531
  removeChain = chain;
@@ -4520,7 +7536,7 @@ Error: ${error2.message}`));
4520
7536
  }
4521
7537
  async runTokens(args) {
4522
7538
  if (args.length === 0) {
4523
- console.log(chalk10.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
7539
+ console.log(chalk11.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
4524
7540
  return;
4525
7541
  }
4526
7542
  const chainStr = args[0];
@@ -4541,7 +7557,7 @@ Error: ${error2.message}`));
4541
7557
  async runSwapQuote(args) {
4542
7558
  if (args.length < 3) {
4543
7559
  console.log(
4544
- chalk10.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
7560
+ chalk11.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
4545
7561
  );
4546
7562
  return;
4547
7563
  }
@@ -4565,7 +7581,7 @@ Error: ${error2.message}`));
4565
7581
  async runSwap(args) {
4566
7582
  if (args.length < 3) {
4567
7583
  console.log(
4568
- chalk10.yellow(
7584
+ chalk11.yellow(
4569
7585
  "Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
4570
7586
  )
4571
7587
  );
@@ -4596,7 +7612,7 @@ Error: ${error2.message}`));
4596
7612
  );
4597
7613
  } catch (err) {
4598
7614
  if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
4599
- console.log(chalk10.yellow("\nSwap cancelled"));
7615
+ console.log(chalk11.yellow("\nSwap cancelled"));
4600
7616
  return;
4601
7617
  }
4602
7618
  throw err;
@@ -4658,24 +7674,24 @@ Error: ${error2.message}`));
4658
7674
  }
4659
7675
  getPrompt() {
4660
7676
  const vault = this.ctx.getActiveVault();
4661
- if (!vault) return chalk10.cyan("wallet> ");
7677
+ if (!vault) return chalk11.cyan("wallet> ");
4662
7678
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
4663
- const status = isUnlocked ? chalk10.green("\u{1F513}") : chalk10.yellow("\u{1F512}");
4664
- return chalk10.cyan(`wallet[${vault.name}]${status}> `);
7679
+ const status = isUnlocked ? chalk11.green("\u{1F513}") : chalk11.yellow("\u{1F512}");
7680
+ return chalk11.cyan(`wallet[${vault.name}]${status}> `);
4665
7681
  }
4666
7682
  displayVaultList() {
4667
7683
  const vaults = Array.from(this.ctx.getVaults().values());
4668
7684
  const activeVault = this.ctx.getActiveVault();
4669
7685
  if (vaults.length === 0) {
4670
- console.log(chalk10.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
7686
+ console.log(chalk11.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
4671
7687
  return;
4672
7688
  }
4673
- console.log(chalk10.cyan("Loaded Vaults:\n"));
7689
+ console.log(chalk11.cyan("Loaded Vaults:\n"));
4674
7690
  vaults.forEach((vault) => {
4675
7691
  const isActive = vault.id === activeVault?.id;
4676
7692
  const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
4677
- const activeMarker = isActive ? chalk10.green(" (active)") : "";
4678
- const lockIcon = isUnlocked ? chalk10.green("\u{1F513}") : chalk10.yellow("\u{1F512}");
7693
+ const activeMarker = isActive ? chalk11.green(" (active)") : "";
7694
+ const lockIcon = isUnlocked ? chalk11.green("\u{1F513}") : chalk11.yellow("\u{1F512}");
4679
7695
  console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
4680
7696
  });
4681
7697
  console.log();
@@ -4683,23 +7699,23 @@ Error: ${error2.message}`));
4683
7699
  };
4684
7700
 
4685
7701
  // src/lib/errors.ts
4686
- import chalk11 from "chalk";
7702
+ import chalk12 from "chalk";
4687
7703
 
4688
7704
  // src/lib/version.ts
4689
- import chalk12 from "chalk";
4690
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
4691
- import { homedir } from "os";
4692
- import { join } from "path";
7705
+ import chalk13 from "chalk";
7706
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
7707
+ import { homedir as homedir2 } from "os";
7708
+ import { join as join2 } from "path";
4693
7709
  var cachedVersion = null;
4694
7710
  function getVersion() {
4695
7711
  if (cachedVersion) return cachedVersion;
4696
7712
  if (true) {
4697
- cachedVersion = "0.4.0";
7713
+ cachedVersion = "0.7.0";
4698
7714
  return cachedVersion;
4699
7715
  }
4700
7716
  try {
4701
7717
  const packagePath = new URL("../../package.json", import.meta.url);
4702
- const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
7718
+ const pkg = JSON.parse(readFileSync2(packagePath, "utf-8"));
4703
7719
  cachedVersion = pkg.version;
4704
7720
  return cachedVersion;
4705
7721
  } catch {
@@ -4707,13 +7723,13 @@ function getVersion() {
4707
7723
  return cachedVersion;
4708
7724
  }
4709
7725
  }
4710
- var CACHE_DIR = join(homedir(), ".vultisig", "cache");
4711
- var VERSION_CACHE_FILE = join(CACHE_DIR, "version-check.json");
7726
+ var CACHE_DIR = join2(homedir2(), ".vultisig", "cache");
7727
+ var VERSION_CACHE_FILE = join2(CACHE_DIR, "version-check.json");
4712
7728
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4713
7729
  function readVersionCache() {
4714
7730
  try {
4715
- if (!existsSync(VERSION_CACHE_FILE)) return null;
4716
- const data = readFileSync(VERSION_CACHE_FILE, "utf-8");
7731
+ if (!existsSync2(VERSION_CACHE_FILE)) return null;
7732
+ const data = readFileSync2(VERSION_CACHE_FILE, "utf-8");
4717
7733
  return JSON.parse(data);
4718
7734
  } catch {
4719
7735
  return null;
@@ -4721,10 +7737,10 @@ function readVersionCache() {
4721
7737
  }
4722
7738
  function writeVersionCache(cache) {
4723
7739
  try {
4724
- if (!existsSync(CACHE_DIR)) {
4725
- mkdirSync(CACHE_DIR, { recursive: true });
7740
+ if (!existsSync2(CACHE_DIR)) {
7741
+ mkdirSync2(CACHE_DIR, { recursive: true });
4726
7742
  }
4727
- writeFileSync(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
7743
+ writeFileSync2(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
4728
7744
  } catch {
4729
7745
  }
4730
7746
  }
@@ -4748,8 +7764,8 @@ async function fetchLatestVersion() {
4748
7764
  }
4749
7765
  function isNewerVersion(v1, v2) {
4750
7766
  const parse = (v) => {
4751
- const clean = v.replace(/^v/, "").replace(/-.*$/, "");
4752
- return clean.split(".").map((n) => parseInt(n, 10) || 0);
7767
+ const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
7768
+ return clean2.split(".").map((n) => parseInt(n, 10) || 0);
4753
7769
  };
4754
7770
  const p1 = parse(v1);
4755
7771
  const p2 = parse(v2);
@@ -4791,7 +7807,7 @@ function formatVersionShort() {
4791
7807
  }
4792
7808
  function formatVersionDetailed() {
4793
7809
  const lines = [];
4794
- lines.push(chalk12.bold(`Vultisig CLI v${getVersion()}`));
7810
+ lines.push(chalk13.bold(`Vultisig CLI v${getVersion()}`));
4795
7811
  lines.push("");
4796
7812
  lines.push(` Node.js: ${process.version}`);
4797
7813
  lines.push(` Platform: ${process.platform}-${process.arch}`);
@@ -4829,9 +7845,9 @@ function getUpdateCommand() {
4829
7845
  }
4830
7846
 
4831
7847
  // src/lib/completion.ts
4832
- import { homedir as homedir2 } from "os";
4833
- import { join as join2 } from "path";
4834
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
7848
+ import { homedir as homedir3 } from "os";
7849
+ import { join as join3 } from "path";
7850
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
4835
7851
  var tabtab = null;
4836
7852
  async function getTabtab() {
4837
7853
  if (!tabtab) {
@@ -4896,15 +7912,15 @@ var CHAINS = [
4896
7912
  ];
4897
7913
  function getVaultNames() {
4898
7914
  try {
4899
- const vaultDir = join2(homedir2(), ".vultisig", "vaults");
4900
- if (!existsSync2(vaultDir)) return [];
7915
+ const vaultDir = join3(homedir3(), ".vultisig", "vaults");
7916
+ if (!existsSync3(vaultDir)) return [];
4901
7917
  const { readdirSync } = __require("fs");
4902
7918
  const files = readdirSync(vaultDir);
4903
7919
  const names = [];
4904
7920
  for (const file of files) {
4905
7921
  if (file.startsWith("vault:") && file.endsWith(".json")) {
4906
7922
  try {
4907
- const content = readFileSync2(join2(vaultDir, file), "utf-8");
7923
+ const content = readFileSync3(join3(vaultDir, file), "utf-8");
4908
7924
  const vault = JSON.parse(content);
4909
7925
  if (vault.name) names.push(vault.name);
4910
7926
  if (vault.id) names.push(vault.id);
@@ -5142,7 +8158,26 @@ complete -c vsig -n "__fish_seen_subcommand_from import export" -a "(__fish_comp
5142
8158
  `.trim();
5143
8159
  }
5144
8160
 
8161
+ // src/lib/user-agent.ts
8162
+ function setupUserAgent() {
8163
+ const userAgent = `vultisig-cli/${getVersion()}`;
8164
+ const originalFetch = globalThis.fetch;
8165
+ globalThis.fetch = (input, init2) => {
8166
+ const headers = new Headers(input instanceof Request ? input.headers : void 0);
8167
+ if (init2?.headers) {
8168
+ new Headers(init2.headers).forEach((value, key) => {
8169
+ headers.set(key, value);
8170
+ });
8171
+ }
8172
+ if (!headers.has("User-Agent")) {
8173
+ headers.set("User-Agent", userAgent);
8174
+ }
8175
+ return originalFetch(input, { ...init2, headers });
8176
+ };
8177
+ }
8178
+
5145
8179
  // src/index.ts
8180
+ setupUserAgent();
5146
8181
  (async () => {
5147
8182
  const handled = await handleCompletion();
5148
8183
  if (handled) process.exit(0);
@@ -5168,7 +8203,7 @@ async function init(vaultOverride, unlockPassword) {
5168
8203
  if (unlockPassword && vaultSelector) {
5169
8204
  cachePassword(vaultSelector, unlockPassword);
5170
8205
  }
5171
- const sdk = new Vultisig4({
8206
+ const sdk = new Vultisig7({
5172
8207
  onPasswordRequired: createPasswordCallback()
5173
8208
  });
5174
8209
  await sdk.initialize();
@@ -5190,10 +8225,10 @@ async function init(vaultOverride, unlockPassword) {
5190
8225
  return ctx;
5191
8226
  }
5192
8227
  var createCmd = program.command("create").description("Create a vault");
5193
- createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").action(
8228
+ createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--two-step", 'Create vault without verifying OTP (verify later with "vultisig verify")').action(
5194
8229
  withExit(async (options) => {
5195
8230
  const context = await init(program.opts().vault);
5196
- await executeCreateFast(context, options);
8231
+ await executeCreateFast(context, { ...options, twoStep: options.twoStep });
5197
8232
  })
5198
8233
  );
5199
8234
  createCmd.command("secure").description("Create a secure vault (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").action(
@@ -5370,15 +8405,17 @@ program.command("balance [chain]").description("Show balance for a chain or all
5370
8405
  });
5371
8406
  })
5372
8407
  );
5373
- program.command("send <chain> <to> <amount>").description("Send tokens to an address").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
8408
+ program.command("send <chain> <to> [amount]").description("Send tokens to an address").option("--max", "Send maximum amount (balance minus fees)").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
5374
8409
  withExit(
5375
8410
  async (chainStr, to, amount, options) => {
8411
+ if (!amount && !options.max) throw new Error("Provide an amount or use --max");
8412
+ if (amount && options.max) throw new Error("Cannot specify both amount and --max");
5376
8413
  const context = await init(program.opts().vault);
5377
8414
  try {
5378
8415
  await executeSend(context, {
5379
8416
  chain: findChainByName(chainStr) || chainStr,
5380
8417
  to,
5381
- amount,
8418
+ amount: amount ?? "max",
5382
8419
  tokenId: options.token,
5383
8420
  memo: options.memo,
5384
8421
  yes: options.yes,
@@ -5394,6 +8431,30 @@ program.command("send <chain> <to> <amount>").description("Send tokens to an add
5394
8431
  }
5395
8432
  )
5396
8433
  );
8434
+ program.command("execute <chain> <contract> <msg>").description("Execute a CosmWasm smart contract (THORChain, MayaChain)").option("--funds <funds>", 'Funds to send with execution (format: "denom:amount" or "denom:amount,denom2:amount2")').option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
8435
+ withExit(
8436
+ async (chainStr, contract, msg, options) => {
8437
+ const context = await init(program.opts().vault, options.password);
8438
+ try {
8439
+ await executeExecute(context, {
8440
+ chain: findChainByName(chainStr) || chainStr,
8441
+ contract,
8442
+ msg,
8443
+ funds: options.funds,
8444
+ memo: options.memo,
8445
+ yes: options.yes,
8446
+ password: options.password
8447
+ });
8448
+ } catch (err) {
8449
+ if (err.message === "Transaction cancelled by user") {
8450
+ warn("\nx Transaction cancelled");
8451
+ return;
8452
+ }
8453
+ throw err;
8454
+ }
8455
+ }
8456
+ )
8457
+ );
5397
8458
  program.command("sign").description("Sign pre-hashed bytes (for externally constructed transactions)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--bytes <base64>", "Base64-encoded pre-hashed data to sign").option("--password <password>", "Vault password for signing").action(
5398
8459
  withExit(async (options) => {
5399
8460
  const context = await init(program.opts().vault, options.password);
@@ -5413,6 +8474,16 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
5413
8474
  });
5414
8475
  })
5415
8476
  );
8477
+ program.command("tx-status").description("Check the status of a transaction (polls until confirmed)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").action(
8478
+ withExit(async (options) => {
8479
+ const context = await init(program.opts().vault);
8480
+ await executeTxStatus(context, {
8481
+ chain: findChainByName(options.chain) || options.chain,
8482
+ txHash: options.txHash,
8483
+ noWait: !options.wait
8484
+ });
8485
+ })
8486
+ );
5416
8487
  program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
5417
8488
  withExit(async (options) => {
5418
8489
  const context = await init(program.opts().vault);
@@ -5532,29 +8603,33 @@ program.command("swap-chains").description("List chains that support swaps").act
5532
8603
  await executeSwapChains(context);
5533
8604
  })
5534
8605
  );
5535
- program.command("swap-quote <fromChain> <toChain> <amount>").description("Get a swap quote without executing").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").action(
8606
+ program.command("swap-quote <fromChain> <toChain> [amount]").description("Get a swap quote without executing").option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").action(
5536
8607
  withExit(
5537
8608
  async (fromChainStr, toChainStr, amountStr, options) => {
8609
+ if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
8610
+ if (amountStr && options.max) throw new Error("Cannot specify both amount and --max");
5538
8611
  const context = await init(program.opts().vault);
5539
8612
  await executeSwapQuote(context, {
5540
8613
  fromChain: findChainByName(fromChainStr) || fromChainStr,
5541
8614
  toChain: findChainByName(toChainStr) || toChainStr,
5542
- amount: parseFloat(amountStr),
8615
+ amount: options.max ? "max" : parseFloat(amountStr),
5543
8616
  fromToken: options.fromToken,
5544
8617
  toToken: options.toToken
5545
8618
  });
5546
8619
  }
5547
8620
  )
5548
8621
  );
5549
- program.command("swap <fromChain> <toChain> <amount>").description("Swap tokens between chains").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").option("--slippage <percent>", "Slippage tolerance in percent", "1").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
8622
+ program.command("swap <fromChain> <toChain> [amount]").description("Swap tokens between chains").option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").option("--slippage <percent>", "Slippage tolerance in percent", "1").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
5550
8623
  withExit(
5551
8624
  async (fromChainStr, toChainStr, amountStr, options) => {
8625
+ if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
8626
+ if (amountStr && options.max) throw new Error("Cannot specify both amount and --max");
5552
8627
  const context = await init(program.opts().vault);
5553
8628
  try {
5554
8629
  await executeSwap(context, {
5555
8630
  fromChain: findChainByName(fromChainStr) || fromChainStr,
5556
8631
  toChain: findChainByName(toChainStr) || toChainStr,
5557
- amount: parseFloat(amountStr),
8632
+ amount: options.max ? "max" : parseFloat(amountStr),
5558
8633
  fromToken: options.fromToken,
5559
8634
  toToken: options.toToken,
5560
8635
  slippage: options.slippage ? parseFloat(options.slippage) : void 0,
@@ -5571,14 +8646,90 @@ program.command("swap <fromChain> <toChain> <amount>").description("Swap tokens
5571
8646
  }
5572
8647
  )
5573
8648
  );
8649
+ var rujiraCmd = program.command("rujira").description("Rujira FIN swaps + secured asset tools on THORChain");
8650
+ rujiraCmd.command("balance").description("Show secured asset balances on THORChain").option("--secured-only", "Filter to secured/FIN-like denoms only").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
8651
+ withExit(async (options) => {
8652
+ const context = await init(program.opts().vault);
8653
+ await executeRujiraBalance(context, {
8654
+ securedOnly: options.securedOnly,
8655
+ rpcEndpoint: options.rpc,
8656
+ restEndpoint: options.rest
8657
+ });
8658
+ })
8659
+ );
8660
+ rujiraCmd.command("routes").description("List available FIN swap routes").action(
8661
+ withExit(async () => {
8662
+ await executeRujiraRoutes();
8663
+ })
8664
+ );
8665
+ rujiraCmd.command("deposit").description("Show deposit instructions (inbound address + memo)").option("--asset <asset>", "L1 asset to deposit (e.g., BTC.BTC, ETH.ETH)").option("--amount <amount>", "Amount in base units (optional; used for validation)", "1").option("--affiliate <thorAddress>", "Affiliate THOR address (optional)").option("--affiliate-bps <bps>", "Affiliate fee in basis points (optional)", "0").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
8666
+ withExit(
8667
+ async (options) => {
8668
+ const context = await init(program.opts().vault);
8669
+ await executeRujiraDeposit(context, {
8670
+ asset: options.asset,
8671
+ amount: options.amount,
8672
+ affiliate: options.affiliate,
8673
+ affiliateBps: options.affiliateBps ? parseInt(options.affiliateBps, 10) : void 0,
8674
+ rpcEndpoint: options.rpc,
8675
+ restEndpoint: options.rest
8676
+ });
8677
+ }
8678
+ )
8679
+ );
8680
+ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a FIN swap (amount in base units)").option("--slippage-bps <bps>", "Slippage tolerance in basis points (default: 100 = 1%)", "100").option("--destination <thorAddress>", "Destination THOR address (default: vault THORChain address)").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
8681
+ withExit(
8682
+ async (fromAsset, toAsset, amount, options) => {
8683
+ const context = await init(program.opts().vault, options.password);
8684
+ await executeRujiraSwap(context, {
8685
+ fromAsset,
8686
+ toAsset,
8687
+ amount,
8688
+ slippageBps: options.slippageBps ? parseInt(options.slippageBps, 10) : void 0,
8689
+ destination: options.destination,
8690
+ yes: options.yes,
8691
+ password: options.password,
8692
+ rpcEndpoint: options.rpc,
8693
+ restEndpoint: options.rest
8694
+ });
8695
+ }
8696
+ )
8697
+ );
8698
+ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw secured assets to L1 (amount in base units)").option("--max-fee-bps <bps>", "Max outbound fee as bps of amount (optional)").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
8699
+ withExit(
8700
+ async (asset, amount, l1Address, options) => {
8701
+ const context = await init(program.opts().vault, options.password);
8702
+ await executeRujiraWithdraw(context, {
8703
+ asset,
8704
+ amount,
8705
+ l1Address,
8706
+ maxFeeBps: options.maxFeeBps ? parseInt(options.maxFeeBps, 10) : void 0,
8707
+ yes: options.yes,
8708
+ password: options.password,
8709
+ rpcEndpoint: options.rpc,
8710
+ restEndpoint: options.rest
8711
+ });
8712
+ }
8713
+ )
8714
+ );
8715
+ program.command("agent").description("AI-powered chat interface for wallet operations").option("--via-agent", "Use NDJSON pipe mode for agent-to-agent communication").option("--verbose", "Show detailed tool call parameters and debug output").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for signing operations").option("--conversation <id>", "Resume an existing conversation").action(async (options) => {
8716
+ const context = await init(program.opts().vault, options.password);
8717
+ await executeAgent(context, {
8718
+ viaAgent: options.viaAgent,
8719
+ verbose: options.verbose,
8720
+ backendUrl: options.backendUrl,
8721
+ password: options.password,
8722
+ conversationId: options.conversation
8723
+ });
8724
+ });
5574
8725
  program.command("version").description("Show detailed version information").action(
5575
8726
  withExit(async () => {
5576
8727
  printResult(formatVersionDetailed());
5577
8728
  const result = await checkForUpdates();
5578
8729
  if (result?.updateAvailable && result.latestVersion) {
5579
8730
  info("");
5580
- info(chalk13.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
5581
- info(chalk13.gray(`Run "${getUpdateCommand()}" to update`));
8731
+ info(chalk14.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
8732
+ info(chalk14.gray(`Run "${getUpdateCommand()}" to update`));
5582
8733
  }
5583
8734
  })
5584
8735
  );
@@ -5587,28 +8738,28 @@ program.command("update").description("Check for updates and show update command
5587
8738
  info("Checking for updates...");
5588
8739
  const result = await checkForUpdates();
5589
8740
  if (!result) {
5590
- printResult(chalk13.gray("Update checking is disabled"));
8741
+ printResult(chalk14.gray("Update checking is disabled"));
5591
8742
  return;
5592
8743
  }
5593
8744
  if (result.updateAvailable && result.latestVersion) {
5594
8745
  printResult("");
5595
- printResult(chalk13.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
8746
+ printResult(chalk14.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
5596
8747
  printResult("");
5597
8748
  if (options.check) {
5598
8749
  printResult(`Run "${getUpdateCommand()}" to update`);
5599
8750
  } else {
5600
8751
  const updateCmd = getUpdateCommand();
5601
8752
  printResult(`To update, run:`);
5602
- printResult(chalk13.cyan(` ${updateCmd}`));
8753
+ printResult(chalk14.cyan(` ${updateCmd}`));
5603
8754
  }
5604
8755
  } else {
5605
- printResult(chalk13.green(`You're on the latest version (${result.currentVersion})`));
8756
+ printResult(chalk14.green(`You're on the latest version (${result.currentVersion})`));
5606
8757
  }
5607
8758
  })
5608
8759
  );
5609
8760
  setupCompletionCommand(program);
5610
8761
  async function startInteractiveMode() {
5611
- const sdk = new Vultisig4({
8762
+ const sdk = new Vultisig7({
5612
8763
  onPasswordRequired: createPasswordCallback()
5613
8764
  });
5614
8765
  await sdk.initialize();
@@ -5630,3 +8781,8 @@ if (isInteractiveMode) {
5630
8781
  } else {
5631
8782
  program.parse();
5632
8783
  }
8784
+ /*! Bundled license information:
8785
+
8786
+ @noble/hashes/esm/utils.js:
8787
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
8788
+ */