@vultisig/cli 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/index.js +1208 -348
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1432,9 +1432,9 @@ var init_sha3 = __esm({
|
|
|
1432
1432
|
|
|
1433
1433
|
// src/index.ts
|
|
1434
1434
|
import "dotenv/config";
|
|
1435
|
-
import { promises as
|
|
1435
|
+
import { promises as fs4 } from "node:fs";
|
|
1436
1436
|
import { parseKeygenQR, Vultisig as Vultisig7 } from "@vultisig/sdk";
|
|
1437
|
-
import
|
|
1437
|
+
import chalk15 from "chalk";
|
|
1438
1438
|
import { program } from "commander";
|
|
1439
1439
|
import inquirer8 from "inquirer";
|
|
1440
1440
|
|
|
@@ -1517,6 +1517,9 @@ import chalk from "chalk";
|
|
|
1517
1517
|
import ora from "ora";
|
|
1518
1518
|
var silentMode = false;
|
|
1519
1519
|
var outputFormat = "table";
|
|
1520
|
+
function setSilentMode(silent) {
|
|
1521
|
+
silentMode = silent;
|
|
1522
|
+
}
|
|
1520
1523
|
function isSilent() {
|
|
1521
1524
|
return silentMode;
|
|
1522
1525
|
}
|
|
@@ -1852,6 +1855,11 @@ function displayVaultInfo(vault) {
|
|
|
1852
1855
|
printResult(chalk2.bold("\nPublic Keys:"));
|
|
1853
1856
|
printResult(` ECDSA: ${vault.publicKeys.ecdsa.substring(0, 20)}...`);
|
|
1854
1857
|
printResult(` EdDSA: ${vault.publicKeys.eddsa.substring(0, 20)}...`);
|
|
1858
|
+
if (vault.publicKeyMldsa) {
|
|
1859
|
+
printResult(` ML-DSA-44: ${vault.publicKeyMldsa.substring(0, 20)}...`);
|
|
1860
|
+
} else {
|
|
1861
|
+
printResult(` ML-DSA-44: ${chalk2.gray("Not available")}`);
|
|
1862
|
+
}
|
|
1855
1863
|
printResult(` Chain Code: ${vault.hexChainCode.substring(0, 20)}...
|
|
1856
1864
|
`);
|
|
1857
1865
|
}
|
|
@@ -2480,8 +2488,9 @@ Or use this URL: ${qrPayload}
|
|
|
2480
2488
|
});
|
|
2481
2489
|
}
|
|
2482
2490
|
try {
|
|
2491
|
+
const cosmosChain = params.chain;
|
|
2483
2492
|
const coin = {
|
|
2484
|
-
chain:
|
|
2493
|
+
chain: cosmosChain,
|
|
2485
2494
|
address,
|
|
2486
2495
|
decimals: 8,
|
|
2487
2496
|
// THORChain uses 8 decimals
|
|
@@ -2501,7 +2510,7 @@ Or use this URL: ${qrPayload}
|
|
|
2501
2510
|
gas: chainConfig.gasLimit
|
|
2502
2511
|
};
|
|
2503
2512
|
const keysignPayload = await vault.prepareSignAminoTx({
|
|
2504
|
-
chain:
|
|
2513
|
+
chain: cosmosChain,
|
|
2505
2514
|
coin,
|
|
2506
2515
|
msgs: [executeContractMsg],
|
|
2507
2516
|
fee,
|
|
@@ -2511,7 +2520,7 @@ Or use this URL: ${qrPayload}
|
|
|
2511
2520
|
const signature = await vault.sign(
|
|
2512
2521
|
{
|
|
2513
2522
|
transaction: keysignPayload,
|
|
2514
|
-
chain:
|
|
2523
|
+
chain: cosmosChain,
|
|
2515
2524
|
messageHashes
|
|
2516
2525
|
},
|
|
2517
2526
|
{ signal: params.signal }
|
|
@@ -2519,7 +2528,7 @@ Or use this URL: ${qrPayload}
|
|
|
2519
2528
|
signSpinner.succeed("Transaction signed");
|
|
2520
2529
|
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
2521
2530
|
const txHash = await vault.broadcastTx({
|
|
2522
|
-
chain:
|
|
2531
|
+
chain: cosmosChain,
|
|
2523
2532
|
keysignPayload,
|
|
2524
2533
|
signature
|
|
2525
2534
|
});
|
|
@@ -2603,7 +2612,8 @@ Or use this URL: ${qrPayload}
|
|
|
2603
2612
|
const result = {
|
|
2604
2613
|
signature: sigBase64,
|
|
2605
2614
|
recovery: signature.recovery,
|
|
2606
|
-
format: signature.format
|
|
2615
|
+
format: signature.format,
|
|
2616
|
+
mldsaSignature: signature.mldsaSignature
|
|
2607
2617
|
};
|
|
2608
2618
|
if (isJsonOutput()) {
|
|
2609
2619
|
outputJson(result);
|
|
@@ -2613,6 +2623,9 @@ Or use this URL: ${qrPayload}
|
|
|
2613
2623
|
printResult(`Recovery: ${result.recovery}`);
|
|
2614
2624
|
}
|
|
2615
2625
|
printResult(`Format: ${result.format}`);
|
|
2626
|
+
if (result.mldsaSignature) {
|
|
2627
|
+
printResult(`ML-DSA-44 Signature: ${result.mldsaSignature.substring(0, 40)}...`);
|
|
2628
|
+
}
|
|
2616
2629
|
}
|
|
2617
2630
|
return result;
|
|
2618
2631
|
} finally {
|
|
@@ -2733,7 +2746,11 @@ function withAbortSignal(promise, signal) {
|
|
|
2733
2746
|
]);
|
|
2734
2747
|
}
|
|
2735
2748
|
async function executeCreateFast(ctx2, options) {
|
|
2736
|
-
const
|
|
2749
|
+
const twoStep = options.twoStep || !process.stdin.isTTY;
|
|
2750
|
+
const { name, password, email, signal } = options;
|
|
2751
|
+
if (!options.twoStep && twoStep) {
|
|
2752
|
+
info("Non-interactive terminal detected. Using --two-step mode automatically.");
|
|
2753
|
+
}
|
|
2737
2754
|
const spinner = createSpinner("Creating vault...");
|
|
2738
2755
|
const vaultId = await withAbortSignal(
|
|
2739
2756
|
ctx2.sdk.createFastVault({
|
|
@@ -2749,6 +2766,16 @@ async function executeCreateFast(ctx2, options) {
|
|
|
2749
2766
|
);
|
|
2750
2767
|
spinner.succeed(`Vault keys generated: ${name}`);
|
|
2751
2768
|
if (twoStep) {
|
|
2769
|
+
if (isJsonOutput()) {
|
|
2770
|
+
outputJson({
|
|
2771
|
+
vaultId,
|
|
2772
|
+
status: "pending_verification",
|
|
2773
|
+
message: "Vault created. Verify with email OTP to activate.",
|
|
2774
|
+
verifyCommand: `vultisig verify ${vaultId} --code <OTP>`,
|
|
2775
|
+
resendCommand: `vultisig verify ${vaultId} --resend --email ${email} --password <password>`
|
|
2776
|
+
});
|
|
2777
|
+
return void 0;
|
|
2778
|
+
}
|
|
2752
2779
|
success("\n+ Vault created and saved to disk (pending verification)");
|
|
2753
2780
|
info(`
|
|
2754
2781
|
Vault ID: ${vaultId}`);
|
|
@@ -2897,15 +2924,19 @@ Important: Save your vault backup file (.vult) in a secure location.`);
|
|
|
2897
2924
|
throw err;
|
|
2898
2925
|
}
|
|
2899
2926
|
}
|
|
2900
|
-
async function executeImport(ctx2, file) {
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2927
|
+
async function executeImport(ctx2, file, flagPassword) {
|
|
2928
|
+
let password = flagPassword || process.env.VAULT_PASSWORD || "";
|
|
2929
|
+
if (!password) {
|
|
2930
|
+
const answers = await inquirer4.prompt([
|
|
2931
|
+
{
|
|
2932
|
+
type: "password",
|
|
2933
|
+
name: "password",
|
|
2934
|
+
message: "Enter vault password (if encrypted):",
|
|
2935
|
+
mask: "*"
|
|
2936
|
+
}
|
|
2937
|
+
]);
|
|
2938
|
+
password = answers.password;
|
|
2939
|
+
}
|
|
2909
2940
|
const spinner = createSpinner("Importing vault...");
|
|
2910
2941
|
const vultContent = await fs.readFile(file, "utf-8");
|
|
2911
2942
|
const vault = await ctx2.sdk.importVault(vultContent, password || void 0);
|
|
@@ -2973,11 +3004,25 @@ async function executeVerify(ctx2, vaultId, options = {}) {
|
|
|
2973
3004
|
spinner.succeed("Vault verified successfully!");
|
|
2974
3005
|
setupVaultEvents(vault);
|
|
2975
3006
|
await ctx2.setActiveVault(vault);
|
|
3007
|
+
if (isJsonOutput()) {
|
|
3008
|
+
outputJson({
|
|
3009
|
+
verified: true,
|
|
3010
|
+
vault: { id: vaultId, name: vault.name, type: "fast" }
|
|
3011
|
+
});
|
|
3012
|
+
return true;
|
|
3013
|
+
}
|
|
2976
3014
|
success(`
|
|
2977
3015
|
+ Vault "${vault.name}" is now ready to use!`);
|
|
2978
3016
|
return true;
|
|
2979
3017
|
} catch (err) {
|
|
2980
3018
|
spinner.fail("Verification failed");
|
|
3019
|
+
if (isJsonOutput()) {
|
|
3020
|
+
outputJson({
|
|
3021
|
+
verified: false,
|
|
3022
|
+
error: err.message || "Verification failed. Please check the code and try again."
|
|
3023
|
+
});
|
|
3024
|
+
return false;
|
|
3025
|
+
}
|
|
2981
3026
|
error(`
|
|
2982
3027
|
\u2717 ${err.message || "Verification failed. Please check the code and try again."}`);
|
|
2983
3028
|
warn("\nTip: Use --resend to get a new verification code:");
|
|
@@ -3058,10 +3103,28 @@ async function executeVaults(ctx2) {
|
|
|
3058
3103
|
}
|
|
3059
3104
|
async function executeSwitch(ctx2, vaultId) {
|
|
3060
3105
|
const spinner = createSpinner("Loading vault...");
|
|
3061
|
-
|
|
3106
|
+
let vault = await ctx2.sdk.getVaultById(vaultId);
|
|
3107
|
+
if (!vault) {
|
|
3108
|
+
const allVaults = await ctx2.sdk.listVaults();
|
|
3109
|
+
const byName = allVaults.filter((v) => v.name.toLowerCase() === vaultId.toLowerCase());
|
|
3110
|
+
if (byName.length === 1) {
|
|
3111
|
+
vault = await ctx2.sdk.getVaultById(byName[0].id);
|
|
3112
|
+
} else if (byName.length > 1) {
|
|
3113
|
+
spinner.fail("Ambiguous vault name");
|
|
3114
|
+
throw new Error(`Multiple vaults match name "${vaultId}". Use the full vault ID instead.`);
|
|
3115
|
+
} else {
|
|
3116
|
+
const byPrefix = allVaults.filter((v) => v.id.startsWith(vaultId));
|
|
3117
|
+
if (byPrefix.length === 1) {
|
|
3118
|
+
vault = await ctx2.sdk.getVaultById(byPrefix[0].id);
|
|
3119
|
+
} else if (byPrefix.length > 1) {
|
|
3120
|
+
spinner.fail("Ambiguous vault ID prefix");
|
|
3121
|
+
throw new Error(`Multiple vaults match prefix "${vaultId}". Use a longer prefix or the full ID.`);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3062
3125
|
if (!vault) {
|
|
3063
3126
|
spinner.fail("Vault not found");
|
|
3064
|
-
throw new Error(`No vault found
|
|
3127
|
+
throw new Error(`No vault found matching: ${vaultId}`);
|
|
3065
3128
|
}
|
|
3066
3129
|
await ctx2.setActiveVault(vault);
|
|
3067
3130
|
setupVaultEvents(vault);
|
|
@@ -3103,6 +3166,7 @@ async function executeInfo(ctx2) {
|
|
|
3103
3166
|
publicKeys: {
|
|
3104
3167
|
ecdsa: vault.publicKeys.ecdsa,
|
|
3105
3168
|
eddsa: vault.publicKeys.eddsa,
|
|
3169
|
+
...vault.publicKeyMldsa ? { mldsa: vault.publicKeyMldsa } : {},
|
|
3106
3170
|
chainCode: vault.hexChainCode
|
|
3107
3171
|
}
|
|
3108
3172
|
}
|
|
@@ -4097,6 +4161,93 @@ function displayDiscountTier(tierInfo) {
|
|
|
4097
4161
|
printResult("");
|
|
4098
4162
|
}
|
|
4099
4163
|
|
|
4164
|
+
// src/commands/agent.ts
|
|
4165
|
+
import chalk9 from "chalk";
|
|
4166
|
+
import Table from "cli-table3";
|
|
4167
|
+
|
|
4168
|
+
// src/agent/ask.ts
|
|
4169
|
+
var AskInterface = class {
|
|
4170
|
+
session;
|
|
4171
|
+
verbose;
|
|
4172
|
+
responseParts = [];
|
|
4173
|
+
toolCalls = [];
|
|
4174
|
+
transactions = [];
|
|
4175
|
+
constructor(session, verbose = false) {
|
|
4176
|
+
this.session = session;
|
|
4177
|
+
this.verbose = verbose;
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* Get UI callbacks that silently collect results.
|
|
4181
|
+
* Tool progress is logged to stderr in verbose mode.
|
|
4182
|
+
*/
|
|
4183
|
+
getCallbacks() {
|
|
4184
|
+
return {
|
|
4185
|
+
onTextDelta: (_delta) => {
|
|
4186
|
+
},
|
|
4187
|
+
onToolCall: (_id, action, params) => {
|
|
4188
|
+
if (this.verbose) {
|
|
4189
|
+
const paramStr = params ? ` ${JSON.stringify(params)}` : "";
|
|
4190
|
+
process.stderr.write(`[tool] ${action}${paramStr} ...
|
|
4191
|
+
`);
|
|
4192
|
+
}
|
|
4193
|
+
},
|
|
4194
|
+
onToolResult: (_id, action, success2, data, error2) => {
|
|
4195
|
+
this.toolCalls.push({ action, success: success2, data, error: error2 });
|
|
4196
|
+
if (this.verbose) {
|
|
4197
|
+
const status = success2 ? "ok" : `error: ${error2}`;
|
|
4198
|
+
process.stderr.write(`[tool] ${action}: ${status}
|
|
4199
|
+
`);
|
|
4200
|
+
}
|
|
4201
|
+
},
|
|
4202
|
+
onAssistantMessage: (content) => {
|
|
4203
|
+
if (content) {
|
|
4204
|
+
this.responseParts.push(content);
|
|
4205
|
+
}
|
|
4206
|
+
},
|
|
4207
|
+
onSuggestions: (_suggestions) => {
|
|
4208
|
+
},
|
|
4209
|
+
onTxStatus: (txHash, chain, _status, explorerUrl) => {
|
|
4210
|
+
this.transactions.push({ hash: txHash, chain, explorerUrl });
|
|
4211
|
+
if (this.verbose) {
|
|
4212
|
+
process.stderr.write(`[tx] ${chain}: ${txHash}
|
|
4213
|
+
`);
|
|
4214
|
+
}
|
|
4215
|
+
},
|
|
4216
|
+
onError: (message) => {
|
|
4217
|
+
process.stderr.write(`[error] ${message}
|
|
4218
|
+
`);
|
|
4219
|
+
},
|
|
4220
|
+
onDone: () => {
|
|
4221
|
+
},
|
|
4222
|
+
requestPassword: async () => {
|
|
4223
|
+
throw new Error(
|
|
4224
|
+
"Password required but not provided. Use --password flag."
|
|
4225
|
+
);
|
|
4226
|
+
},
|
|
4227
|
+
requestConfirmation: async (_message) => {
|
|
4228
|
+
return true;
|
|
4229
|
+
}
|
|
4230
|
+
};
|
|
4231
|
+
}
|
|
4232
|
+
/**
|
|
4233
|
+
* Send a message and wait for the complete response.
|
|
4234
|
+
* All tool calls and actions are executed automatically.
|
|
4235
|
+
*/
|
|
4236
|
+
async ask(message) {
|
|
4237
|
+
this.responseParts = [];
|
|
4238
|
+
this.toolCalls = [];
|
|
4239
|
+
this.transactions = [];
|
|
4240
|
+
const callbacks = this.getCallbacks();
|
|
4241
|
+
await this.session.sendMessage(message, callbacks);
|
|
4242
|
+
return {
|
|
4243
|
+
sessionId: this.session.getConversationId() || "",
|
|
4244
|
+
response: this.responseParts[this.responseParts.length - 1] || "",
|
|
4245
|
+
toolCalls: this.toolCalls,
|
|
4246
|
+
transactions: this.transactions
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4250
|
+
|
|
4100
4251
|
// src/agent/auth.ts
|
|
4101
4252
|
init_sha3();
|
|
4102
4253
|
import { randomBytes } from "node:crypto";
|
|
@@ -4372,8 +4523,8 @@ var AgentClient = class {
|
|
|
4372
4523
|
// ============================================================================
|
|
4373
4524
|
// Private helpers
|
|
4374
4525
|
// ============================================================================
|
|
4375
|
-
async post(
|
|
4376
|
-
const res = await fetch(`${this.baseUrl}${
|
|
4526
|
+
async post(path4, body) {
|
|
4527
|
+
const res = await fetch(`${this.baseUrl}${path4}`, {
|
|
4377
4528
|
method: "POST",
|
|
4378
4529
|
headers: {
|
|
4379
4530
|
"Content-Type": "application/json",
|
|
@@ -4387,8 +4538,8 @@ var AgentClient = class {
|
|
|
4387
4538
|
}
|
|
4388
4539
|
return await res.json();
|
|
4389
4540
|
}
|
|
4390
|
-
async delete(
|
|
4391
|
-
const res = await fetch(`${this.baseUrl}${
|
|
4541
|
+
async delete(path4, body) {
|
|
4542
|
+
const res = await fetch(`${this.baseUrl}${path4}`, {
|
|
4392
4543
|
method: "DELETE",
|
|
4393
4544
|
headers: {
|
|
4394
4545
|
"Content-Type": "application/json",
|
|
@@ -4408,7 +4559,8 @@ import { Chain as Chain8 } from "@vultisig/sdk";
|
|
|
4408
4559
|
async function buildMessageContext(vault) {
|
|
4409
4560
|
const context = {
|
|
4410
4561
|
vault_address: vault.publicKeys.ecdsa,
|
|
4411
|
-
vault_name: vault.name
|
|
4562
|
+
vault_name: vault.name,
|
|
4563
|
+
mldsa_public_key: vault.publicKeyMldsa
|
|
4412
4564
|
};
|
|
4413
4565
|
try {
|
|
4414
4566
|
const chains = vault.chains;
|
|
@@ -4468,6 +4620,30 @@ async function buildMessageContext(vault) {
|
|
|
4468
4620
|
}
|
|
4469
4621
|
return context;
|
|
4470
4622
|
}
|
|
4623
|
+
async function buildMinimalContext(vault) {
|
|
4624
|
+
const context = {
|
|
4625
|
+
vault_address: vault.publicKeys.ecdsa,
|
|
4626
|
+
vault_name: vault.name
|
|
4627
|
+
};
|
|
4628
|
+
try {
|
|
4629
|
+
const chains = vault.chains;
|
|
4630
|
+
const addressEntries = await Promise.allSettled(
|
|
4631
|
+
chains.map(async (chain) => ({
|
|
4632
|
+
chain: chain.toString(),
|
|
4633
|
+
address: await vault.address(chain)
|
|
4634
|
+
}))
|
|
4635
|
+
);
|
|
4636
|
+
const addresses = {};
|
|
4637
|
+
for (const result of addressEntries) {
|
|
4638
|
+
if (result.status === "fulfilled") {
|
|
4639
|
+
addresses[result.value.chain] = result.value.address;
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
context.addresses = addresses;
|
|
4643
|
+
} catch {
|
|
4644
|
+
}
|
|
4645
|
+
return context;
|
|
4646
|
+
}
|
|
4471
4647
|
function getNativeTokenTicker(chain) {
|
|
4472
4648
|
const tickers = {
|
|
4473
4649
|
[Chain8.Ethereum]: "ETH",
|
|
@@ -4528,6 +4704,161 @@ function getNativeTokenDecimals(chain) {
|
|
|
4528
4704
|
// src/agent/executor.ts
|
|
4529
4705
|
import { Chain as Chain9, Vultisig as Vultisig6 } from "@vultisig/sdk";
|
|
4530
4706
|
|
|
4707
|
+
// src/core/VaultStateStore.ts
|
|
4708
|
+
import * as fs2 from "node:fs";
|
|
4709
|
+
import * as os from "node:os";
|
|
4710
|
+
import * as path2 from "node:path";
|
|
4711
|
+
var LOCK_STALE_MS = 6e4;
|
|
4712
|
+
var LOCK_RETRY_INIT_MS = 100;
|
|
4713
|
+
var LOCK_MAX_WAIT_MS = 3e4;
|
|
4714
|
+
var STATE_TTL_MS = 10 * 6e4;
|
|
4715
|
+
var VaultStateStore = class {
|
|
4716
|
+
baseDir;
|
|
4717
|
+
constructor(vaultId) {
|
|
4718
|
+
const safeId = vaultId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 40);
|
|
4719
|
+
if (!safeId) {
|
|
4720
|
+
throw new Error("Invalid vaultId: must contain alphanumeric characters");
|
|
4721
|
+
}
|
|
4722
|
+
this.baseDir = path2.join(os.homedir(), ".vultisig", "vault-state", safeId);
|
|
4723
|
+
fs2.mkdirSync(this.baseDir, { recursive: true });
|
|
4724
|
+
}
|
|
4725
|
+
// -------------------------------------------------------------------------
|
|
4726
|
+
// Chain-level locking
|
|
4727
|
+
// -------------------------------------------------------------------------
|
|
4728
|
+
/**
|
|
4729
|
+
* Acquire an exclusive file lock for the given chain.
|
|
4730
|
+
* Blocks (with exponential backoff) until the lock is available or timeout.
|
|
4731
|
+
*
|
|
4732
|
+
* @returns A release function — caller MUST call it when done.
|
|
4733
|
+
*/
|
|
4734
|
+
async acquireChainLock(chain) {
|
|
4735
|
+
const lockPath = path2.join(this.baseDir, `${chain}.lock`);
|
|
4736
|
+
const startTime = Date.now();
|
|
4737
|
+
let delay = LOCK_RETRY_INIT_MS;
|
|
4738
|
+
while (true) {
|
|
4739
|
+
try {
|
|
4740
|
+
const fd = fs2.openSync(lockPath, "wx");
|
|
4741
|
+
const lockToken = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4742
|
+
const info2 = { pid: process.pid, timestamp: Date.now(), token: lockToken };
|
|
4743
|
+
fs2.writeSync(fd, JSON.stringify(info2));
|
|
4744
|
+
fs2.closeSync(fd);
|
|
4745
|
+
return async () => {
|
|
4746
|
+
try {
|
|
4747
|
+
const content = fs2.readFileSync(lockPath, "utf8");
|
|
4748
|
+
const current = JSON.parse(content);
|
|
4749
|
+
if (current.token === lockToken) {
|
|
4750
|
+
fs2.unlinkSync(lockPath);
|
|
4751
|
+
}
|
|
4752
|
+
} catch {
|
|
4753
|
+
}
|
|
4754
|
+
};
|
|
4755
|
+
} catch (err) {
|
|
4756
|
+
if (err.code !== "EEXIST") throw err;
|
|
4757
|
+
if (this.tryCleanStaleLock(lockPath)) {
|
|
4758
|
+
continue;
|
|
4759
|
+
}
|
|
4760
|
+
if (Date.now() - startTime > LOCK_MAX_WAIT_MS) {
|
|
4761
|
+
throw new Error(
|
|
4762
|
+
`Timeout after ${LOCK_MAX_WAIT_MS}ms waiting for ${chain} chain lock. Another process may be stuck. Lock file: ${lockPath}`
|
|
4763
|
+
);
|
|
4764
|
+
}
|
|
4765
|
+
await sleep2(delay);
|
|
4766
|
+
delay = Math.min(delay * 1.5, 2e3);
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
// -------------------------------------------------------------------------
|
|
4771
|
+
// EVM nonce management
|
|
4772
|
+
// -------------------------------------------------------------------------
|
|
4773
|
+
/**
|
|
4774
|
+
* Get the next nonce to use for an EVM chain.
|
|
4775
|
+
*
|
|
4776
|
+
* Takes the on-chain nonce (from `getTransactionCount`) and returns
|
|
4777
|
+
* `max(onChainNonce, localLastUsed + 1)`. This ensures that:
|
|
4778
|
+
* - Locally queued txs get incrementing nonces
|
|
4779
|
+
* - External txs (MetaMask, other wallets) are respected
|
|
4780
|
+
*
|
|
4781
|
+
* MUST be called while holding the chain lock.
|
|
4782
|
+
*/
|
|
4783
|
+
getNextEvmNonce(chain, onChainNonce) {
|
|
4784
|
+
const state = this.readEvmState(chain);
|
|
4785
|
+
if (!state) return onChainNonce;
|
|
4786
|
+
if (Date.now() - state.updatedAt > STATE_TTL_MS) return onChainNonce;
|
|
4787
|
+
const localNext = BigInt(state.lastUsedNonce) + 1n;
|
|
4788
|
+
return localNext > onChainNonce ? localNext : onChainNonce;
|
|
4789
|
+
}
|
|
4790
|
+
/**
|
|
4791
|
+
* Clear persisted nonce state for a chain (e.g. when pending txs were evicted).
|
|
4792
|
+
*/
|
|
4793
|
+
clearEvmState(chain) {
|
|
4794
|
+
const filePath = path2.join(this.baseDir, `${chain}.state.json`);
|
|
4795
|
+
try {
|
|
4796
|
+
fs2.unlinkSync(filePath);
|
|
4797
|
+
} catch {
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
/**
|
|
4801
|
+
* Record that we broadcast a tx using the given nonce.
|
|
4802
|
+
* For approve+swap flows, pass the HIGHEST nonce used.
|
|
4803
|
+
*
|
|
4804
|
+
* MUST be called while holding the chain lock (before releasing).
|
|
4805
|
+
*/
|
|
4806
|
+
recordEvmNonce(chain, nonce) {
|
|
4807
|
+
const state = {
|
|
4808
|
+
lastUsedNonce: nonce.toString(),
|
|
4809
|
+
updatedAt: Date.now()
|
|
4810
|
+
};
|
|
4811
|
+
this.writeEvmState(chain, state);
|
|
4812
|
+
}
|
|
4813
|
+
// -------------------------------------------------------------------------
|
|
4814
|
+
// Internal helpers
|
|
4815
|
+
// -------------------------------------------------------------------------
|
|
4816
|
+
readEvmState(chain) {
|
|
4817
|
+
const filePath = path2.join(this.baseDir, `${chain}.state.json`);
|
|
4818
|
+
try {
|
|
4819
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
4820
|
+
return JSON.parse(content);
|
|
4821
|
+
} catch {
|
|
4822
|
+
return null;
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
writeEvmState(chain, state) {
|
|
4826
|
+
const filePath = path2.join(this.baseDir, `${chain}.state.json`);
|
|
4827
|
+
const tmpPath = filePath + `.tmp.${process.pid}`;
|
|
4828
|
+
fs2.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
4829
|
+
fs2.renameSync(tmpPath, filePath);
|
|
4830
|
+
}
|
|
4831
|
+
/**
|
|
4832
|
+
* Check if a lock file is stale and remove it if so.
|
|
4833
|
+
* @returns true if a stale lock was removed.
|
|
4834
|
+
*/
|
|
4835
|
+
tryCleanStaleLock(lockPath) {
|
|
4836
|
+
try {
|
|
4837
|
+
const content = fs2.readFileSync(lockPath, "utf8");
|
|
4838
|
+
const info2 = JSON.parse(content);
|
|
4839
|
+
if (Date.now() - info2.timestamp > LOCK_STALE_MS) {
|
|
4840
|
+
fs2.unlinkSync(lockPath);
|
|
4841
|
+
return true;
|
|
4842
|
+
}
|
|
4843
|
+
} catch {
|
|
4844
|
+
try {
|
|
4845
|
+
const stat = fs2.statSync(lockPath);
|
|
4846
|
+
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
4847
|
+
fs2.unlinkSync(lockPath);
|
|
4848
|
+
return true;
|
|
4849
|
+
}
|
|
4850
|
+
} catch {
|
|
4851
|
+
return true;
|
|
4852
|
+
}
|
|
4853
|
+
return false;
|
|
4854
|
+
}
|
|
4855
|
+
return false;
|
|
4856
|
+
}
|
|
4857
|
+
};
|
|
4858
|
+
function sleep2(ms) {
|
|
4859
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4531
4862
|
// src/agent/types.ts
|
|
4532
4863
|
var AUTO_EXECUTE_ACTIONS = /* @__PURE__ */ new Set([
|
|
4533
4864
|
"add_chain",
|
|
@@ -4555,14 +4886,50 @@ var AUTO_EXECUTE_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
4555
4886
|
var PASSWORD_REQUIRED_ACTIONS = /* @__PURE__ */ new Set(["sign_tx", "sign_typed_data", "build_custom_tx"]);
|
|
4556
4887
|
|
|
4557
4888
|
// src/agent/executor.ts
|
|
4889
|
+
var EVM_CHAINS = /* @__PURE__ */ new Set([
|
|
4890
|
+
"Ethereum",
|
|
4891
|
+
"BSC",
|
|
4892
|
+
"Polygon",
|
|
4893
|
+
"Avalanche",
|
|
4894
|
+
"Arbitrum",
|
|
4895
|
+
"Optimism",
|
|
4896
|
+
"Base",
|
|
4897
|
+
"Blast",
|
|
4898
|
+
"Zksync",
|
|
4899
|
+
"Mantle",
|
|
4900
|
+
"CronosChain",
|
|
4901
|
+
"Hyperliquid",
|
|
4902
|
+
"Sei"
|
|
4903
|
+
]);
|
|
4904
|
+
var EVM_GAS_RPC = {
|
|
4905
|
+
Ethereum: "https://eth.llamarpc.com",
|
|
4906
|
+
BSC: "https://bsc-dataseed.binance.org",
|
|
4907
|
+
Polygon: "https://polygon-rpc.com",
|
|
4908
|
+
Avalanche: "https://api.avax.network/ext/bc/C/rpc",
|
|
4909
|
+
Arbitrum: "https://arb1.arbitrum.io/rpc",
|
|
4910
|
+
Optimism: "https://mainnet.optimism.io",
|
|
4911
|
+
Base: "https://mainnet.base.org",
|
|
4912
|
+
Blast: "https://rpc.blast.io",
|
|
4913
|
+
Zksync: "https://mainnet.era.zksync.io",
|
|
4914
|
+
Mantle: "https://rpc.mantle.xyz",
|
|
4915
|
+
CronosChain: "https://cronos-evm-rpc.publicnode.com",
|
|
4916
|
+
Hyperliquid: "https://rpc.hyperliquid.xyz/evm",
|
|
4917
|
+
Sei: "https://evm-rpc.sei-apis.com"
|
|
4918
|
+
};
|
|
4558
4919
|
var AgentExecutor = class {
|
|
4559
4920
|
vault;
|
|
4560
4921
|
pendingPayloads = /* @__PURE__ */ new Map();
|
|
4561
4922
|
password = null;
|
|
4562
4923
|
verbose;
|
|
4563
|
-
|
|
4924
|
+
stateStore = null;
|
|
4925
|
+
/** Held chain lock release functions, keyed by chain name */
|
|
4926
|
+
chainLockReleases = /* @__PURE__ */ new Map();
|
|
4927
|
+
constructor(vault, verbose = false, vaultId) {
|
|
4564
4928
|
this.vault = vault;
|
|
4565
4929
|
this.verbose = verbose;
|
|
4930
|
+
if (vaultId) {
|
|
4931
|
+
this.stateStore = new VaultStateStore(vaultId);
|
|
4932
|
+
}
|
|
4566
4933
|
}
|
|
4567
4934
|
setPassword(password) {
|
|
4568
4935
|
this.password = password;
|
|
@@ -4581,6 +4948,7 @@ var AgentExecutor = class {
|
|
|
4581
4948
|
return;
|
|
4582
4949
|
}
|
|
4583
4950
|
const chain = resolveChainFromTxReady(txReadyData) || Chain9.Ethereum;
|
|
4951
|
+
this.pendingPayloads.clear();
|
|
4584
4952
|
this.pendingPayloads.set("latest", {
|
|
4585
4953
|
payload: { __serverTx: true, ...txReadyData },
|
|
4586
4954
|
coin: { chain, address: "", decimals: 18, ticker: "" },
|
|
@@ -4786,39 +5154,47 @@ var AgentExecutor = class {
|
|
|
4786
5154
|
const amountStr = params.amount;
|
|
4787
5155
|
if (!toAddress) throw new Error("Destination address is required");
|
|
4788
5156
|
if (!amountStr) throw new Error("Amount is required");
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
chain,
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
message_hashes: messageHashes,
|
|
4814
|
-
tx_details: {
|
|
4815
|
-
chain: chain.toString(),
|
|
4816
|
-
from: address,
|
|
4817
|
-
to: toAddress,
|
|
5157
|
+
await this.acquireEvmLockIfNeeded(chain);
|
|
5158
|
+
try {
|
|
5159
|
+
const address = await this.vault.address(chain);
|
|
5160
|
+
const balance = await this.vault.balance(chain, params.token_id);
|
|
5161
|
+
const coin = {
|
|
5162
|
+
chain,
|
|
5163
|
+
address,
|
|
5164
|
+
decimals: balance.decimals,
|
|
5165
|
+
ticker: symbol || balance.symbol,
|
|
5166
|
+
id: params.token_id
|
|
5167
|
+
};
|
|
5168
|
+
const amount = parseAmount(amountStr, balance.decimals);
|
|
5169
|
+
const memo = params.memo;
|
|
5170
|
+
const payload = await this.vault.prepareSendTx({ coin, receiver: toAddress, amount, memo });
|
|
5171
|
+
await this.patchEvmNonce(chain, payload);
|
|
5172
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
5173
|
+
this.pendingPayloads.clear();
|
|
5174
|
+
const payloadId = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5175
|
+
this.pendingPayloads.set(payloadId, { payload, coin, chain, timestamp: Date.now() });
|
|
5176
|
+
this.pendingPayloads.set("latest", { payload, coin, chain, timestamp: Date.now() });
|
|
5177
|
+
return {
|
|
5178
|
+
keysign_payload: payloadId,
|
|
5179
|
+
from_chain: chain.toString(),
|
|
5180
|
+
from_symbol: coin.ticker,
|
|
4818
5181
|
amount: amountStr,
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
5182
|
+
sender: address,
|
|
5183
|
+
destination: toAddress,
|
|
5184
|
+
memo: memo || void 0,
|
|
5185
|
+
message_hashes: messageHashes,
|
|
5186
|
+
tx_details: {
|
|
5187
|
+
chain: chain.toString(),
|
|
5188
|
+
from: address,
|
|
5189
|
+
to: toAddress,
|
|
5190
|
+
amount: amountStr,
|
|
5191
|
+
symbol: coin.ticker
|
|
5192
|
+
}
|
|
5193
|
+
};
|
|
5194
|
+
} catch (err) {
|
|
5195
|
+
await this.releaseEvmLock(chain);
|
|
5196
|
+
throw err;
|
|
5197
|
+
}
|
|
4822
5198
|
}
|
|
4823
5199
|
async buildSwapTx(params) {
|
|
4824
5200
|
if (this.verbose) process.stderr.write(`[build_swap_tx] called with params: ${JSON.stringify(params).slice(0, 500)}
|
|
@@ -4828,42 +5204,50 @@ var AgentExecutor = class {
|
|
|
4828
5204
|
const fromChain = resolveChain(fromChainName);
|
|
4829
5205
|
const toChain = toChainName ? resolveChain(toChainName) : null;
|
|
4830
5206
|
if (!fromChain) throw new Error(`Unknown from_chain: ${fromChainName}`);
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
5207
|
+
await this.acquireEvmLockIfNeeded(fromChain);
|
|
5208
|
+
try {
|
|
5209
|
+
const amountStr = params.amount;
|
|
5210
|
+
const fromSymbol = params.from_symbol || params.from_token || "";
|
|
5211
|
+
const toSymbol = params.to_symbol || params.to_token || "";
|
|
5212
|
+
const fromToken = params.from_contract || params.from_token_id;
|
|
5213
|
+
const toToken = params.to_contract || params.to_token_id;
|
|
5214
|
+
const fromCoin = { chain: fromChain, token: fromToken || void 0 };
|
|
5215
|
+
const toCoin = { chain: toChain || fromChain, token: toToken || void 0 };
|
|
5216
|
+
const quote = await this.vault.getSwapQuote({
|
|
5217
|
+
fromCoin,
|
|
5218
|
+
toCoin,
|
|
5219
|
+
amount: parseFloat(amountStr)
|
|
5220
|
+
});
|
|
5221
|
+
const swapResult = await this.vault.prepareSwapTx({
|
|
5222
|
+
fromCoin,
|
|
5223
|
+
toCoin,
|
|
5224
|
+
amount: parseFloat(amountStr),
|
|
5225
|
+
swapQuote: quote,
|
|
5226
|
+
autoApprove: true
|
|
5227
|
+
});
|
|
5228
|
+
const chain = fromChain;
|
|
5229
|
+
const payload = swapResult.keysignPayload;
|
|
5230
|
+
await this.patchEvmNonce(chain, payload);
|
|
5231
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
5232
|
+
this.pendingPayloads.clear();
|
|
5233
|
+
const payloadId = `swap_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5234
|
+
this.pendingPayloads.set(payloadId, { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
|
|
5235
|
+
this.pendingPayloads.set("latest", { payload, coin: { chain, address: "", decimals: 18, ticker: fromSymbol }, chain, timestamp: Date.now() });
|
|
5236
|
+
return {
|
|
5237
|
+
keysign_payload: payloadId,
|
|
5238
|
+
from_chain: fromChain.toString(),
|
|
5239
|
+
to_chain: (toChain || fromChain).toString(),
|
|
5240
|
+
from_symbol: fromSymbol,
|
|
5241
|
+
to_symbol: toSymbol,
|
|
5242
|
+
amount: amountStr,
|
|
5243
|
+
estimated_output: quote.estimatedOutput?.toString(),
|
|
5244
|
+
provider: quote.provider,
|
|
5245
|
+
message_hashes: messageHashes
|
|
5246
|
+
};
|
|
5247
|
+
} catch (err) {
|
|
5248
|
+
await this.releaseEvmLock(fromChain);
|
|
5249
|
+
throw err;
|
|
5250
|
+
}
|
|
4867
5251
|
}
|
|
4868
5252
|
async buildTx(params) {
|
|
4869
5253
|
if (params.function_name && params.contract_address) {
|
|
@@ -4944,6 +5328,18 @@ var AgentExecutor = class {
|
|
|
4944
5328
|
}
|
|
4945
5329
|
const { payload, chain } = stored;
|
|
4946
5330
|
if (payload.__serverTx) {
|
|
5331
|
+
if (chain === "Solana" && (payload.swap_tx || payload.provider)) {
|
|
5332
|
+
try {
|
|
5333
|
+
return await this.buildAndSignSolanaSwapLocally(payload);
|
|
5334
|
+
} catch (e) {
|
|
5335
|
+
if (e._phase === "prepare") {
|
|
5336
|
+
if (this.verbose) process.stderr.write(`[sign_tx] Solana local build failed (${e.message}), falling back to signServerTx
|
|
5337
|
+
`);
|
|
5338
|
+
} else {
|
|
5339
|
+
throw e;
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
4947
5343
|
return this.signServerTx(payload, chain, params);
|
|
4948
5344
|
}
|
|
4949
5345
|
return this.signSdkTx(payload, chain, payloadId);
|
|
@@ -4951,37 +5347,46 @@ var AgentExecutor = class {
|
|
|
4951
5347
|
/**
|
|
4952
5348
|
* Sign and broadcast an SDK-built transaction (keysign payload from local build methods).
|
|
4953
5349
|
*/
|
|
4954
|
-
async signSdkTx(payload, chain,
|
|
4955
|
-
|
|
4956
|
-
if (this.
|
|
4957
|
-
|
|
5350
|
+
async signSdkTx(payload, chain, _payloadId) {
|
|
5351
|
+
try {
|
|
5352
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5353
|
+
if (this.password) {
|
|
5354
|
+
await this.vault.unlock?.(this.password);
|
|
5355
|
+
}
|
|
4958
5356
|
}
|
|
5357
|
+
await this.patchEvmGas(chain, payload);
|
|
5358
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
5359
|
+
const signature = await this.vault.sign(
|
|
5360
|
+
{
|
|
5361
|
+
transaction: payload,
|
|
5362
|
+
chain: payload.coin?.chain || chain,
|
|
5363
|
+
messageHashes
|
|
5364
|
+
},
|
|
5365
|
+
{}
|
|
5366
|
+
);
|
|
5367
|
+
const txHash = await this.vault.broadcastTx({
|
|
5368
|
+
chain,
|
|
5369
|
+
keysignPayload: payload,
|
|
5370
|
+
signature
|
|
5371
|
+
});
|
|
5372
|
+
try {
|
|
5373
|
+
this.recordEvmNonceFromPayload(chain, payload, messageHashes.length);
|
|
5374
|
+
} catch (nonceErr) {
|
|
5375
|
+
console.warn(`[nonce] failed to persist nonce for ${chain}:`, nonceErr);
|
|
5376
|
+
}
|
|
5377
|
+
await this.releaseEvmLock(chain);
|
|
5378
|
+
this.pendingPayloads.clear();
|
|
5379
|
+
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
5380
|
+
return {
|
|
5381
|
+
tx_hash: txHash,
|
|
5382
|
+
chain: chain.toString(),
|
|
5383
|
+
status: "pending",
|
|
5384
|
+
explorer_url: explorerUrl
|
|
5385
|
+
};
|
|
5386
|
+
} catch (err) {
|
|
5387
|
+
await this.releaseEvmLock(chain);
|
|
5388
|
+
throw err;
|
|
4959
5389
|
}
|
|
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
5390
|
}
|
|
4986
5391
|
/**
|
|
4987
5392
|
* Sign and broadcast a server-built transaction (raw EVM tx from tx_ready SSE).
|
|
@@ -5000,38 +5405,121 @@ var AgentExecutor = class {
|
|
|
5000
5405
|
} else if (chainId) {
|
|
5001
5406
|
chain = resolveChainId(chainId) || defaultChain;
|
|
5002
5407
|
}
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
chain
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5408
|
+
await this.acquireEvmLockIfNeeded(chain);
|
|
5409
|
+
try {
|
|
5410
|
+
const address = await this.vault.address(chain);
|
|
5411
|
+
const balance = await this.vault.balance(chain);
|
|
5412
|
+
const coin = {
|
|
5413
|
+
chain,
|
|
5414
|
+
address,
|
|
5415
|
+
decimals: balance.decimals || 18,
|
|
5416
|
+
ticker: balance.symbol || chain.toString()
|
|
5417
|
+
};
|
|
5418
|
+
const amount = BigInt(swapTx.value || "0");
|
|
5419
|
+
const hasCalldata = !!(swapTx.data && swapTx.data !== "0x");
|
|
5420
|
+
if (this.verbose) process.stderr.write(`[sign_server_tx] chain=${chain}, to=${swapTx.to}, value=${swapTx.value}, amount=${amount}, hasCalldata=${hasCalldata}
|
|
5421
|
+
`);
|
|
5422
|
+
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5423
|
+
if (this.password) {
|
|
5424
|
+
await this.vault.unlock?.(this.password);
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
const buildAmount = amount === 0n && hasCalldata ? 1n : amount;
|
|
5428
|
+
const keysignPayload = await this.vault.prepareSendTx({
|
|
5429
|
+
coin,
|
|
5430
|
+
receiver: swapTx.to,
|
|
5431
|
+
amount: buildAmount,
|
|
5432
|
+
memo: swapTx.data
|
|
5433
|
+
});
|
|
5434
|
+
if (amount === 0n && hasCalldata) {
|
|
5435
|
+
;
|
|
5436
|
+
keysignPayload.toAmount = "0";
|
|
5437
|
+
}
|
|
5438
|
+
await this.patchEvmNonce(chain, keysignPayload);
|
|
5439
|
+
await this.patchEvmGas(chain, keysignPayload);
|
|
5440
|
+
const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
|
|
5441
|
+
const signature = await this.vault.sign(
|
|
5442
|
+
{
|
|
5443
|
+
transaction: keysignPayload,
|
|
5444
|
+
chain,
|
|
5445
|
+
messageHashes
|
|
5446
|
+
},
|
|
5447
|
+
{}
|
|
5448
|
+
);
|
|
5449
|
+
const txHash = await this.vault.broadcastTx({
|
|
5450
|
+
chain,
|
|
5451
|
+
keysignPayload,
|
|
5452
|
+
signature
|
|
5453
|
+
});
|
|
5454
|
+
try {
|
|
5455
|
+
this.recordEvmNonceFromPayload(chain, keysignPayload, messageHashes.length);
|
|
5456
|
+
} catch (nonceErr) {
|
|
5457
|
+
console.warn(`[nonce] failed to persist nonce for ${chain}:`, nonceErr);
|
|
5458
|
+
}
|
|
5459
|
+
await this.releaseEvmLock(chain);
|
|
5460
|
+
this.pendingPayloads.clear();
|
|
5461
|
+
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
5462
|
+
return {
|
|
5463
|
+
tx_hash: txHash,
|
|
5464
|
+
chain: chain.toString(),
|
|
5465
|
+
status: "pending",
|
|
5466
|
+
explorer_url: explorerUrl
|
|
5467
|
+
};
|
|
5468
|
+
} catch (err) {
|
|
5469
|
+
await this.releaseEvmLock(chain);
|
|
5470
|
+
throw err;
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
/**
|
|
5474
|
+
* Build, sign, and broadcast a Solana swap locally using the SDK's swap flow.
|
|
5475
|
+
* Uses swap params from the tx_ready event to call vault.getSwapQuote → prepareSwapTx.
|
|
5476
|
+
*/
|
|
5477
|
+
async buildAndSignSolanaSwapLocally(serverTxData) {
|
|
5478
|
+
const fromChainName = serverTxData.from_chain || serverTxData.chain || "Solana";
|
|
5479
|
+
const toChainName = serverTxData.to_chain;
|
|
5480
|
+
const fromChain = resolveChain(fromChainName);
|
|
5481
|
+
if (!fromChain) throw Object.assign(new Error(`Unknown from_chain: ${fromChainName}`), { _phase: "prepare" });
|
|
5482
|
+
const toChain = toChainName ? resolveChain(toChainName) : fromChain;
|
|
5483
|
+
if (!toChain) throw Object.assign(new Error(`Unknown to_chain: ${toChainName}`), { _phase: "prepare" });
|
|
5484
|
+
const amountStr = serverTxData.amount;
|
|
5485
|
+
if (!amountStr) throw Object.assign(new Error("Missing amount in tx_ready data for local Solana swap build"), { _phase: "prepare" });
|
|
5486
|
+
const fromToken = serverTxData.from_address;
|
|
5487
|
+
const toToken = serverTxData.to_address;
|
|
5488
|
+
const fromDecimals = serverTxData.from_decimals;
|
|
5489
|
+
if (fromDecimals == null) throw Object.assign(new Error("Missing from_decimals in tx_ready data for local Solana swap build"), { _phase: "prepare" });
|
|
5490
|
+
const fromCoin = { chain: fromChain, token: fromToken || void 0 };
|
|
5491
|
+
const toCoin = { chain: toChain, token: toToken || void 0 };
|
|
5492
|
+
const humanAmount = Number(amountStr) / Math.pow(10, fromDecimals);
|
|
5493
|
+
if (this.verbose) process.stderr.write(`[solana_local_swap] from=${fromChainName} to=${toChainName || fromChainName} amount=${amountStr}
|
|
5014
5494
|
`);
|
|
5015
5495
|
if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
|
|
5016
5496
|
if (this.password) {
|
|
5017
5497
|
await this.vault.unlock?.(this.password);
|
|
5018
5498
|
}
|
|
5019
5499
|
}
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5500
|
+
let quote, swapResult;
|
|
5501
|
+
try {
|
|
5502
|
+
quote = await this.vault.getSwapQuote({
|
|
5503
|
+
fromCoin,
|
|
5504
|
+
toCoin,
|
|
5505
|
+
amount: humanAmount
|
|
5506
|
+
});
|
|
5507
|
+
swapResult = await this.vault.prepareSwapTx({
|
|
5508
|
+
fromCoin,
|
|
5509
|
+
toCoin,
|
|
5510
|
+
amount: humanAmount,
|
|
5511
|
+
swapQuote: quote,
|
|
5512
|
+
autoApprove: true
|
|
5513
|
+
});
|
|
5514
|
+
} catch (e) {
|
|
5515
|
+
throw Object.assign(e, { _phase: "prepare" });
|
|
5030
5516
|
}
|
|
5031
|
-
const
|
|
5517
|
+
const payload = swapResult.keysignPayload;
|
|
5518
|
+
const chain = fromChain;
|
|
5519
|
+
const messageHashes = await this.vault.extractMessageHashes(payload);
|
|
5032
5520
|
const signature = await this.vault.sign(
|
|
5033
5521
|
{
|
|
5034
|
-
transaction:
|
|
5522
|
+
transaction: payload,
|
|
5035
5523
|
chain,
|
|
5036
5524
|
messageHashes
|
|
5037
5525
|
},
|
|
@@ -5039,10 +5527,10 @@ var AgentExecutor = class {
|
|
|
5039
5527
|
);
|
|
5040
5528
|
const txHash = await this.vault.broadcastTx({
|
|
5041
5529
|
chain,
|
|
5042
|
-
keysignPayload,
|
|
5530
|
+
keysignPayload: payload,
|
|
5043
5531
|
signature
|
|
5044
5532
|
});
|
|
5045
|
-
this.pendingPayloads.
|
|
5533
|
+
this.pendingPayloads.clear();
|
|
5046
5534
|
const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
|
|
5047
5535
|
return {
|
|
5048
5536
|
tx_hash: txHash,
|
|
@@ -5052,6 +5540,147 @@ var AgentExecutor = class {
|
|
|
5052
5540
|
};
|
|
5053
5541
|
}
|
|
5054
5542
|
// ============================================================================
|
|
5543
|
+
// EVM Nonce Management
|
|
5544
|
+
// ============================================================================
|
|
5545
|
+
/**
|
|
5546
|
+
* Acquire chain-level file lock if the chain is EVM.
|
|
5547
|
+
* Releases any previously held lock first (e.g. from an abandoned build).
|
|
5548
|
+
*/
|
|
5549
|
+
async acquireEvmLockIfNeeded(chain) {
|
|
5550
|
+
if (!this.stateStore || !EVM_CHAINS.has(chain)) return;
|
|
5551
|
+
await this.releaseEvmLock(chain);
|
|
5552
|
+
const release = await this.stateStore.acquireChainLock(chain);
|
|
5553
|
+
this.chainLockReleases.set(chain, release);
|
|
5554
|
+
if (this.verbose) process.stderr.write(`[nonce] Acquired lock for ${chain}
|
|
5555
|
+
`);
|
|
5556
|
+
}
|
|
5557
|
+
/**
|
|
5558
|
+
* Release the held chain lock (no-op if not held).
|
|
5559
|
+
*/
|
|
5560
|
+
async releaseEvmLock(chain) {
|
|
5561
|
+
const release = this.chainLockReleases.get(chain);
|
|
5562
|
+
if (release) {
|
|
5563
|
+
await release();
|
|
5564
|
+
this.chainLockReleases.delete(chain);
|
|
5565
|
+
if (this.verbose) process.stderr.write(`[nonce] Released lock for ${chain}
|
|
5566
|
+
`);
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
/**
|
|
5570
|
+
* Patch the EVM nonce in a keysign payload if our local state is ahead of on-chain.
|
|
5571
|
+
* The payload's blockchainSpecific.ethereumSpecific.nonce was set from RPC during
|
|
5572
|
+
* prepareSendTx(). If we have locally-tracked pending txs, we override with a higher value.
|
|
5573
|
+
*
|
|
5574
|
+
* Also detects evicted txs: if local state claims a higher nonce but there are
|
|
5575
|
+
* no pending txs in the mempool (pending == latest), the intermediate txs were
|
|
5576
|
+
* dropped and local state is stale.
|
|
5577
|
+
*/
|
|
5578
|
+
async patchEvmNonce(chain, payload) {
|
|
5579
|
+
if (!this.stateStore || !EVM_CHAINS.has(chain)) return;
|
|
5580
|
+
const bs = payload.blockchainSpecific;
|
|
5581
|
+
if (!bs || bs.case !== "ethereumSpecific") return;
|
|
5582
|
+
const rpcNonce = bs.value.nonce;
|
|
5583
|
+
const nextNonce = this.stateStore.getNextEvmNonce(chain, rpcNonce);
|
|
5584
|
+
if (nextNonce !== rpcNonce) {
|
|
5585
|
+
const pendingNonce = await this.fetchEvmPendingNonce(chain);
|
|
5586
|
+
if (pendingNonce !== null && pendingNonce === rpcNonce) {
|
|
5587
|
+
if (this.verbose) process.stderr.write(`[nonce] Stale local state for ${chain}: local=${nextNonce}, on-chain=${rpcNonce}, no pending txs \u2014 using on-chain nonce
|
|
5588
|
+
`);
|
|
5589
|
+
this.stateStore.clearEvmState(chain);
|
|
5590
|
+
return;
|
|
5591
|
+
}
|
|
5592
|
+
const nonceGap = nextNonce - rpcNonce;
|
|
5593
|
+
if (pendingNonce === null && nonceGap > 3n) {
|
|
5594
|
+
if (this.verbose) process.stderr.write(`[nonce] Large nonce gap for ${chain} (${nonceGap}) and couldn't verify pending txs \u2014 using on-chain nonce ${rpcNonce}
|
|
5595
|
+
`);
|
|
5596
|
+
this.stateStore.clearEvmState(chain);
|
|
5597
|
+
return;
|
|
5598
|
+
}
|
|
5599
|
+
bs.value.nonce = nextNonce;
|
|
5600
|
+
if (this.verbose) process.stderr.write(`[nonce] Patched ${chain} nonce: ${rpcNonce} \u2192 ${nextNonce}
|
|
5601
|
+
`);
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
/**
|
|
5605
|
+
* Ensure the keysign payload's maxFeePerGas covers current network base fee.
|
|
5606
|
+
* Re-fetches latest base fee from RPC and bumps maxFeePerGas if it's too low.
|
|
5607
|
+
* Compensates for gas price drift between build time and sign time.
|
|
5608
|
+
*/
|
|
5609
|
+
async patchEvmGas(chain, payload) {
|
|
5610
|
+
if (!EVM_CHAINS.has(chain)) return;
|
|
5611
|
+
const bs = payload.blockchainSpecific;
|
|
5612
|
+
if (!bs || bs.case !== "ethereumSpecific") return;
|
|
5613
|
+
const rpcUrl = EVM_GAS_RPC[chain];
|
|
5614
|
+
if (!rpcUrl) return;
|
|
5615
|
+
try {
|
|
5616
|
+
const res = await fetch(rpcUrl, {
|
|
5617
|
+
method: "POST",
|
|
5618
|
+
headers: { "Content-Type": "application/json" },
|
|
5619
|
+
body: JSON.stringify({
|
|
5620
|
+
jsonrpc: "2.0",
|
|
5621
|
+
method: "eth_getBlockByNumber",
|
|
5622
|
+
params: ["latest", false],
|
|
5623
|
+
id: 1
|
|
5624
|
+
}),
|
|
5625
|
+
signal: AbortSignal.timeout(5e3)
|
|
5626
|
+
});
|
|
5627
|
+
const data = await res.json();
|
|
5628
|
+
const baseFee = BigInt(data.result?.baseFeePerGas || "0");
|
|
5629
|
+
if (baseFee === 0n) return;
|
|
5630
|
+
const currentPriorityFee = BigInt(bs.value.priorityFee || "0");
|
|
5631
|
+
const currentMaxFee = BigInt(bs.value.maxFeePerGasWei || "0");
|
|
5632
|
+
const minMaxFee = baseFee * 25n / 10n + currentPriorityFee;
|
|
5633
|
+
if (currentMaxFee < minMaxFee) {
|
|
5634
|
+
bs.value.maxFeePerGasWei = minMaxFee.toString();
|
|
5635
|
+
if (this.verbose) process.stderr.write(`[gas] Bumped ${chain} maxFeePerGas: ${currentMaxFee} \u2192 ${minMaxFee} (baseFee=${baseFee})
|
|
5636
|
+
`);
|
|
5637
|
+
}
|
|
5638
|
+
} catch {
|
|
5639
|
+
if (this.verbose) process.stderr.write(`[gas] Failed to refresh base fee for ${chain}, keeping original
|
|
5640
|
+
`);
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
/**
|
|
5644
|
+
* Fetch the pending nonce from RPC (eth_getTransactionCount with "pending" tag).
|
|
5645
|
+
* Returns null if the RPC call fails (non-fatal).
|
|
5646
|
+
*/
|
|
5647
|
+
async fetchEvmPendingNonce(chain) {
|
|
5648
|
+
const rpcUrl = EVM_GAS_RPC[chain];
|
|
5649
|
+
if (!rpcUrl) return null;
|
|
5650
|
+
try {
|
|
5651
|
+
const address = await this.vault.address(chain);
|
|
5652
|
+
const res = await fetch(rpcUrl, {
|
|
5653
|
+
method: "POST",
|
|
5654
|
+
headers: { "Content-Type": "application/json" },
|
|
5655
|
+
body: JSON.stringify({
|
|
5656
|
+
jsonrpc: "2.0",
|
|
5657
|
+
method: "eth_getTransactionCount",
|
|
5658
|
+
params: [address, "pending"],
|
|
5659
|
+
id: 1
|
|
5660
|
+
}),
|
|
5661
|
+
signal: AbortSignal.timeout(5e3)
|
|
5662
|
+
});
|
|
5663
|
+
const data = await res.json();
|
|
5664
|
+
return BigInt(data.result || "0");
|
|
5665
|
+
} catch {
|
|
5666
|
+
return null;
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
/**
|
|
5670
|
+
* Record the nonce(s) used after a successful broadcast.
|
|
5671
|
+
* For approve+swap flows with N message hashes, the highest nonce used is base + N - 1.
|
|
5672
|
+
*/
|
|
5673
|
+
recordEvmNonceFromPayload(chain, payload, numTxs) {
|
|
5674
|
+
if (!this.stateStore || !EVM_CHAINS.has(chain)) return;
|
|
5675
|
+
const bs = payload.blockchainSpecific;
|
|
5676
|
+
if (!bs || bs.case !== "ethereumSpecific") return;
|
|
5677
|
+
const baseNonce = bs.value.nonce;
|
|
5678
|
+
const highestNonce = baseNonce + BigInt(Math.max(0, numTxs - 1));
|
|
5679
|
+
this.stateStore.recordEvmNonce(chain, highestNonce);
|
|
5680
|
+
if (this.verbose) process.stderr.write(`[nonce] Recorded ${chain} nonce: ${highestNonce}
|
|
5681
|
+
`);
|
|
5682
|
+
}
|
|
5683
|
+
// ============================================================================
|
|
5055
5684
|
// EIP-712 Typed Data Signing
|
|
5056
5685
|
// ============================================================================
|
|
5057
5686
|
/**
|
|
@@ -5491,6 +6120,17 @@ var PipeInterface = class {
|
|
|
5491
6120
|
terminal: false
|
|
5492
6121
|
});
|
|
5493
6122
|
this.emit({ type: "ready", vault: vaultName, addresses });
|
|
6123
|
+
const sessionId = this.session.getConversationId();
|
|
6124
|
+
if (sessionId) {
|
|
6125
|
+
this.emit({ type: "session", id: sessionId });
|
|
6126
|
+
}
|
|
6127
|
+
const history = this.session.getHistoryMessages();
|
|
6128
|
+
if (history.length > 0) {
|
|
6129
|
+
this.emit({
|
|
6130
|
+
type: "history",
|
|
6131
|
+
messages: history.filter((m) => m.content_type !== "action_result").map((m) => ({ role: m.role, content: m.content, created_at: m.created_at }))
|
|
6132
|
+
});
|
|
6133
|
+
}
|
|
5494
6134
|
const lines = [];
|
|
5495
6135
|
let inputDone = false;
|
|
5496
6136
|
let processing = false;
|
|
@@ -5621,9 +6261,9 @@ var PipeInterface = class {
|
|
|
5621
6261
|
};
|
|
5622
6262
|
|
|
5623
6263
|
// 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";
|
|
6264
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6265
|
+
import { homedir as homedir2 } from "node:os";
|
|
6266
|
+
import { join as join2 } from "node:path";
|
|
5627
6267
|
var AgentSession = class {
|
|
5628
6268
|
client;
|
|
5629
6269
|
vault;
|
|
@@ -5633,12 +6273,13 @@ var AgentSession = class {
|
|
|
5633
6273
|
publicKey;
|
|
5634
6274
|
cachedContext = null;
|
|
5635
6275
|
abortController = null;
|
|
6276
|
+
historyMessages = [];
|
|
5636
6277
|
constructor(vault, config) {
|
|
5637
6278
|
this.vault = vault;
|
|
5638
6279
|
this.config = config;
|
|
5639
6280
|
this.client = new AgentClient(config.backendUrl);
|
|
5640
6281
|
this.client.verbose = !!config.verbose;
|
|
5641
|
-
this.executor = new AgentExecutor(vault, !!config.verbose);
|
|
6282
|
+
this.executor = new AgentExecutor(vault, !!config.verbose, vault.publicKeys.ecdsa);
|
|
5642
6283
|
this.publicKey = vault.publicKeys.ecdsa;
|
|
5643
6284
|
if (config.password) {
|
|
5644
6285
|
this.executor.setPassword(config.password);
|
|
@@ -5669,17 +6310,38 @@ var AgentSession = class {
|
|
|
5669
6310
|
} catch (err) {
|
|
5670
6311
|
throw new Error(`Authentication failed: ${err.message}`);
|
|
5671
6312
|
}
|
|
5672
|
-
if (this.config.
|
|
5673
|
-
this.conversationId = this.config.
|
|
6313
|
+
if (this.config.sessionId) {
|
|
6314
|
+
this.conversationId = this.config.sessionId;
|
|
6315
|
+
try {
|
|
6316
|
+
const conv = await this.client.getConversation(this.conversationId, this.publicKey);
|
|
6317
|
+
this.historyMessages = conv.messages || [];
|
|
6318
|
+
} catch (err) {
|
|
6319
|
+
if (err.message?.includes("401") || err.message?.includes("403")) {
|
|
6320
|
+
clearCachedToken(this.publicKey);
|
|
6321
|
+
const auth = await authenticateVault(this.client, this.vault, this.config.password);
|
|
6322
|
+
this.client.setAuthToken(auth.token);
|
|
6323
|
+
saveCachedToken(this.publicKey, auth.token, auth.expiresAt);
|
|
6324
|
+
const conv = await this.client.getConversation(this.conversationId, this.publicKey);
|
|
6325
|
+
this.historyMessages = conv.messages || [];
|
|
6326
|
+
} else {
|
|
6327
|
+
this.conversationId = null;
|
|
6328
|
+
this.historyMessages = [];
|
|
6329
|
+
const conv = await this.client.createConversation(this.publicKey);
|
|
6330
|
+
this.conversationId = conv.id;
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
5674
6333
|
} else {
|
|
5675
6334
|
const conv = await this.client.createConversation(this.publicKey);
|
|
5676
6335
|
this.conversationId = conv.id;
|
|
5677
6336
|
}
|
|
5678
|
-
this.cachedContext = await buildMessageContext(this.vault);
|
|
6337
|
+
this.cachedContext = this.config.viaAgent || this.config.askMode ? await buildMinimalContext(this.vault) : await buildMessageContext(this.vault);
|
|
5679
6338
|
}
|
|
5680
6339
|
getConversationId() {
|
|
5681
6340
|
return this.conversationId;
|
|
5682
6341
|
}
|
|
6342
|
+
getHistoryMessages() {
|
|
6343
|
+
return this.historyMessages;
|
|
6344
|
+
}
|
|
5683
6345
|
getVaultAddresses() {
|
|
5684
6346
|
return this.cachedContext?.addresses || {};
|
|
5685
6347
|
}
|
|
@@ -5699,7 +6361,7 @@ var AgentSession = class {
|
|
|
5699
6361
|
}
|
|
5700
6362
|
this.abortController = new AbortController();
|
|
5701
6363
|
try {
|
|
5702
|
-
this.cachedContext = await buildMessageContext(this.vault);
|
|
6364
|
+
this.cachedContext = this.config.viaAgent || this.config.askMode ? await buildMinimalContext(this.vault) : await buildMessageContext(this.vault);
|
|
5703
6365
|
} catch {
|
|
5704
6366
|
}
|
|
5705
6367
|
try {
|
|
@@ -5728,6 +6390,9 @@ var AgentSession = class {
|
|
|
5728
6390
|
public_key: this.publicKey,
|
|
5729
6391
|
context: this.cachedContext
|
|
5730
6392
|
};
|
|
6393
|
+
if (this.config.viaAgent || this.config.askMode) {
|
|
6394
|
+
request.via_agent = true;
|
|
6395
|
+
}
|
|
5731
6396
|
if (content) {
|
|
5732
6397
|
request.content = content;
|
|
5733
6398
|
}
|
|
@@ -5785,15 +6450,14 @@ var AgentSession = class {
|
|
|
5785
6450
|
} else if (responseText) {
|
|
5786
6451
|
ui.onAssistantMessage(responseText);
|
|
5787
6452
|
}
|
|
5788
|
-
const
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
const results = await this.executeActions(nonSignActions, ui);
|
|
6453
|
+
const actions = streamResult.actions.filter((a) => a.type !== "sign_tx");
|
|
6454
|
+
if (actions.length > 0) {
|
|
6455
|
+
const results = await this.executeActions(actions, ui);
|
|
5792
6456
|
const hasBuildSuccess = results.some(
|
|
5793
6457
|
(r) => r.success && r.action.startsWith("build_")
|
|
5794
6458
|
);
|
|
5795
6459
|
if (hasBuildSuccess && this.executor.hasPendingTransaction()) {
|
|
5796
|
-
if (this.config.verbose) process.stderr.write(`[session] build_* action produced pending tx, auto-
|
|
6460
|
+
if (this.config.verbose) process.stderr.write(`[session] build_* action produced pending tx, auto-signing client-side
|
|
5797
6461
|
`);
|
|
5798
6462
|
const signAction = {
|
|
5799
6463
|
id: `tx_sign_${Date.now()}`,
|
|
@@ -5803,11 +6467,11 @@ var AgentSession = class {
|
|
|
5803
6467
|
auto_execute: true
|
|
5804
6468
|
};
|
|
5805
6469
|
const signResults = await this.executeActions([signAction], ui);
|
|
5806
|
-
const
|
|
5807
|
-
|
|
5808
|
-
await this.processMessageLoop(null, [
|
|
6470
|
+
const signResult = signResults[0];
|
|
6471
|
+
if (signResult) {
|
|
6472
|
+
await this.processMessageLoop(null, [signResult], ui);
|
|
6473
|
+
return;
|
|
5809
6474
|
}
|
|
5810
|
-
return;
|
|
5811
6475
|
}
|
|
5812
6476
|
if (results.length > 0) {
|
|
5813
6477
|
for (const result of results) {
|
|
@@ -5833,27 +6497,6 @@ var AgentSession = class {
|
|
|
5833
6497
|
}
|
|
5834
6498
|
return;
|
|
5835
6499
|
}
|
|
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
6500
|
}
|
|
5858
6501
|
ui.onDone();
|
|
5859
6502
|
}
|
|
@@ -5911,6 +6554,7 @@ var AgentSession = class {
|
|
|
5911
6554
|
this.cancel();
|
|
5912
6555
|
this.cachedContext = null;
|
|
5913
6556
|
this.conversationId = null;
|
|
6557
|
+
this.historyMessages = [];
|
|
5914
6558
|
}
|
|
5915
6559
|
};
|
|
5916
6560
|
function parseInlineToolCalls(text) {
|
|
@@ -5943,23 +6587,23 @@ function parseInlineToolCalls(text) {
|
|
|
5943
6587
|
return actions;
|
|
5944
6588
|
}
|
|
5945
6589
|
function getTokenCachePath() {
|
|
5946
|
-
const dir = process.env.VULTISIG_CONFIG_DIR ??
|
|
5947
|
-
return
|
|
6590
|
+
const dir = process.env.VULTISIG_CONFIG_DIR ?? join2(homedir2(), ".vultisig");
|
|
6591
|
+
return join2(dir, "agent-tokens.json");
|
|
5948
6592
|
}
|
|
5949
6593
|
function readTokenStore() {
|
|
5950
6594
|
try {
|
|
5951
|
-
const
|
|
5952
|
-
if (!existsSync(
|
|
5953
|
-
return JSON.parse(
|
|
6595
|
+
const path4 = getTokenCachePath();
|
|
6596
|
+
if (!existsSync(path4)) return {};
|
|
6597
|
+
return JSON.parse(readFileSync2(path4, "utf-8"));
|
|
5954
6598
|
} catch {
|
|
5955
6599
|
return {};
|
|
5956
6600
|
}
|
|
5957
6601
|
}
|
|
5958
6602
|
function writeTokenStore(store) {
|
|
5959
|
-
const
|
|
5960
|
-
const dir =
|
|
5961
|
-
if (!existsSync(dir))
|
|
5962
|
-
|
|
6603
|
+
const path4 = getTokenCachePath();
|
|
6604
|
+
const dir = join2(path4, "..");
|
|
6605
|
+
if (!existsSync(dir)) mkdirSync2(dir, { recursive: true });
|
|
6606
|
+
writeFileSync2(path4, JSON.stringify(store, null, 2), { mode: 384 });
|
|
5963
6607
|
}
|
|
5964
6608
|
function loadCachedToken(publicKey) {
|
|
5965
6609
|
const store = readTokenStore();
|
|
@@ -6021,6 +6665,15 @@ var ChatTUI = class {
|
|
|
6021
6665
|
*/
|
|
6022
6666
|
async start() {
|
|
6023
6667
|
this.printHeader();
|
|
6668
|
+
const sessionId = this.session.getConversationId();
|
|
6669
|
+
if (sessionId) {
|
|
6670
|
+
console.log(chalk8.gray(` Session: ${sessionId}`));
|
|
6671
|
+
console.log("");
|
|
6672
|
+
}
|
|
6673
|
+
const history = this.session.getHistoryMessages();
|
|
6674
|
+
if (history.length > 0) {
|
|
6675
|
+
this.printHistory(history);
|
|
6676
|
+
}
|
|
6024
6677
|
this.printHelp();
|
|
6025
6678
|
this.showPrompt();
|
|
6026
6679
|
this.rl.on("line", async (line) => {
|
|
@@ -6229,6 +6882,30 @@ var ChatTUI = class {
|
|
|
6229
6882
|
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
6883
|
console.log("");
|
|
6231
6884
|
}
|
|
6885
|
+
printHistory(messages) {
|
|
6886
|
+
console.log(chalk8.gray(" \u2500\u2500 Session History \u2500\u2500"));
|
|
6887
|
+
console.log("");
|
|
6888
|
+
for (const msg of messages) {
|
|
6889
|
+
if (msg.content_type === "action_result") continue;
|
|
6890
|
+
const ts = this.formatHistoryTimestamp(msg.created_at);
|
|
6891
|
+
if (msg.role === "user") {
|
|
6892
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.green.bold("You")}: ${msg.content}`);
|
|
6893
|
+
} else if (msg.role === "assistant") {
|
|
6894
|
+
console.log(`${chalk8.gray(ts)} ${chalk8.cyan.bold("Agent")}: ${renderMarkdown(msg.content)}`);
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
console.log("");
|
|
6898
|
+
console.log(chalk8.gray(" \u2500\u2500 End of History \u2500\u2500"));
|
|
6899
|
+
console.log("");
|
|
6900
|
+
}
|
|
6901
|
+
formatHistoryTimestamp(iso) {
|
|
6902
|
+
try {
|
|
6903
|
+
const d = new Date(iso);
|
|
6904
|
+
return `[${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}]`;
|
|
6905
|
+
} catch {
|
|
6906
|
+
return "[--:--:--]";
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6232
6909
|
printHelp() {
|
|
6233
6910
|
console.log(chalk8.gray(" Commands: /help, /clear, /quit"));
|
|
6234
6911
|
console.log(chalk8.gray(" Press Ctrl+C to cancel a response, or to exit"));
|
|
@@ -6277,7 +6954,7 @@ async function executeAgent(ctx2, options) {
|
|
|
6277
6954
|
vaultName: vault.name,
|
|
6278
6955
|
password: options.password,
|
|
6279
6956
|
viaAgent: options.viaAgent,
|
|
6280
|
-
|
|
6957
|
+
sessionId: options.sessionId,
|
|
6281
6958
|
verbose: options.verbose
|
|
6282
6959
|
};
|
|
6283
6960
|
const session = new AgentSession(vault, config);
|
|
@@ -6304,11 +6981,146 @@ async function executeAgent(ctx2, options) {
|
|
|
6304
6981
|
}
|
|
6305
6982
|
}
|
|
6306
6983
|
}
|
|
6984
|
+
async function executeAgentAsk(ctx2, message, options) {
|
|
6985
|
+
setSilentMode(true);
|
|
6986
|
+
const originalConsoleLog = console.log;
|
|
6987
|
+
console.log = (...args) => {
|
|
6988
|
+
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
6989
|
+
};
|
|
6990
|
+
try {
|
|
6991
|
+
const vault = await ctx2.ensureActiveVault();
|
|
6992
|
+
const config = {
|
|
6993
|
+
backendUrl: options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998",
|
|
6994
|
+
vaultName: vault.name,
|
|
6995
|
+
password: options.password,
|
|
6996
|
+
sessionId: options.session,
|
|
6997
|
+
verbose: options.verbose,
|
|
6998
|
+
askMode: true
|
|
6999
|
+
};
|
|
7000
|
+
const session = new AgentSession(vault, config);
|
|
7001
|
+
const ask = new AskInterface(session, !!config.verbose);
|
|
7002
|
+
const callbacks = ask.getCallbacks();
|
|
7003
|
+
await session.initialize(callbacks);
|
|
7004
|
+
const result = await ask.ask(message);
|
|
7005
|
+
if (options.json) {
|
|
7006
|
+
process.stdout.write(
|
|
7007
|
+
JSON.stringify({
|
|
7008
|
+
session_id: result.sessionId,
|
|
7009
|
+
response: result.response,
|
|
7010
|
+
tool_calls: result.toolCalls,
|
|
7011
|
+
transactions: result.transactions
|
|
7012
|
+
}) + "\n"
|
|
7013
|
+
);
|
|
7014
|
+
} else {
|
|
7015
|
+
process.stdout.write(`session:${result.sessionId}
|
|
7016
|
+
`);
|
|
7017
|
+
if (result.response) {
|
|
7018
|
+
process.stdout.write(`
|
|
7019
|
+
${result.response}
|
|
7020
|
+
`);
|
|
7021
|
+
}
|
|
7022
|
+
for (const tx of result.transactions) {
|
|
7023
|
+
process.stdout.write(`
|
|
7024
|
+
tx:${tx.chain}:${tx.hash}
|
|
7025
|
+
`);
|
|
7026
|
+
if (tx.explorerUrl) {
|
|
7027
|
+
process.stdout.write(`explorer:${tx.explorerUrl}
|
|
7028
|
+
`);
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
} catch (err) {
|
|
7033
|
+
if (options.json) {
|
|
7034
|
+
process.stdout.write(JSON.stringify({ error: err.message }) + "\n");
|
|
7035
|
+
} else {
|
|
7036
|
+
process.stderr.write(`Error: ${err.message}
|
|
7037
|
+
`);
|
|
7038
|
+
}
|
|
7039
|
+
process.exit(1);
|
|
7040
|
+
} finally {
|
|
7041
|
+
console.log = originalConsoleLog;
|
|
7042
|
+
setSilentMode(false);
|
|
7043
|
+
}
|
|
7044
|
+
process.exit(0);
|
|
7045
|
+
}
|
|
7046
|
+
async function executeAgentSessionsList(ctx2, options) {
|
|
7047
|
+
const vault = await ctx2.ensureActiveVault();
|
|
7048
|
+
const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998";
|
|
7049
|
+
const client = await createAuthenticatedClient(backendUrl, vault, options.password);
|
|
7050
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
7051
|
+
const PAGE_SIZE = 100;
|
|
7052
|
+
const allConversations = [];
|
|
7053
|
+
let totalCount = 0;
|
|
7054
|
+
let skip = 0;
|
|
7055
|
+
while (true) {
|
|
7056
|
+
const page = await client.listConversations(publicKey, skip, PAGE_SIZE);
|
|
7057
|
+
totalCount = page.total_count;
|
|
7058
|
+
allConversations.push(...page.conversations);
|
|
7059
|
+
if (allConversations.length >= totalCount || page.conversations.length < PAGE_SIZE) break;
|
|
7060
|
+
skip += PAGE_SIZE;
|
|
7061
|
+
}
|
|
7062
|
+
if (isJsonOutput()) {
|
|
7063
|
+
outputJson({
|
|
7064
|
+
sessions: allConversations.map((c) => ({
|
|
7065
|
+
id: c.id,
|
|
7066
|
+
title: c.title,
|
|
7067
|
+
created_at: c.created_at,
|
|
7068
|
+
updated_at: c.updated_at
|
|
7069
|
+
})),
|
|
7070
|
+
total_count: totalCount
|
|
7071
|
+
});
|
|
7072
|
+
return;
|
|
7073
|
+
}
|
|
7074
|
+
if (allConversations.length === 0) {
|
|
7075
|
+
printResult("No sessions found.");
|
|
7076
|
+
return;
|
|
7077
|
+
}
|
|
7078
|
+
const table = new Table({
|
|
7079
|
+
head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
|
|
7080
|
+
});
|
|
7081
|
+
for (const conv of allConversations) {
|
|
7082
|
+
table.push([
|
|
7083
|
+
conv.id,
|
|
7084
|
+
conv.title || chalk9.gray("(untitled)"),
|
|
7085
|
+
formatDate(conv.created_at),
|
|
7086
|
+
formatDate(conv.updated_at)
|
|
7087
|
+
]);
|
|
7088
|
+
}
|
|
7089
|
+
printResult(table.toString());
|
|
7090
|
+
printResult(chalk9.gray(`
|
|
7091
|
+
${totalCount} session(s) total`));
|
|
7092
|
+
}
|
|
7093
|
+
async function executeAgentSessionsDelete(ctx2, sessionId, options) {
|
|
7094
|
+
const vault = await ctx2.ensureActiveVault();
|
|
7095
|
+
const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "http://localhost:9998";
|
|
7096
|
+
const client = await createAuthenticatedClient(backendUrl, vault, options.password);
|
|
7097
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
7098
|
+
await client.deleteConversation(sessionId, publicKey);
|
|
7099
|
+
if (isJsonOutput()) {
|
|
7100
|
+
outputJson({ deleted: sessionId });
|
|
7101
|
+
return;
|
|
7102
|
+
}
|
|
7103
|
+
printResult(chalk9.green(`Session ${sessionId} deleted.`));
|
|
7104
|
+
}
|
|
7105
|
+
async function createAuthenticatedClient(backendUrl, vault, password) {
|
|
7106
|
+
const client = new AgentClient(backendUrl);
|
|
7107
|
+
const auth = await authenticateVault(client, vault, password);
|
|
7108
|
+
client.setAuthToken(auth.token);
|
|
7109
|
+
return client;
|
|
7110
|
+
}
|
|
7111
|
+
function formatDate(iso) {
|
|
7112
|
+
try {
|
|
7113
|
+
const d = new Date(iso);
|
|
7114
|
+
return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
7115
|
+
} catch {
|
|
7116
|
+
return iso;
|
|
7117
|
+
}
|
|
7118
|
+
}
|
|
6307
7119
|
|
|
6308
7120
|
// src/interactive/completer.ts
|
|
6309
7121
|
import { Chain as Chain10 } from "@vultisig/sdk";
|
|
6310
|
-
import
|
|
6311
|
-
import
|
|
7122
|
+
import fs3 from "fs";
|
|
7123
|
+
import path3 from "path";
|
|
6312
7124
|
var COMMANDS = [
|
|
6313
7125
|
// Vault management
|
|
6314
7126
|
"vaults",
|
|
@@ -6403,28 +7215,28 @@ function createCompleter(ctx2) {
|
|
|
6403
7215
|
}
|
|
6404
7216
|
function completeFilePath(partial, filterVult) {
|
|
6405
7217
|
try {
|
|
6406
|
-
const endsWithSeparator = partial.endsWith("/") || partial.endsWith(
|
|
7218
|
+
const endsWithSeparator = partial.endsWith("/") || partial.endsWith(path3.sep);
|
|
6407
7219
|
let dir;
|
|
6408
7220
|
let basename;
|
|
6409
7221
|
if (endsWithSeparator) {
|
|
6410
7222
|
dir = partial;
|
|
6411
7223
|
basename = "";
|
|
6412
7224
|
} else {
|
|
6413
|
-
dir =
|
|
6414
|
-
basename =
|
|
6415
|
-
if (
|
|
7225
|
+
dir = path3.dirname(partial);
|
|
7226
|
+
basename = path3.basename(partial);
|
|
7227
|
+
if (fs3.existsSync(partial) && fs3.statSync(partial).isDirectory()) {
|
|
6416
7228
|
dir = partial;
|
|
6417
7229
|
basename = "";
|
|
6418
7230
|
}
|
|
6419
7231
|
}
|
|
6420
|
-
const resolvedDir =
|
|
6421
|
-
if (!
|
|
7232
|
+
const resolvedDir = path3.resolve(dir);
|
|
7233
|
+
if (!fs3.existsSync(resolvedDir) || !fs3.statSync(resolvedDir).isDirectory()) {
|
|
6422
7234
|
return [[], partial];
|
|
6423
7235
|
}
|
|
6424
|
-
const files =
|
|
7236
|
+
const files = fs3.readdirSync(resolvedDir);
|
|
6425
7237
|
const matches = files.filter((file) => file.startsWith(basename)).map((file) => {
|
|
6426
|
-
const fullPath =
|
|
6427
|
-
const stats =
|
|
7238
|
+
const fullPath = path3.join(dir, file);
|
|
7239
|
+
const stats = fs3.statSync(path3.join(resolvedDir, file));
|
|
6428
7240
|
if (stats.isDirectory()) {
|
|
6429
7241
|
return fullPath + "/";
|
|
6430
7242
|
}
|
|
@@ -6465,7 +7277,7 @@ function findChainByName(name) {
|
|
|
6465
7277
|
}
|
|
6466
7278
|
|
|
6467
7279
|
// src/interactive/event-buffer.ts
|
|
6468
|
-
import
|
|
7280
|
+
import chalk10 from "chalk";
|
|
6469
7281
|
var EventBuffer = class {
|
|
6470
7282
|
eventBuffer = [];
|
|
6471
7283
|
isCommandRunning = false;
|
|
@@ -6505,17 +7317,17 @@ var EventBuffer = class {
|
|
|
6505
7317
|
displayEvent(message, type) {
|
|
6506
7318
|
switch (type) {
|
|
6507
7319
|
case "success":
|
|
6508
|
-
console.log(
|
|
7320
|
+
console.log(chalk10.green(message));
|
|
6509
7321
|
break;
|
|
6510
7322
|
case "warning":
|
|
6511
|
-
console.log(
|
|
7323
|
+
console.log(chalk10.yellow(message));
|
|
6512
7324
|
break;
|
|
6513
7325
|
case "error":
|
|
6514
|
-
console.error(
|
|
7326
|
+
console.error(chalk10.red(message));
|
|
6515
7327
|
break;
|
|
6516
7328
|
case "info":
|
|
6517
7329
|
default:
|
|
6518
|
-
console.log(
|
|
7330
|
+
console.log(chalk10.blue(message));
|
|
6519
7331
|
break;
|
|
6520
7332
|
}
|
|
6521
7333
|
}
|
|
@@ -6526,13 +7338,13 @@ var EventBuffer = class {
|
|
|
6526
7338
|
if (this.eventBuffer.length === 0) {
|
|
6527
7339
|
return;
|
|
6528
7340
|
}
|
|
6529
|
-
console.log(
|
|
7341
|
+
console.log(chalk10.gray("\n--- Background Events ---"));
|
|
6530
7342
|
this.eventBuffer.forEach((event) => {
|
|
6531
7343
|
const timeStr = event.timestamp.toLocaleTimeString();
|
|
6532
7344
|
const message = `[${timeStr}] ${event.message}`;
|
|
6533
7345
|
this.displayEvent(message, event.type);
|
|
6534
7346
|
});
|
|
6535
|
-
console.log(
|
|
7347
|
+
console.log(chalk10.gray("--- End Events ---\n"));
|
|
6536
7348
|
}
|
|
6537
7349
|
/**
|
|
6538
7350
|
* Setup all vault event listeners
|
|
@@ -6642,13 +7454,13 @@ var EventBuffer = class {
|
|
|
6642
7454
|
|
|
6643
7455
|
// src/interactive/session.ts
|
|
6644
7456
|
import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
|
|
6645
|
-
import
|
|
7457
|
+
import chalk12 from "chalk";
|
|
6646
7458
|
import ora3 from "ora";
|
|
6647
7459
|
import * as readline3 from "readline";
|
|
6648
7460
|
|
|
6649
7461
|
// src/interactive/shell-commands.ts
|
|
6650
|
-
import
|
|
6651
|
-
import
|
|
7462
|
+
import chalk11 from "chalk";
|
|
7463
|
+
import Table2 from "cli-table3";
|
|
6652
7464
|
import inquirer6 from "inquirer";
|
|
6653
7465
|
import ora2 from "ora";
|
|
6654
7466
|
function formatTimeRemaining(ms) {
|
|
@@ -6664,25 +7476,25 @@ function formatTimeRemaining(ms) {
|
|
|
6664
7476
|
async function executeLock(ctx2) {
|
|
6665
7477
|
const vault = ctx2.getActiveVault();
|
|
6666
7478
|
if (!vault) {
|
|
6667
|
-
console.log(
|
|
6668
|
-
console.log(
|
|
7479
|
+
console.log(chalk11.red("No active vault."));
|
|
7480
|
+
console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
6669
7481
|
return;
|
|
6670
7482
|
}
|
|
6671
7483
|
ctx2.lockVault(vault.id);
|
|
6672
|
-
console.log(
|
|
6673
|
-
console.log(
|
|
7484
|
+
console.log(chalk11.green("\n+ Vault locked"));
|
|
7485
|
+
console.log(chalk11.gray("Password cache cleared. You will need to enter the password again."));
|
|
6674
7486
|
}
|
|
6675
7487
|
async function executeUnlock(ctx2) {
|
|
6676
7488
|
const vault = ctx2.getActiveVault();
|
|
6677
7489
|
if (!vault) {
|
|
6678
|
-
console.log(
|
|
6679
|
-
console.log(
|
|
7490
|
+
console.log(chalk11.red("No active vault."));
|
|
7491
|
+
console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
6680
7492
|
return;
|
|
6681
7493
|
}
|
|
6682
7494
|
if (ctx2.isVaultUnlocked(vault.id)) {
|
|
6683
7495
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
6684
|
-
console.log(
|
|
6685
|
-
console.log(
|
|
7496
|
+
console.log(chalk11.yellow("\nVault is already unlocked."));
|
|
7497
|
+
console.log(chalk11.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
6686
7498
|
return;
|
|
6687
7499
|
}
|
|
6688
7500
|
const { password } = await inquirer6.prompt([
|
|
@@ -6699,19 +7511,19 @@ async function executeUnlock(ctx2) {
|
|
|
6699
7511
|
ctx2.cachePassword(vault.id, password);
|
|
6700
7512
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
6701
7513
|
spinner.succeed("Vault unlocked");
|
|
6702
|
-
console.log(
|
|
7514
|
+
console.log(chalk11.green(`
|
|
6703
7515
|
+ Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
|
|
6704
7516
|
} catch (err) {
|
|
6705
7517
|
spinner.fail("Failed to unlock vault");
|
|
6706
|
-
console.error(
|
|
7518
|
+
console.error(chalk11.red(`
|
|
6707
7519
|
x ${err.message}`));
|
|
6708
7520
|
}
|
|
6709
7521
|
}
|
|
6710
7522
|
async function executeStatus(ctx2) {
|
|
6711
7523
|
const vault = ctx2.getActiveVault();
|
|
6712
7524
|
if (!vault) {
|
|
6713
|
-
console.log(
|
|
6714
|
-
console.log(
|
|
7525
|
+
console.log(chalk11.red("No active vault."));
|
|
7526
|
+
console.log(chalk11.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
6715
7527
|
return;
|
|
6716
7528
|
}
|
|
6717
7529
|
const isUnlocked = ctx2.isVaultUnlocked(vault.id);
|
|
@@ -6742,30 +7554,30 @@ async function executeStatus(ctx2) {
|
|
|
6742
7554
|
displayStatus(status);
|
|
6743
7555
|
}
|
|
6744
7556
|
function displayStatus(status) {
|
|
6745
|
-
console.log(
|
|
6746
|
-
console.log(
|
|
6747
|
-
console.log(
|
|
6748
|
-
console.log(
|
|
6749
|
-
console.log(` Name: ${
|
|
7557
|
+
console.log(chalk11.cyan("\n+----------------------------------------+"));
|
|
7558
|
+
console.log(chalk11.cyan("| Vault Status |"));
|
|
7559
|
+
console.log(chalk11.cyan("+----------------------------------------+\n"));
|
|
7560
|
+
console.log(chalk11.bold("Vault:"));
|
|
7561
|
+
console.log(` Name: ${chalk11.green(status.name)}`);
|
|
6750
7562
|
console.log(` ID: ${status.id}`);
|
|
6751
|
-
console.log(` Type: ${
|
|
6752
|
-
console.log(
|
|
7563
|
+
console.log(` Type: ${chalk11.yellow(status.type)}`);
|
|
7564
|
+
console.log(chalk11.bold("\nSecurity:"));
|
|
6753
7565
|
if (status.isUnlocked) {
|
|
6754
|
-
console.log(` Status: ${
|
|
7566
|
+
console.log(` Status: ${chalk11.green("Unlocked")} ${chalk11.green("\u{1F513}")}`);
|
|
6755
7567
|
console.log(` Expires: ${status.timeRemainingFormatted}`);
|
|
6756
7568
|
} else {
|
|
6757
|
-
console.log(` Status: ${
|
|
7569
|
+
console.log(` Status: ${chalk11.yellow("Locked")} ${chalk11.yellow("\u{1F512}")}`);
|
|
6758
7570
|
}
|
|
6759
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
6760
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
6761
|
-
console.log(
|
|
7571
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk11.green("Yes") : chalk11.gray("No")}`);
|
|
7572
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk11.green("Yes") : chalk11.yellow("No")}`);
|
|
7573
|
+
console.log(chalk11.bold("\nMPC Configuration:"));
|
|
6762
7574
|
console.log(` Library: ${status.libType}`);
|
|
6763
|
-
console.log(` Threshold: ${
|
|
6764
|
-
console.log(
|
|
7575
|
+
console.log(` Threshold: ${chalk11.cyan(status.threshold)} of ${chalk11.cyan(status.totalSigners)}`);
|
|
7576
|
+
console.log(chalk11.bold("\nSigning Modes:"));
|
|
6765
7577
|
status.availableSigningModes.forEach((mode) => {
|
|
6766
7578
|
console.log(` - ${mode}`);
|
|
6767
7579
|
});
|
|
6768
|
-
console.log(
|
|
7580
|
+
console.log(chalk11.bold("\nDetails:"));
|
|
6769
7581
|
console.log(` Chains: ${status.chains}`);
|
|
6770
7582
|
console.log(` Currency: ${status.currency.toUpperCase()}`);
|
|
6771
7583
|
console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
|
|
@@ -6773,8 +7585,8 @@ function displayStatus(status) {
|
|
|
6773
7585
|
`);
|
|
6774
7586
|
}
|
|
6775
7587
|
function showHelp() {
|
|
6776
|
-
const table = new
|
|
6777
|
-
head: [
|
|
7588
|
+
const table = new Table2({
|
|
7589
|
+
head: [chalk11.bold("Available Commands")],
|
|
6778
7590
|
colWidths: [50],
|
|
6779
7591
|
chars: {
|
|
6780
7592
|
mid: "",
|
|
@@ -6788,7 +7600,7 @@ function showHelp() {
|
|
|
6788
7600
|
}
|
|
6789
7601
|
});
|
|
6790
7602
|
table.push(
|
|
6791
|
-
[
|
|
7603
|
+
[chalk11.bold("Vault Management:")],
|
|
6792
7604
|
[" vaults - List all vaults"],
|
|
6793
7605
|
[" vault <name> - Switch to vault"],
|
|
6794
7606
|
[" import <file> - Import vault from file"],
|
|
@@ -6797,7 +7609,7 @@ function showHelp() {
|
|
|
6797
7609
|
[" info - Show vault details"],
|
|
6798
7610
|
[" export [path] - Export vault to file"],
|
|
6799
7611
|
[""],
|
|
6800
|
-
[
|
|
7612
|
+
[chalk11.bold("Wallet Operations:")],
|
|
6801
7613
|
[" balance [chain] - Show balances"],
|
|
6802
7614
|
[" send <chain> <to> <amount> - Send transaction"],
|
|
6803
7615
|
[" tx-status <chain> <txHash> - Check transaction status"],
|
|
@@ -6806,22 +7618,22 @@ function showHelp() {
|
|
|
6806
7618
|
[" chains [--add/--remove/--add-all] - Manage chains"],
|
|
6807
7619
|
[" tokens <chain> - Manage tokens"],
|
|
6808
7620
|
[""],
|
|
6809
|
-
[
|
|
7621
|
+
[chalk11.bold("Swap Operations:")],
|
|
6810
7622
|
[" swap-chains - List swap-enabled chains"],
|
|
6811
7623
|
[" swap-quote <from> <to> <amount> - Get quote"],
|
|
6812
7624
|
[" swap <from> <to> <amount> - Execute swap"],
|
|
6813
7625
|
[""],
|
|
6814
|
-
[
|
|
7626
|
+
[chalk11.bold("Session Commands (shell only):")],
|
|
6815
7627
|
[" lock - Lock vault"],
|
|
6816
7628
|
[" unlock - Unlock vault"],
|
|
6817
7629
|
[" status - Show vault status"],
|
|
6818
7630
|
[""],
|
|
6819
|
-
[
|
|
7631
|
+
[chalk11.bold("Settings:")],
|
|
6820
7632
|
[" currency [code] - View/set currency"],
|
|
6821
7633
|
[" server - Check server status"],
|
|
6822
7634
|
[" address-book - Manage saved addresses"],
|
|
6823
7635
|
[""],
|
|
6824
|
-
[
|
|
7636
|
+
[chalk11.bold("Help & Navigation:")],
|
|
6825
7637
|
[" help, ? - Show this help"],
|
|
6826
7638
|
[" .clear - Clear screen"],
|
|
6827
7639
|
[" .exit - Exit shell"]
|
|
@@ -6959,12 +7771,12 @@ var ShellSession = class {
|
|
|
6959
7771
|
*/
|
|
6960
7772
|
async start() {
|
|
6961
7773
|
console.clear();
|
|
6962
|
-
console.log(
|
|
6963
|
-
console.log(
|
|
6964
|
-
console.log(
|
|
7774
|
+
console.log(chalk12.cyan.bold("\n=============================================="));
|
|
7775
|
+
console.log(chalk12.cyan.bold(" Vultisig Interactive Shell"));
|
|
7776
|
+
console.log(chalk12.cyan.bold("==============================================\n"));
|
|
6965
7777
|
await this.loadAllVaults();
|
|
6966
7778
|
this.displayVaultList();
|
|
6967
|
-
console.log(
|
|
7779
|
+
console.log(chalk12.gray('Type "help" for available commands, "exit" to quit\n'));
|
|
6968
7780
|
this.promptLoop().catch(() => {
|
|
6969
7781
|
});
|
|
6970
7782
|
}
|
|
@@ -6998,12 +7810,12 @@ var ShellSession = class {
|
|
|
6998
7810
|
const now = Date.now();
|
|
6999
7811
|
if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
|
|
7000
7812
|
rl.close();
|
|
7001
|
-
console.log(
|
|
7813
|
+
console.log(chalk12.yellow("\nGoodbye!"));
|
|
7002
7814
|
this.ctx.dispose();
|
|
7003
7815
|
process.exit(0);
|
|
7004
7816
|
}
|
|
7005
7817
|
this.lastSigintTime = now;
|
|
7006
|
-
console.log(
|
|
7818
|
+
console.log(chalk12.yellow("\n(Press Ctrl+C again to exit)"));
|
|
7007
7819
|
rl.close();
|
|
7008
7820
|
resolve("");
|
|
7009
7821
|
});
|
|
@@ -7098,7 +7910,7 @@ var ShellSession = class {
|
|
|
7098
7910
|
stopAllSpinners();
|
|
7099
7911
|
process.stdout.write("\x1B[?25h");
|
|
7100
7912
|
process.stdout.write("\r\x1B[K");
|
|
7101
|
-
console.log(
|
|
7913
|
+
console.log(chalk12.yellow("\nCancelling operation..."));
|
|
7102
7914
|
};
|
|
7103
7915
|
const cleanup = () => {
|
|
7104
7916
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -7135,10 +7947,10 @@ var ShellSession = class {
|
|
|
7135
7947
|
stopAllSpinners();
|
|
7136
7948
|
process.stdout.write("\x1B[?25h");
|
|
7137
7949
|
process.stdout.write("\r\x1B[K");
|
|
7138
|
-
console.log(
|
|
7950
|
+
console.log(chalk12.yellow("Operation cancelled"));
|
|
7139
7951
|
return;
|
|
7140
7952
|
}
|
|
7141
|
-
console.error(
|
|
7953
|
+
console.error(chalk12.red(`
|
|
7142
7954
|
Error: ${error2.message}`));
|
|
7143
7955
|
}
|
|
7144
7956
|
}
|
|
@@ -7171,7 +7983,7 @@ Error: ${error2.message}`));
|
|
|
7171
7983
|
break;
|
|
7172
7984
|
case "rename":
|
|
7173
7985
|
if (args.length === 0) {
|
|
7174
|
-
console.log(
|
|
7986
|
+
console.log(chalk12.yellow("Usage: rename <newName>"));
|
|
7175
7987
|
return;
|
|
7176
7988
|
}
|
|
7177
7989
|
await executeRename(this.ctx, args.join(" "));
|
|
@@ -7247,41 +8059,41 @@ Error: ${error2.message}`));
|
|
|
7247
8059
|
// Exit
|
|
7248
8060
|
case "exit":
|
|
7249
8061
|
case "quit":
|
|
7250
|
-
console.log(
|
|
8062
|
+
console.log(chalk12.yellow("\nGoodbye!"));
|
|
7251
8063
|
this.ctx.dispose();
|
|
7252
8064
|
process.exit(0);
|
|
7253
8065
|
break;
|
|
7254
8066
|
// eslint requires break even after process.exit
|
|
7255
8067
|
default:
|
|
7256
|
-
console.log(
|
|
7257
|
-
console.log(
|
|
8068
|
+
console.log(chalk12.yellow(`Unknown command: ${command}`));
|
|
8069
|
+
console.log(chalk12.gray('Type "help" for available commands'));
|
|
7258
8070
|
break;
|
|
7259
8071
|
}
|
|
7260
8072
|
}
|
|
7261
8073
|
// ===== Command Helpers =====
|
|
7262
8074
|
async switchVault(args) {
|
|
7263
8075
|
if (args.length === 0) {
|
|
7264
|
-
console.log(
|
|
7265
|
-
console.log(
|
|
8076
|
+
console.log(chalk12.yellow("Usage: vault <name>"));
|
|
8077
|
+
console.log(chalk12.gray('Run "vaults" to see available vaults'));
|
|
7266
8078
|
return;
|
|
7267
8079
|
}
|
|
7268
8080
|
const vaultName = args.join(" ");
|
|
7269
8081
|
const vault = this.ctx.findVaultByName(vaultName);
|
|
7270
8082
|
if (!vault) {
|
|
7271
|
-
console.log(
|
|
7272
|
-
console.log(
|
|
8083
|
+
console.log(chalk12.red(`Vault not found: ${vaultName}`));
|
|
8084
|
+
console.log(chalk12.gray('Run "vaults" to see available vaults'));
|
|
7273
8085
|
return;
|
|
7274
8086
|
}
|
|
7275
8087
|
await this.ctx.setActiveVault(vault);
|
|
7276
|
-
console.log(
|
|
8088
|
+
console.log(chalk12.green(`
|
|
7277
8089
|
+ Switched to: ${vault.name}`));
|
|
7278
8090
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
7279
|
-
const status = isUnlocked ?
|
|
8091
|
+
const status = isUnlocked ? chalk12.green("Unlocked") : chalk12.yellow("Locked");
|
|
7280
8092
|
console.log(`Status: ${status}`);
|
|
7281
8093
|
}
|
|
7282
8094
|
async importVault(args) {
|
|
7283
8095
|
if (args.length === 0) {
|
|
7284
|
-
console.log(
|
|
8096
|
+
console.log(chalk12.yellow("Usage: import <file>"));
|
|
7285
8097
|
return;
|
|
7286
8098
|
}
|
|
7287
8099
|
const filePath = args.join(" ");
|
|
@@ -7296,45 +8108,45 @@ Error: ${error2.message}`));
|
|
|
7296
8108
|
async createVault(args) {
|
|
7297
8109
|
const type = args[0]?.toLowerCase();
|
|
7298
8110
|
if (!type || type !== "fast" && type !== "secure") {
|
|
7299
|
-
console.log(
|
|
7300
|
-
console.log(
|
|
7301
|
-
console.log(
|
|
8111
|
+
console.log(chalk12.yellow("Usage: create <fast|secure>"));
|
|
8112
|
+
console.log(chalk12.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
8113
|
+
console.log(chalk12.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
7302
8114
|
return;
|
|
7303
8115
|
}
|
|
7304
8116
|
let vault;
|
|
7305
8117
|
if (type === "fast") {
|
|
7306
8118
|
const name = await this.prompt("Vault name");
|
|
7307
8119
|
if (!name) {
|
|
7308
|
-
console.log(
|
|
8120
|
+
console.log(chalk12.red("Name is required"));
|
|
7309
8121
|
return;
|
|
7310
8122
|
}
|
|
7311
8123
|
const password = await this.promptPassword("Vault password");
|
|
7312
8124
|
if (!password) {
|
|
7313
|
-
console.log(
|
|
8125
|
+
console.log(chalk12.red("Password is required"));
|
|
7314
8126
|
return;
|
|
7315
8127
|
}
|
|
7316
8128
|
const email = await this.prompt("Email for verification");
|
|
7317
8129
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
7318
|
-
console.log(
|
|
8130
|
+
console.log(chalk12.red("Valid email is required"));
|
|
7319
8131
|
return;
|
|
7320
8132
|
}
|
|
7321
8133
|
vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
|
|
7322
8134
|
} else {
|
|
7323
8135
|
const name = await this.prompt("Vault name");
|
|
7324
8136
|
if (!name) {
|
|
7325
|
-
console.log(
|
|
8137
|
+
console.log(chalk12.red("Name is required"));
|
|
7326
8138
|
return;
|
|
7327
8139
|
}
|
|
7328
8140
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
7329
8141
|
const shares = parseInt(sharesStr, 10);
|
|
7330
8142
|
if (isNaN(shares) || shares < 2) {
|
|
7331
|
-
console.log(
|
|
8143
|
+
console.log(chalk12.red("Must have at least 2 shares"));
|
|
7332
8144
|
return;
|
|
7333
8145
|
}
|
|
7334
8146
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
7335
8147
|
const threshold = parseInt(thresholdStr, 10);
|
|
7336
8148
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
7337
|
-
console.log(
|
|
8149
|
+
console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
|
|
7338
8150
|
return;
|
|
7339
8151
|
}
|
|
7340
8152
|
const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
|
|
@@ -7356,37 +8168,37 @@ Error: ${error2.message}`));
|
|
|
7356
8168
|
async importSeedphrase(args) {
|
|
7357
8169
|
const type = args[0]?.toLowerCase();
|
|
7358
8170
|
if (!type || type !== "fast" && type !== "secure") {
|
|
7359
|
-
console.log(
|
|
7360
|
-
console.log(
|
|
7361
|
-
console.log(
|
|
8171
|
+
console.log(chalk12.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
8172
|
+
console.log(chalk12.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
8173
|
+
console.log(chalk12.gray(" secure - Import with device coordination (N-of-M)"));
|
|
7362
8174
|
return;
|
|
7363
8175
|
}
|
|
7364
|
-
console.log(
|
|
8176
|
+
console.log(chalk12.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
7365
8177
|
const mnemonic = await this.promptPassword("Seedphrase");
|
|
7366
8178
|
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
7367
8179
|
if (!validation.valid) {
|
|
7368
|
-
console.log(
|
|
8180
|
+
console.log(chalk12.red(`Invalid seedphrase: ${validation.error}`));
|
|
7369
8181
|
if (validation.invalidWords?.length) {
|
|
7370
|
-
console.log(
|
|
8182
|
+
console.log(chalk12.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
7371
8183
|
}
|
|
7372
8184
|
return;
|
|
7373
8185
|
}
|
|
7374
|
-
console.log(
|
|
8186
|
+
console.log(chalk12.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
7375
8187
|
let vault;
|
|
7376
8188
|
if (type === "fast") {
|
|
7377
8189
|
const name = await this.prompt("Vault name");
|
|
7378
8190
|
if (!name) {
|
|
7379
|
-
console.log(
|
|
8191
|
+
console.log(chalk12.red("Name is required"));
|
|
7380
8192
|
return;
|
|
7381
8193
|
}
|
|
7382
8194
|
const password = await this.promptPassword("Vault password");
|
|
7383
8195
|
if (!password) {
|
|
7384
|
-
console.log(
|
|
8196
|
+
console.log(chalk12.red("Password is required"));
|
|
7385
8197
|
return;
|
|
7386
8198
|
}
|
|
7387
8199
|
const email = await this.prompt("Email for verification");
|
|
7388
8200
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
7389
|
-
console.log(
|
|
8201
|
+
console.log(chalk12.red("Valid email is required"));
|
|
7390
8202
|
return;
|
|
7391
8203
|
}
|
|
7392
8204
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
@@ -7404,19 +8216,19 @@ Error: ${error2.message}`));
|
|
|
7404
8216
|
} else {
|
|
7405
8217
|
const name = await this.prompt("Vault name");
|
|
7406
8218
|
if (!name) {
|
|
7407
|
-
console.log(
|
|
8219
|
+
console.log(chalk12.red("Name is required"));
|
|
7408
8220
|
return;
|
|
7409
8221
|
}
|
|
7410
8222
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
7411
8223
|
const shares = parseInt(sharesStr, 10);
|
|
7412
8224
|
if (isNaN(shares) || shares < 2) {
|
|
7413
|
-
console.log(
|
|
8225
|
+
console.log(chalk12.red("Must have at least 2 shares"));
|
|
7414
8226
|
return;
|
|
7415
8227
|
}
|
|
7416
8228
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
7417
8229
|
const threshold = parseInt(thresholdStr, 10);
|
|
7418
8230
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
7419
|
-
console.log(
|
|
8231
|
+
console.log(chalk12.red(`Threshold must be between 1 and ${shares}`));
|
|
7420
8232
|
return;
|
|
7421
8233
|
}
|
|
7422
8234
|
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
@@ -7460,8 +8272,8 @@ Error: ${error2.message}`));
|
|
|
7460
8272
|
}
|
|
7461
8273
|
}
|
|
7462
8274
|
if (!fiatCurrencies3.includes(currency)) {
|
|
7463
|
-
console.log(
|
|
7464
|
-
console.log(
|
|
8275
|
+
console.log(chalk12.red(`Invalid currency: ${currency}`));
|
|
8276
|
+
console.log(chalk12.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
|
|
7465
8277
|
return;
|
|
7466
8278
|
}
|
|
7467
8279
|
const raw = args.includes("--raw");
|
|
@@ -7469,7 +8281,7 @@ Error: ${error2.message}`));
|
|
|
7469
8281
|
}
|
|
7470
8282
|
async runSend(args) {
|
|
7471
8283
|
if (args.length < 3) {
|
|
7472
|
-
console.log(
|
|
8284
|
+
console.log(chalk12.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
|
|
7473
8285
|
return;
|
|
7474
8286
|
}
|
|
7475
8287
|
const [chainStr, to, amount, ...rest] = args;
|
|
@@ -7489,7 +8301,7 @@ Error: ${error2.message}`));
|
|
|
7489
8301
|
await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
|
|
7490
8302
|
} catch (err) {
|
|
7491
8303
|
if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
7492
|
-
console.log(
|
|
8304
|
+
console.log(chalk12.yellow("\nTransaction cancelled"));
|
|
7493
8305
|
return;
|
|
7494
8306
|
}
|
|
7495
8307
|
throw err;
|
|
@@ -7497,7 +8309,7 @@ Error: ${error2.message}`));
|
|
|
7497
8309
|
}
|
|
7498
8310
|
async runTxStatus(args) {
|
|
7499
8311
|
if (args.length < 2) {
|
|
7500
|
-
console.log(
|
|
8312
|
+
console.log(chalk12.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
|
|
7501
8313
|
return;
|
|
7502
8314
|
}
|
|
7503
8315
|
const [chainStr, txHash, ...rest] = args;
|
|
@@ -7515,8 +8327,8 @@ Error: ${error2.message}`));
|
|
|
7515
8327
|
} else if (args[i] === "--add" && i + 1 < args.length) {
|
|
7516
8328
|
const chain = findChainByName(args[i + 1]);
|
|
7517
8329
|
if (!chain) {
|
|
7518
|
-
console.log(
|
|
7519
|
-
console.log(
|
|
8330
|
+
console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
|
|
8331
|
+
console.log(chalk12.gray("Use tab completion to see available chains"));
|
|
7520
8332
|
return;
|
|
7521
8333
|
}
|
|
7522
8334
|
addChain = chain;
|
|
@@ -7524,8 +8336,8 @@ Error: ${error2.message}`));
|
|
|
7524
8336
|
} else if (args[i] === "--remove" && i + 1 < args.length) {
|
|
7525
8337
|
const chain = findChainByName(args[i + 1]);
|
|
7526
8338
|
if (!chain) {
|
|
7527
|
-
console.log(
|
|
7528
|
-
console.log(
|
|
8339
|
+
console.log(chalk12.red(`Unknown chain: ${args[i + 1]}`));
|
|
8340
|
+
console.log(chalk12.gray("Use tab completion to see available chains"));
|
|
7529
8341
|
return;
|
|
7530
8342
|
}
|
|
7531
8343
|
removeChain = chain;
|
|
@@ -7536,7 +8348,7 @@ Error: ${error2.message}`));
|
|
|
7536
8348
|
}
|
|
7537
8349
|
async runTokens(args) {
|
|
7538
8350
|
if (args.length === 0) {
|
|
7539
|
-
console.log(
|
|
8351
|
+
console.log(chalk12.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
|
|
7540
8352
|
return;
|
|
7541
8353
|
}
|
|
7542
8354
|
const chainStr = args[0];
|
|
@@ -7557,7 +8369,7 @@ Error: ${error2.message}`));
|
|
|
7557
8369
|
async runSwapQuote(args) {
|
|
7558
8370
|
if (args.length < 3) {
|
|
7559
8371
|
console.log(
|
|
7560
|
-
|
|
8372
|
+
chalk12.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
|
|
7561
8373
|
);
|
|
7562
8374
|
return;
|
|
7563
8375
|
}
|
|
@@ -7581,7 +8393,7 @@ Error: ${error2.message}`));
|
|
|
7581
8393
|
async runSwap(args) {
|
|
7582
8394
|
if (args.length < 3) {
|
|
7583
8395
|
console.log(
|
|
7584
|
-
|
|
8396
|
+
chalk12.yellow(
|
|
7585
8397
|
"Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
|
|
7586
8398
|
)
|
|
7587
8399
|
);
|
|
@@ -7612,7 +8424,7 @@ Error: ${error2.message}`));
|
|
|
7612
8424
|
);
|
|
7613
8425
|
} catch (err) {
|
|
7614
8426
|
if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
7615
|
-
console.log(
|
|
8427
|
+
console.log(chalk12.yellow("\nSwap cancelled"));
|
|
7616
8428
|
return;
|
|
7617
8429
|
}
|
|
7618
8430
|
throw err;
|
|
@@ -7674,24 +8486,24 @@ Error: ${error2.message}`));
|
|
|
7674
8486
|
}
|
|
7675
8487
|
getPrompt() {
|
|
7676
8488
|
const vault = this.ctx.getActiveVault();
|
|
7677
|
-
if (!vault) return
|
|
8489
|
+
if (!vault) return chalk12.cyan("wallet> ");
|
|
7678
8490
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
7679
|
-
const status = isUnlocked ?
|
|
7680
|
-
return
|
|
8491
|
+
const status = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
|
|
8492
|
+
return chalk12.cyan(`wallet[${vault.name}]${status}> `);
|
|
7681
8493
|
}
|
|
7682
8494
|
displayVaultList() {
|
|
7683
8495
|
const vaults = Array.from(this.ctx.getVaults().values());
|
|
7684
8496
|
const activeVault = this.ctx.getActiveVault();
|
|
7685
8497
|
if (vaults.length === 0) {
|
|
7686
|
-
console.log(
|
|
8498
|
+
console.log(chalk12.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
|
|
7687
8499
|
return;
|
|
7688
8500
|
}
|
|
7689
|
-
console.log(
|
|
8501
|
+
console.log(chalk12.cyan("Loaded Vaults:\n"));
|
|
7690
8502
|
vaults.forEach((vault) => {
|
|
7691
8503
|
const isActive = vault.id === activeVault?.id;
|
|
7692
8504
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
7693
|
-
const activeMarker = isActive ?
|
|
7694
|
-
const lockIcon = isUnlocked ?
|
|
8505
|
+
const activeMarker = isActive ? chalk12.green(" (active)") : "";
|
|
8506
|
+
const lockIcon = isUnlocked ? chalk12.green("\u{1F513}") : chalk12.yellow("\u{1F512}");
|
|
7695
8507
|
console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
|
|
7696
8508
|
});
|
|
7697
8509
|
console.log();
|
|
@@ -7699,23 +8511,23 @@ Error: ${error2.message}`));
|
|
|
7699
8511
|
};
|
|
7700
8512
|
|
|
7701
8513
|
// src/lib/errors.ts
|
|
7702
|
-
import
|
|
8514
|
+
import chalk13 from "chalk";
|
|
7703
8515
|
|
|
7704
8516
|
// src/lib/version.ts
|
|
7705
|
-
import
|
|
7706
|
-
import { readFileSync as
|
|
7707
|
-
import { homedir as
|
|
7708
|
-
import { join as
|
|
8517
|
+
import chalk14 from "chalk";
|
|
8518
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
|
|
8519
|
+
import { homedir as homedir3 } from "os";
|
|
8520
|
+
import { join as join3 } from "path";
|
|
7709
8521
|
var cachedVersion = null;
|
|
7710
8522
|
function getVersion() {
|
|
7711
8523
|
if (cachedVersion) return cachedVersion;
|
|
7712
8524
|
if (true) {
|
|
7713
|
-
cachedVersion = "0.
|
|
8525
|
+
cachedVersion = "0.9.0";
|
|
7714
8526
|
return cachedVersion;
|
|
7715
8527
|
}
|
|
7716
8528
|
try {
|
|
7717
8529
|
const packagePath = new URL("../../package.json", import.meta.url);
|
|
7718
|
-
const pkg = JSON.parse(
|
|
8530
|
+
const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
|
|
7719
8531
|
cachedVersion = pkg.version;
|
|
7720
8532
|
return cachedVersion;
|
|
7721
8533
|
} catch {
|
|
@@ -7723,13 +8535,13 @@ function getVersion() {
|
|
|
7723
8535
|
return cachedVersion;
|
|
7724
8536
|
}
|
|
7725
8537
|
}
|
|
7726
|
-
var CACHE_DIR =
|
|
7727
|
-
var VERSION_CACHE_FILE =
|
|
8538
|
+
var CACHE_DIR = join3(homedir3(), ".vultisig", "cache");
|
|
8539
|
+
var VERSION_CACHE_FILE = join3(CACHE_DIR, "version-check.json");
|
|
7728
8540
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7729
8541
|
function readVersionCache() {
|
|
7730
8542
|
try {
|
|
7731
8543
|
if (!existsSync2(VERSION_CACHE_FILE)) return null;
|
|
7732
|
-
const data =
|
|
8544
|
+
const data = readFileSync3(VERSION_CACHE_FILE, "utf-8");
|
|
7733
8545
|
return JSON.parse(data);
|
|
7734
8546
|
} catch {
|
|
7735
8547
|
return null;
|
|
@@ -7738,9 +8550,9 @@ function readVersionCache() {
|
|
|
7738
8550
|
function writeVersionCache(cache) {
|
|
7739
8551
|
try {
|
|
7740
8552
|
if (!existsSync2(CACHE_DIR)) {
|
|
7741
|
-
|
|
8553
|
+
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
7742
8554
|
}
|
|
7743
|
-
|
|
8555
|
+
writeFileSync3(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
7744
8556
|
} catch {
|
|
7745
8557
|
}
|
|
7746
8558
|
}
|
|
@@ -7807,7 +8619,7 @@ function formatVersionShort() {
|
|
|
7807
8619
|
}
|
|
7808
8620
|
function formatVersionDetailed() {
|
|
7809
8621
|
const lines = [];
|
|
7810
|
-
lines.push(
|
|
8622
|
+
lines.push(chalk14.bold(`Vultisig CLI v${getVersion()}`));
|
|
7811
8623
|
lines.push("");
|
|
7812
8624
|
lines.push(` Node.js: ${process.version}`);
|
|
7813
8625
|
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
@@ -7845,9 +8657,9 @@ function getUpdateCommand() {
|
|
|
7845
8657
|
}
|
|
7846
8658
|
|
|
7847
8659
|
// src/lib/completion.ts
|
|
7848
|
-
import { homedir as
|
|
7849
|
-
import { join as
|
|
7850
|
-
import { readFileSync as
|
|
8660
|
+
import { homedir as homedir4 } from "os";
|
|
8661
|
+
import { join as join4 } from "path";
|
|
8662
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
7851
8663
|
var tabtab = null;
|
|
7852
8664
|
async function getTabtab() {
|
|
7853
8665
|
if (!tabtab) {
|
|
@@ -7912,7 +8724,7 @@ var CHAINS = [
|
|
|
7912
8724
|
];
|
|
7913
8725
|
function getVaultNames() {
|
|
7914
8726
|
try {
|
|
7915
|
-
const vaultDir =
|
|
8727
|
+
const vaultDir = join4(homedir4(), ".vultisig", "vaults");
|
|
7916
8728
|
if (!existsSync3(vaultDir)) return [];
|
|
7917
8729
|
const { readdirSync } = __require("fs");
|
|
7918
8730
|
const files = readdirSync(vaultDir);
|
|
@@ -7920,7 +8732,7 @@ function getVaultNames() {
|
|
|
7920
8732
|
for (const file of files) {
|
|
7921
8733
|
if (file.startsWith("vault:") && file.endsWith(".json")) {
|
|
7922
8734
|
try {
|
|
7923
|
-
const content =
|
|
8735
|
+
const content = readFileSync4(join4(vaultDir, file), "utf-8");
|
|
7924
8736
|
const vault = JSON.parse(content);
|
|
7925
8737
|
if (vault.name) names.push(vault.name);
|
|
7926
8738
|
if (vault.id) names.push(vault.id);
|
|
@@ -8197,14 +9009,15 @@ async function findVaultByNameOrId(sdk, nameOrId) {
|
|
|
8197
9009
|
if (byPartialId) return byPartialId;
|
|
8198
9010
|
return null;
|
|
8199
9011
|
}
|
|
8200
|
-
async function init(vaultOverride, unlockPassword) {
|
|
9012
|
+
async function init(vaultOverride, unlockPassword, passwordTTL) {
|
|
8201
9013
|
if (!ctx) {
|
|
8202
9014
|
const vaultSelector = vaultOverride || process.env.VULTISIG_VAULT;
|
|
8203
9015
|
if (unlockPassword && vaultSelector) {
|
|
8204
9016
|
cachePassword(vaultSelector, unlockPassword);
|
|
8205
9017
|
}
|
|
8206
9018
|
const sdk = new Vultisig7({
|
|
8207
|
-
onPasswordRequired: createPasswordCallback()
|
|
9019
|
+
onPasswordRequired: createPasswordCallback(),
|
|
9020
|
+
...passwordTTL !== void 0 ? { passwordCache: { defaultTTL: passwordTTL } } : {}
|
|
8208
9021
|
});
|
|
8209
9022
|
await sdk.initialize();
|
|
8210
9023
|
ctx = new CLIContext(sdk);
|
|
@@ -8242,10 +9055,10 @@ createCmd.command("secure").description("Create a secure vault (multi-device MPC
|
|
|
8242
9055
|
});
|
|
8243
9056
|
})
|
|
8244
9057
|
);
|
|
8245
|
-
program.command("import <file>").description("Import vault from .vult file").action(
|
|
8246
|
-
withExit(async (file) => {
|
|
9058
|
+
program.command("import <file>").description("Import vault from .vult file").option("--password <password>", "Password to decrypt the vault file").action(
|
|
9059
|
+
withExit(async (file, options) => {
|
|
8247
9060
|
const context = await init(program.opts().vault);
|
|
8248
|
-
await executeImport(context, file);
|
|
9061
|
+
await executeImport(context, file, options.password);
|
|
8249
9062
|
})
|
|
8250
9063
|
);
|
|
8251
9064
|
var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
|
|
@@ -8362,7 +9175,7 @@ joinCmd.command("secure").description("Join a SecureVault creation session").opt
|
|
|
8362
9175
|
const context = await init(program.opts().vault);
|
|
8363
9176
|
let qrPayload = options.qr;
|
|
8364
9177
|
if (!qrPayload && options.qrFile) {
|
|
8365
|
-
qrPayload = (await
|
|
9178
|
+
qrPayload = (await fs4.readFile(options.qrFile, "utf-8")).trim();
|
|
8366
9179
|
}
|
|
8367
9180
|
if (!qrPayload) {
|
|
8368
9181
|
qrPayload = await promptQrPayload();
|
|
@@ -8512,10 +9325,10 @@ program.command("discount").description("Show your VULT discount tier for swap f
|
|
|
8512
9325
|
})
|
|
8513
9326
|
);
|
|
8514
9327
|
program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").action(
|
|
8515
|
-
withExit(async (
|
|
9328
|
+
withExit(async (path4, options) => {
|
|
8516
9329
|
const context = await init(program.opts().vault, options.password);
|
|
8517
9330
|
await executeExport(context, {
|
|
8518
|
-
outputPath:
|
|
9331
|
+
outputPath: path4,
|
|
8519
9332
|
password: options.password,
|
|
8520
9333
|
exportPassword: options.exportPassword
|
|
8521
9334
|
});
|
|
@@ -8712,24 +9525,71 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
|
|
|
8712
9525
|
}
|
|
8713
9526
|
)
|
|
8714
9527
|
);
|
|
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("--
|
|
8716
|
-
const
|
|
9528
|
+
var agentCmd = program.command("agent").description("AI-powered chat interface for wallet operations").option("--via-agent", "Use NDJSON pipe mode for agent-to-agent communication").option("--verbose", "Show detailed tool call parameters and debug output").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for signing operations").option("--password-ttl <ms>", "Password cache TTL in milliseconds (default: 300000, 86400000/24h for --via-agent)").option("--session-id <id>", "Resume an existing session").action(async (options) => {
|
|
9529
|
+
const MAX_TTL = 864e5;
|
|
9530
|
+
let passwordTTL;
|
|
9531
|
+
if (options.passwordTtl) {
|
|
9532
|
+
const parsed = parseInt(options.passwordTtl, 10);
|
|
9533
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
9534
|
+
throw new Error(`Invalid --password-ttl value: "${options.passwordTtl}". Expected a non-negative integer in milliseconds.`);
|
|
9535
|
+
}
|
|
9536
|
+
passwordTTL = parsed;
|
|
9537
|
+
} else if (options.viaAgent) {
|
|
9538
|
+
passwordTTL = MAX_TTL;
|
|
9539
|
+
}
|
|
9540
|
+
const context = await init(program.opts().vault, options.password, passwordTTL);
|
|
8717
9541
|
await executeAgent(context, {
|
|
8718
9542
|
viaAgent: options.viaAgent,
|
|
8719
9543
|
verbose: options.verbose,
|
|
8720
9544
|
backendUrl: options.backendUrl,
|
|
8721
9545
|
password: options.password,
|
|
8722
|
-
|
|
9546
|
+
sessionId: options.sessionId
|
|
8723
9547
|
});
|
|
8724
9548
|
});
|
|
9549
|
+
agentCmd.command("ask <message>").description("Send a single message and get the response (for AI agent integration)").option("--session <id>", "Continue an existing conversation").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for signing operations").option("--verbose", "Show tool calls and debug info on stderr").option("--json", "Output structured JSON instead of text").action(
|
|
9550
|
+
async (message, options) => {
|
|
9551
|
+
const parentOpts = agentCmd.opts();
|
|
9552
|
+
const context = await init(
|
|
9553
|
+
program.opts().vault,
|
|
9554
|
+
options.password || parentOpts.password
|
|
9555
|
+
);
|
|
9556
|
+
await executeAgentAsk(context, message, {
|
|
9557
|
+
...options,
|
|
9558
|
+
backendUrl: options.backendUrl || parentOpts.backendUrl,
|
|
9559
|
+
password: options.password || parentOpts.password,
|
|
9560
|
+
verbose: options.verbose || parentOpts.verbose
|
|
9561
|
+
});
|
|
9562
|
+
}
|
|
9563
|
+
);
|
|
9564
|
+
var sessionsCmd = agentCmd.command("sessions").description("Manage agent chat sessions");
|
|
9565
|
+
sessionsCmd.command("list").description("List chat sessions for the current vault").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for authentication").action(
|
|
9566
|
+
withExit(async (options) => {
|
|
9567
|
+
const parentOpts = agentCmd.opts();
|
|
9568
|
+
const context = await init(program.opts().vault, options.password || parentOpts.password);
|
|
9569
|
+
await executeAgentSessionsList(context, {
|
|
9570
|
+
backendUrl: options.backendUrl || parentOpts.backendUrl,
|
|
9571
|
+
password: options.password || parentOpts.password
|
|
9572
|
+
});
|
|
9573
|
+
})
|
|
9574
|
+
);
|
|
9575
|
+
sessionsCmd.command("delete <id>").description("Delete a chat session").option("--backend-url <url>", "Agent backend URL (default: http://localhost:9998)").option("--password <password>", "Vault password for authentication").action(
|
|
9576
|
+
withExit(async (id, options) => {
|
|
9577
|
+
const parentOpts = agentCmd.opts();
|
|
9578
|
+
const context = await init(program.opts().vault, options.password || parentOpts.password);
|
|
9579
|
+
await executeAgentSessionsDelete(context, id, {
|
|
9580
|
+
backendUrl: options.backendUrl || parentOpts.backendUrl,
|
|
9581
|
+
password: options.password || parentOpts.password
|
|
9582
|
+
});
|
|
9583
|
+
})
|
|
9584
|
+
);
|
|
8725
9585
|
program.command("version").description("Show detailed version information").action(
|
|
8726
9586
|
withExit(async () => {
|
|
8727
9587
|
printResult(formatVersionDetailed());
|
|
8728
9588
|
const result = await checkForUpdates();
|
|
8729
9589
|
if (result?.updateAvailable && result.latestVersion) {
|
|
8730
9590
|
info("");
|
|
8731
|
-
info(
|
|
8732
|
-
info(
|
|
9591
|
+
info(chalk15.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
9592
|
+
info(chalk15.gray(`Run "${getUpdateCommand()}" to update`));
|
|
8733
9593
|
}
|
|
8734
9594
|
})
|
|
8735
9595
|
);
|
|
@@ -8738,22 +9598,22 @@ program.command("update").description("Check for updates and show update command
|
|
|
8738
9598
|
info("Checking for updates...");
|
|
8739
9599
|
const result = await checkForUpdates();
|
|
8740
9600
|
if (!result) {
|
|
8741
|
-
printResult(
|
|
9601
|
+
printResult(chalk15.gray("Update checking is disabled"));
|
|
8742
9602
|
return;
|
|
8743
9603
|
}
|
|
8744
9604
|
if (result.updateAvailable && result.latestVersion) {
|
|
8745
9605
|
printResult("");
|
|
8746
|
-
printResult(
|
|
9606
|
+
printResult(chalk15.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
8747
9607
|
printResult("");
|
|
8748
9608
|
if (options.check) {
|
|
8749
9609
|
printResult(`Run "${getUpdateCommand()}" to update`);
|
|
8750
9610
|
} else {
|
|
8751
9611
|
const updateCmd = getUpdateCommand();
|
|
8752
9612
|
printResult(`To update, run:`);
|
|
8753
|
-
printResult(
|
|
9613
|
+
printResult(chalk15.cyan(` ${updateCmd}`));
|
|
8754
9614
|
}
|
|
8755
9615
|
} else {
|
|
8756
|
-
printResult(
|
|
9616
|
+
printResult(chalk15.green(`You're on the latest version (${result.currentVersion})`));
|
|
8757
9617
|
}
|
|
8758
9618
|
})
|
|
8759
9619
|
);
|