@vultisig/cli 2.8.0 → 2.8.1
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 +18 -0
- package/dist/index.js +87 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @vultisig/cli
|
|
2
2
|
|
|
3
|
+
## 2.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#867](https://github.com/vultisig/vultisig-sdk/pull/867) [`ddd08af`](https://github.com/vultisig/vultisig-sdk/commit/ddd08af883a1b2ee2f72dac4d406782de9090672) Thanks [@neavra](https://github.com/neavra)! - Agent: poll for final on-chain confirmation after broadcasting a signed tx
|
|
8
|
+
(audit F1). A `pending` `tx_status` only means "broadcast accepted" — the tx can
|
|
9
|
+
still revert, expire, or be dropped. After broadcast the session now polls
|
|
10
|
+
`vault.getTxStatus` until the tx reaches a final state and emits `confirmed` /
|
|
11
|
+
`failed`, or `timeout` when the bounded poll budget (~120s) is exhausted. The
|
|
12
|
+
`ask` result records the latest per-tx `status` (deduped by hash), and the pipe
|
|
13
|
+
`tx_status` event gains a `timeout` status. Best-effort and non-fatal: when the
|
|
14
|
+
chain can't be resolved or the vault can't poll status, the existing `pending`
|
|
15
|
+
status stands. The blocking confirmation wait is scoped to the top of the
|
|
16
|
+
message loop (depth 0 — the single-tx ask/pipe case); inside a multi-turn tool
|
|
17
|
+
loop a leg keeps its honest `pending` instead of stacking the poll budget per
|
|
18
|
+
tx. The shared `pending | confirmed | failed | timeout` union is now threaded
|
|
19
|
+
through the ask result, pipe event, and UI callback without unchecked casts.
|
|
20
|
+
|
|
3
21
|
## 2.8.0
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -8224,9 +8224,15 @@ var AskInterface = class {
|
|
|
8224
8224
|
onSuggestions: (_suggestions) => {
|
|
8225
8225
|
},
|
|
8226
8226
|
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
8227
|
-
this.transactions.
|
|
8227
|
+
const existing = this.transactions.find((t) => t.hash === txHash);
|
|
8228
|
+
if (existing) {
|
|
8229
|
+
existing.status = status;
|
|
8230
|
+
if (explorerUrl) existing.explorerUrl = explorerUrl;
|
|
8231
|
+
} else {
|
|
8232
|
+
this.transactions.push({ hash: txHash, chain, explorerUrl, status });
|
|
8233
|
+
}
|
|
8228
8234
|
if (this.verbose) {
|
|
8229
|
-
process.stderr.write(`[tx] ${chain}: ${txHash}
|
|
8235
|
+
process.stderr.write(`[tx] ${chain}: ${txHash} (${status})
|
|
8230
8236
|
`);
|
|
8231
8237
|
}
|
|
8232
8238
|
},
|
|
@@ -12080,6 +12086,8 @@ var CLIENT_SIDE_TOOL_DISPATCH = {
|
|
|
12080
12086
|
var MAX_MESSAGE_LOOP_DEPTH = 16;
|
|
12081
12087
|
var RECOVERY_POLL_INTERVAL_MS = 2e3;
|
|
12082
12088
|
var RECOVERY_MAX_POLLS = 90;
|
|
12089
|
+
var TX_CONFIRM_POLL_INTERVAL_MS = 3e3;
|
|
12090
|
+
var TX_CONFIRM_MAX_POLLS = 40;
|
|
12083
12091
|
var AgentSession = class {
|
|
12084
12092
|
client;
|
|
12085
12093
|
vault;
|
|
@@ -12097,6 +12105,10 @@ var AgentSession = class {
|
|
|
12097
12105
|
// poll loop without real 2s waits.
|
|
12098
12106
|
recoveryPollIntervalMs = RECOVERY_POLL_INTERVAL_MS;
|
|
12099
12107
|
recoveryMaxPolls = RECOVERY_MAX_POLLS;
|
|
12108
|
+
// Post-broadcast confirmation poll cadence — instance fields so tests can
|
|
12109
|
+
// drive the loop without real waits.
|
|
12110
|
+
txConfirmPollIntervalMs = TX_CONFIRM_POLL_INTERVAL_MS;
|
|
12111
|
+
txConfirmMaxPolls = TX_CONFIRM_MAX_POLLS;
|
|
12100
12112
|
constructor(vault, config) {
|
|
12101
12113
|
this.vault = vault;
|
|
12102
12114
|
this.config = config;
|
|
@@ -12375,7 +12387,9 @@ var AgentSession = class {
|
|
|
12375
12387
|
const txHash = recent.data.tx_hash;
|
|
12376
12388
|
const chain = recent.data.chain;
|
|
12377
12389
|
const explorerUrl = recent.data.explorer_url;
|
|
12378
|
-
if (txHash)
|
|
12390
|
+
if (txHash) {
|
|
12391
|
+
await this.emitAndConfirmTx(txHash, chain, explorerUrl, depth, ui);
|
|
12392
|
+
}
|
|
12379
12393
|
}
|
|
12380
12394
|
await this.processMessageLoop(null, ui, depth + 1);
|
|
12381
12395
|
return;
|
|
@@ -12434,6 +12448,75 @@ var AgentSession = class {
|
|
|
12434
12448
|
recoverySleep() {
|
|
12435
12449
|
return new Promise((resolve) => setTimeout(resolve, this.recoveryPollIntervalMs));
|
|
12436
12450
|
}
|
|
12451
|
+
/**
|
|
12452
|
+
* Post-broadcast confirmation polling (audit F1). A bare `pending` status only
|
|
12453
|
+
* means "broadcast accepted"; the tx can still revert, expire, or be dropped,
|
|
12454
|
+
* so a headless caller that stops at `pending` may mark a later-reverted
|
|
12455
|
+
* operation complete. Poll vault.getTxStatus until the tx reaches a final
|
|
12456
|
+
* state and emit the matching lifecycle status (`confirmed`/`failed`), or
|
|
12457
|
+
* `timeout` when the bounded poll budget is exhausted (the tx may still
|
|
12458
|
+
* confirm later — callers can re-check with `vultisig tx-status`).
|
|
12459
|
+
*
|
|
12460
|
+
* Transient RPC/network errors are treated as "not final yet" and retried
|
|
12461
|
+
* until the budget is spent. Best-effort and non-fatal: if the chain can't be
|
|
12462
|
+
* resolved or the vault doesn't expose getTxStatus, the caller's already-
|
|
12463
|
+
* emitted `pending` status stands and this returns quietly.
|
|
12464
|
+
*
|
|
12465
|
+
* Scoped to headless callers (ask/pipe) that need machine-readable finality.
|
|
12466
|
+
* The interactive TUI already shows `pending` + an explorer link immediately
|
|
12467
|
+
* and has the dedicated `vultisig tx-status` command, so blocking its prompt
|
|
12468
|
+
* for the full poll budget would be a UX regression the audit didn't scope.
|
|
12469
|
+
* The poll also bails on cancel (Ctrl-C aborts the controller) so a long wait
|
|
12470
|
+
* is interruptible.
|
|
12471
|
+
*
|
|
12472
|
+
* The caller only invokes this at message-loop depth 0 (see the call site):
|
|
12473
|
+
* inside a multi-turn tool loop the broadcast result already drives the next
|
|
12474
|
+
* turn, so blocking here would stack the poll budget per leg without feeding
|
|
12475
|
+
* the server any extra signal. Those deeper legs keep their honest `pending`.
|
|
12476
|
+
*/
|
|
12477
|
+
async emitAndConfirmTx(txHash, chain, explorerUrl, depth, ui) {
|
|
12478
|
+
ui.onTxStatus(txHash, chain || "", "pending", explorerUrl);
|
|
12479
|
+
if (depth === 0) {
|
|
12480
|
+
await this.confirmBroadcastedTx(txHash, chain, explorerUrl, ui);
|
|
12481
|
+
}
|
|
12482
|
+
}
|
|
12483
|
+
async confirmBroadcastedTx(txHash, chainName, explorerUrl, ui) {
|
|
12484
|
+
if (!this.config.askMode && !this.config.viaAgent) return;
|
|
12485
|
+
const chain = resolveChain(chainName ?? "");
|
|
12486
|
+
if (!chain || typeof this.vault?.getTxStatus !== "function") return;
|
|
12487
|
+
for (let attempt = 0; attempt < this.txConfirmMaxPolls; attempt++) {
|
|
12488
|
+
if (this.abortController?.signal?.aborted) return;
|
|
12489
|
+
try {
|
|
12490
|
+
const result = await this.vault.getTxStatus({ chain, txHash });
|
|
12491
|
+
if (result.status === "success") {
|
|
12492
|
+
ui.onTxStatus(txHash, chainName ?? "", "confirmed", explorerUrl);
|
|
12493
|
+
return;
|
|
12494
|
+
}
|
|
12495
|
+
if (result.status === "error") {
|
|
12496
|
+
ui.onTxStatus(txHash, chainName ?? "", "failed", explorerUrl);
|
|
12497
|
+
return;
|
|
12498
|
+
}
|
|
12499
|
+
} catch (err) {
|
|
12500
|
+
if (this.config.verbose) {
|
|
12501
|
+
process.stderr.write(`[session] tx confirm poll ${attempt + 1} failed: ${err?.message ?? err}
|
|
12502
|
+
`);
|
|
12503
|
+
}
|
|
12504
|
+
}
|
|
12505
|
+
if (attempt < this.txConfirmMaxPolls - 1) await this.txConfirmSleep();
|
|
12506
|
+
}
|
|
12507
|
+
if (this.abortController?.signal?.aborted) return;
|
|
12508
|
+
if (this.config.verbose) {
|
|
12509
|
+
process.stderr.write(
|
|
12510
|
+
`[session] tx ${txHash} not confirmed within ${this.txConfirmMaxPolls} polls; emitting timeout
|
|
12511
|
+
`
|
|
12512
|
+
);
|
|
12513
|
+
}
|
|
12514
|
+
ui.onTxStatus(txHash, chainName ?? "", "timeout", explorerUrl);
|
|
12515
|
+
}
|
|
12516
|
+
/** Sleep between confirmation polls. Separate method so tests can stub it out. */
|
|
12517
|
+
txConfirmSleep() {
|
|
12518
|
+
return new Promise((resolve) => setTimeout(resolve, this.txConfirmPollIntervalMs));
|
|
12519
|
+
}
|
|
12437
12520
|
/**
|
|
12438
12521
|
* Fold a recovered assistant message back into the live stream result: the
|
|
12439
12522
|
* authoritative message wins over any partial deltas, and any persisted
|
|
@@ -13283,7 +13366,7 @@ var cachedVersion = null;
|
|
|
13283
13366
|
function getVersion() {
|
|
13284
13367
|
if (cachedVersion) return cachedVersion;
|
|
13285
13368
|
if (true) {
|
|
13286
|
-
cachedVersion = "2.8.
|
|
13369
|
+
cachedVersion = "2.8.1";
|
|
13287
13370
|
return cachedVersion;
|
|
13288
13371
|
}
|
|
13289
13372
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vultisig/cli",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "The self-custody MPC wallet CLI for AI coding agents (Claude Code, Cursor, OpenCode). Natural-language agent mode, 36+ chains, DKLS23 threshold signatures. Seedless.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|