@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.
- package/CHANGELOG.md +61 -0
- package/README.md +39 -0
- package/dist/index.js +3371 -215
- 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
|
|
1046
|
-
|
|
1047
|
-
|
|
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 =
|
|
1052
|
-
var moduleData =
|
|
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(
|
|
1092
|
+
var border = repeat(white).times(qrcode5.getModuleCount() + 3);
|
|
1086
1093
|
output += border + "\n";
|
|
1087
|
-
|
|
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 {
|
|
1107
|
-
import
|
|
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,
|
|
1778
|
+
function displayBalance(chain, balance, _raw = false) {
|
|
1450
1779
|
printResult(chalk2.cyan(`
|
|
1451
1780
|
${chain} Balance:`));
|
|
1452
|
-
|
|
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,
|
|
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:
|
|
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,
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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/
|
|
2384
|
+
// src/commands/execute.ts
|
|
2046
2385
|
var import_qrcode_terminal2 = __toESM(require_main(), 1);
|
|
2047
|
-
import {
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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:
|
|
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
|
|
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(
|
|
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/
|
|
3316
|
-
|
|
3317
|
-
import
|
|
3318
|
-
import
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
"
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
6508
|
+
console.log(chalk9.green(message));
|
|
3515
6509
|
break;
|
|
3516
6510
|
case "warning":
|
|
3517
|
-
console.log(
|
|
6511
|
+
console.log(chalk9.yellow(message));
|
|
3518
6512
|
break;
|
|
3519
6513
|
case "error":
|
|
3520
|
-
console.error(
|
|
6514
|
+
console.error(chalk9.red(message));
|
|
3521
6515
|
break;
|
|
3522
6516
|
case "info":
|
|
3523
6517
|
default:
|
|
3524
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
6645
|
+
import chalk11 from "chalk";
|
|
3644
6646
|
import ora3 from "ora";
|
|
3645
|
-
import * as
|
|
6647
|
+
import * as readline3 from "readline";
|
|
3646
6648
|
|
|
3647
6649
|
// src/interactive/shell-commands.ts
|
|
3648
|
-
import
|
|
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(
|
|
3666
|
-
console.log(
|
|
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(
|
|
3671
|
-
console.log(
|
|
6672
|
+
console.log(chalk10.green("\n+ Vault locked"));
|
|
6673
|
+
console.log(chalk10.gray("Password cache cleared. You will need to enter the password again."));
|
|
3672
6674
|
}
|
|
3673
6675
|
async function executeUnlock(ctx2) {
|
|
3674
6676
|
const vault = ctx2.getActiveVault();
|
|
3675
6677
|
if (!vault) {
|
|
3676
|
-
console.log(
|
|
3677
|
-
console.log(
|
|
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(
|
|
3683
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
3712
|
-
console.log(
|
|
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(
|
|
3744
|
-
console.log(
|
|
3745
|
-
console.log(
|
|
3746
|
-
console.log(
|
|
3747
|
-
console.log(` Name: ${
|
|
6745
|
+
console.log(chalk10.cyan("\n+----------------------------------------+"));
|
|
6746
|
+
console.log(chalk10.cyan("| Vault Status |"));
|
|
6747
|
+
console.log(chalk10.cyan("+----------------------------------------+\n"));
|
|
6748
|
+
console.log(chalk10.bold("Vault:"));
|
|
6749
|
+
console.log(` Name: ${chalk10.green(status.name)}`);
|
|
3748
6750
|
console.log(` ID: ${status.id}`);
|
|
3749
|
-
console.log(` Type: ${
|
|
3750
|
-
console.log(
|
|
6751
|
+
console.log(` Type: ${chalk10.yellow(status.type)}`);
|
|
6752
|
+
console.log(chalk10.bold("\nSecurity:"));
|
|
3751
6753
|
if (status.isUnlocked) {
|
|
3752
|
-
console.log(` Status: ${
|
|
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: ${
|
|
6757
|
+
console.log(` Status: ${chalk10.yellow("Locked")} ${chalk10.yellow("\u{1F512}")}`);
|
|
3756
6758
|
}
|
|
3757
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
3758
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
3759
|
-
console.log(
|
|
6759
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk10.green("Yes") : chalk10.gray("No")}`);
|
|
6760
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk10.green("Yes") : chalk10.yellow("No")}`);
|
|
6761
|
+
console.log(chalk10.bold("\nMPC Configuration:"));
|
|
3760
6762
|
console.log(` Library: ${status.libType}`);
|
|
3761
|
-
console.log(` Threshold: ${
|
|
3762
|
-
console.log(
|
|
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(
|
|
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: [
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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(
|
|
3960
|
-
console.log(
|
|
3961
|
-
console.log(
|
|
6962
|
+
console.log(chalk11.cyan.bold("\n=============================================="));
|
|
6963
|
+
console.log(chalk11.cyan.bold(" Vultisig Interactive Shell"));
|
|
6964
|
+
console.log(chalk11.cyan.bold("==============================================\n"));
|
|
3962
6965
|
await this.loadAllVaults();
|
|
3963
6966
|
this.displayVaultList();
|
|
3964
|
-
console.log(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
7138
|
+
console.log(chalk11.yellow("Operation cancelled"));
|
|
4136
7139
|
return;
|
|
4137
7140
|
}
|
|
4138
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
4251
|
-
console.log(
|
|
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(
|
|
4259
|
-
console.log(
|
|
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(
|
|
4266
|
-
console.log(
|
|
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(
|
|
7276
|
+
console.log(chalk11.green(`
|
|
4271
7277
|
+ Switched to: ${vault.name}`));
|
|
4272
7278
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4273
|
-
const status = isUnlocked ?
|
|
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(
|
|
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(
|
|
4294
|
-
console.log(
|
|
4295
|
-
console.log(
|
|
7299
|
+
console.log(chalk11.yellow("Usage: create <fast|secure>"));
|
|
7300
|
+
console.log(chalk11.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
7301
|
+
console.log(chalk11.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4354
|
-
console.log(
|
|
4355
|
-
console.log(
|
|
7359
|
+
console.log(chalk11.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
7360
|
+
console.log(chalk11.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
7361
|
+
console.log(chalk11.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4356
7362
|
return;
|
|
4357
7363
|
}
|
|
4358
|
-
console.log(
|
|
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(
|
|
7368
|
+
console.log(chalk11.red(`Invalid seedphrase: ${validation.error}`));
|
|
4363
7369
|
if (validation.invalidWords?.length) {
|
|
4364
|
-
console.log(
|
|
7370
|
+
console.log(chalk11.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4365
7371
|
}
|
|
4366
7372
|
return;
|
|
4367
7373
|
}
|
|
4368
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
4458
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
4503
|
-
console.log(
|
|
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(
|
|
4512
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
7677
|
+
if (!vault) return chalk11.cyan("wallet> ");
|
|
4662
7678
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4663
|
-
const status = isUnlocked ?
|
|
4664
|
-
return
|
|
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(
|
|
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(
|
|
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 ?
|
|
4678
|
-
const lockIcon = isUnlocked ?
|
|
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
|
|
7702
|
+
import chalk12 from "chalk";
|
|
4687
7703
|
|
|
4688
7704
|
// src/lib/version.ts
|
|
4689
|
-
import
|
|
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.
|
|
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(
|
|
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 =
|
|
4711
|
-
var VERSION_CACHE_FILE =
|
|
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 (!
|
|
4716
|
-
const data =
|
|
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 (!
|
|
4725
|
-
|
|
7740
|
+
if (!existsSync2(CACHE_DIR)) {
|
|
7741
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
4726
7742
|
}
|
|
4727
|
-
|
|
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
|
|
4752
|
-
return
|
|
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(
|
|
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
|
|
4833
|
-
import { join as
|
|
4834
|
-
import { readFileSync as
|
|
7848
|
+
import { homedir as homedir3 } from "os";
|
|
7849
|
+
import { join as join3 } from "path";
|
|
7850
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
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 =
|
|
4900
|
-
if (!
|
|
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 =
|
|
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
|
|
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>
|
|
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>
|
|
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>
|
|
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(
|
|
5581
|
-
info(
|
|
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(
|
|
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(
|
|
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(
|
|
8753
|
+
printResult(chalk14.cyan(` ${updateCmd}`));
|
|
5603
8754
|
}
|
|
5604
8755
|
} else {
|
|
5605
|
-
printResult(
|
|
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
|
|
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
|
+
*/
|