@cogcoin/client 0.5.13 → 0.5.14

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.
@@ -47,6 +47,7 @@ export async function runWalletMutationCommand(parsed, context) {
47
47
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
48
48
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
49
49
  const prompter = createCommandPrompter(parsed, context);
50
+ const interactive = prompter.isInteractive;
50
51
  if (isAnchorClearMutationCommand(parsed.command)) {
51
52
  const result = await context.clearPendingAnchor({
52
53
  domainName: parsed.args[0],
@@ -65,6 +66,7 @@ export async function runWalletMutationCommand(parsed, context) {
65
66
  previewData: buildAnchorClearPreviewData(result),
66
67
  reusedExisting: false,
67
68
  reusedMessage: "",
69
+ interactive,
68
70
  outcome: result.cleared ? "cleared" : "noop",
69
71
  nextSteps,
70
72
  text: {
@@ -83,6 +85,7 @@ export async function runWalletMutationCommand(parsed, context) {
83
85
  const result = await context.anchorDomain({
84
86
  domainName: parsed.args[0],
85
87
  foundingMessageText: parsed.anchorMessage,
88
+ promptForFoundingMessageWhenMissing: parsed.anchorMessage === null,
86
89
  dataDir,
87
90
  databasePath: dbPath,
88
91
  provider: context.walletSecretProvider,
@@ -92,13 +95,15 @@ export async function runWalletMutationCommand(parsed, context) {
92
95
  const nextSteps = getAnchorNextSteps(result.domainName);
93
96
  return writeMutationCommandSuccess(parsed, context, {
94
97
  data: buildAnchorMutationData(result, {
95
- foundingMessageText: parsed.anchorMessage,
98
+ foundingMessageText: result.foundingMessageText ?? parsed.anchorMessage,
96
99
  }),
97
100
  previewData: buildAnchorPreviewData(result, {
98
- foundingMessageText: parsed.anchorMessage,
101
+ foundingMessageText: result.foundingMessageText ?? parsed.anchorMessage,
99
102
  }),
100
103
  reusedExisting: result.reusedExisting,
101
104
  reusedMessage: "The existing anchor family was reconciled instead of creating a duplicate.",
105
+ interactive,
106
+ explorerTxid: result.tx2Txid,
102
107
  nextSteps: workflowMutationNextSteps(nextSteps),
103
108
  text: {
104
109
  heading: "Anchor family submitted.",
@@ -136,6 +141,8 @@ export async function runWalletMutationCommand(parsed, context) {
136
141
  }),
137
142
  reusedExisting: result.reusedExisting,
138
143
  reusedMessage: "The existing pending registration was reconciled instead of creating a duplicate.",
144
+ interactive,
145
+ explorerTxid: result.txid,
139
146
  nextSteps: workflowMutationNextSteps(nextSteps),
140
147
  text: {
141
148
  heading: "Registration submitted.",
@@ -171,6 +178,8 @@ export async function runWalletMutationCommand(parsed, context) {
171
178
  }),
172
179
  reusedExisting: result.reusedExisting,
173
180
  reusedMessage: "The existing pending transfer was reconciled instead of creating a duplicate.",
181
+ interactive,
182
+ explorerTxid: result.txid,
174
183
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
175
184
  text: {
176
185
  heading: "Transfer submitted.",
@@ -208,6 +217,8 @@ export async function runWalletMutationCommand(parsed, context) {
208
217
  }),
209
218
  reusedExisting: result.reusedExisting,
210
219
  reusedMessage: "The existing pending listing mutation was reconciled instead of creating a duplicate.",
220
+ interactive,
221
+ explorerTxid: result.txid,
211
222
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
212
223
  text: {
213
224
  heading: result.listedPriceCogtoshi === 0n ? "Listing cancellation submitted." : "Listing submitted.",
@@ -256,6 +267,8 @@ export async function runWalletMutationCommand(parsed, context) {
256
267
  }),
257
268
  reusedExisting: result.reusedExisting,
258
269
  reusedMessage: "The existing pending endpoint mutation was reconciled instead of creating a duplicate.",
270
+ interactive,
271
+ explorerTxid: result.txid,
259
272
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
260
273
  text: {
261
274
  heading: parsed.command === "domain-endpoint-set" ? "Endpoint update submitted." : "Endpoint clear submitted.",
@@ -300,6 +313,8 @@ export async function runWalletMutationCommand(parsed, context) {
300
313
  }),
301
314
  reusedExisting: result.reusedExisting,
302
315
  reusedMessage: "The existing pending delegate mutation was reconciled instead of creating a duplicate.",
316
+ interactive,
317
+ explorerTxid: result.txid,
303
318
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
304
319
  text: {
305
320
  heading: parsed.command === "domain-delegate-set" ? "Delegate update submitted." : "Delegate clear submitted.",
@@ -344,6 +359,8 @@ export async function runWalletMutationCommand(parsed, context) {
344
359
  }),
345
360
  reusedExisting: result.reusedExisting,
346
361
  reusedMessage: "The existing pending miner mutation was reconciled instead of creating a duplicate.",
362
+ interactive,
363
+ explorerTxid: result.txid,
347
364
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
348
365
  text: {
349
366
  heading: parsed.command === "domain-miner-set" ? "Miner update submitted." : "Miner clear submitted.",
@@ -377,6 +394,8 @@ export async function runWalletMutationCommand(parsed, context) {
377
394
  }),
378
395
  reusedExisting: result.reusedExisting,
379
396
  reusedMessage: "The existing pending canonical mutation was reconciled instead of creating a duplicate.",
397
+ interactive,
398
+ explorerTxid: result.txid,
380
399
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
381
400
  text: {
382
401
  heading: "Canonical update submitted.",
@@ -415,6 +434,8 @@ export async function runWalletMutationCommand(parsed, context) {
415
434
  reusedMessage: result.family
416
435
  ? "The existing pending field family was reconciled instead of creating a duplicate."
417
436
  : "The existing pending field creation was reconciled instead of creating a duplicate.",
437
+ interactive,
438
+ explorerTxid: result.family ? (result.tx2Txid ?? null) : result.txid,
418
439
  nextSteps: commandMutationNextSteps(`cogcoin field show ${result.domainName} ${result.fieldName}`),
419
440
  text: {
420
441
  heading: result.family ? "Field create+write family submitted." : "Field creation submitted.",
@@ -450,6 +471,8 @@ export async function runWalletMutationCommand(parsed, context) {
450
471
  previewData: buildFieldPreviewData(result),
451
472
  reusedExisting: result.reusedExisting,
452
473
  reusedMessage: "The existing pending field update was reconciled instead of creating a duplicate.",
474
+ interactive,
475
+ explorerTxid: result.txid,
453
476
  nextSteps: commandMutationNextSteps(`cogcoin field show ${result.domainName} ${result.fieldName}`),
454
477
  text: {
455
478
  heading: "Field update submitted.",
@@ -481,6 +504,8 @@ export async function runWalletMutationCommand(parsed, context) {
481
504
  previewData: buildFieldPreviewData(result),
482
505
  reusedExisting: result.reusedExisting,
483
506
  reusedMessage: "The existing pending field clear was reconciled instead of creating a duplicate.",
507
+ interactive,
508
+ explorerTxid: result.txid,
484
509
  nextSteps: commandMutationNextSteps(`cogcoin field show ${result.domainName} ${result.fieldName}`),
485
510
  text: {
486
511
  heading: "Field clear submitted.",
@@ -518,6 +543,8 @@ export async function runWalletMutationCommand(parsed, context) {
518
543
  }),
519
544
  reusedExisting: result.reusedExisting,
520
545
  reusedMessage: "The existing pending COG transfer was reconciled instead of creating a duplicate.",
546
+ interactive,
547
+ explorerTxid: result.txid,
521
548
  nextSteps: commandMutationNextSteps("cogcoin balance"),
522
549
  text: {
523
550
  heading: "COG transfer submitted.",
@@ -563,6 +590,8 @@ export async function runWalletMutationCommand(parsed, context) {
563
590
  }),
564
591
  reusedExisting: result.reusedExisting,
565
592
  reusedMessage: "The existing pending lock was reconciled instead of creating a duplicate.",
593
+ interactive,
594
+ explorerTxid: result.txid,
566
595
  nextSteps: commandMutationNextSteps("cogcoin locks"),
567
596
  text: {
568
597
  heading: "COG lock submitted.",
@@ -597,6 +626,8 @@ export async function runWalletMutationCommand(parsed, context) {
597
626
  }),
598
627
  reusedExisting: result.reusedExisting,
599
628
  reusedMessage: "The existing pending claim was reconciled instead of creating a duplicate.",
629
+ interactive,
630
+ explorerTxid: result.txid,
600
631
  nextSteps: commandMutationNextSteps("cogcoin locks --claimable"),
601
632
  text: {
602
633
  heading: "Lock claim submitted.",
@@ -631,6 +662,8 @@ export async function runWalletMutationCommand(parsed, context) {
631
662
  }),
632
663
  reusedExisting: result.reusedExisting,
633
664
  reusedMessage: "The existing pending reclaim was reconciled instead of creating a duplicate.",
665
+ interactive,
666
+ explorerTxid: result.txid,
634
667
  nextSteps: commandMutationNextSteps("cogcoin locks --reclaimable"),
635
668
  text: {
636
669
  heading: "Lock reclaim submitted.",
@@ -676,6 +709,8 @@ export async function runWalletMutationCommand(parsed, context) {
676
709
  previewData: buildReputationPreviewData(result),
677
710
  reusedExisting: result.reusedExisting,
678
711
  reusedMessage: "The existing pending reputation mutation was reconciled instead of creating a duplicate.",
712
+ interactive,
713
+ explorerTxid: result.txid,
679
714
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.targetDomainName}`),
680
715
  text: {
681
716
  heading: parsed.command === "rep-give" ? "Reputation support submitted." : "Reputation revoke submitted.",
@@ -714,6 +749,8 @@ export async function runWalletMutationCommand(parsed, context) {
714
749
  }),
715
750
  reusedExisting: result.reusedExisting,
716
751
  reusedMessage: "The existing pending purchase was reconciled instead of creating a duplicate.",
752
+ interactive,
753
+ explorerTxid: result.txid,
717
754
  nextSteps: commandMutationNextSteps(`cogcoin show ${result.domainName}`),
718
755
  text: {
719
756
  heading: "Purchase submitted.",
@@ -17,9 +17,26 @@ import { createLazyDefaultWalletSecretProvider } from "../wallet/state/provider.
17
17
  import { anchorDomain, clearPendingAnchor, buyDomain, claimCogLock, clearDomainDelegate, clearDomainEndpoint, clearDomainMiner, clearField, createField, giveReputation, lockCogToDomain, registerDomain, reclaimCogLock, revokeReputation, sendCog, setField, setDomainCanonical, setDomainDelegate, setDomainEndpoint, setDomainMiner, sellDomain, transferDomain, } from "../wallet/tx/index.js";
18
18
  import { createTerminalPrompter } from "./prompt.js";
19
19
  export async function readPackageVersionFromDisk() {
20
- const raw = await readFile(new URL("../../package.json", import.meta.url), "utf8");
21
- const parsed = JSON.parse(raw);
22
- return parsed.version ?? "0.0.0";
20
+ const packageUrls = [
21
+ new URL("../../package.json", import.meta.url),
22
+ new URL("../../../package.json", import.meta.url),
23
+ ];
24
+ for (const packageUrl of packageUrls) {
25
+ try {
26
+ const raw = await readFile(packageUrl, "utf8");
27
+ const parsed = JSON.parse(raw);
28
+ return parsed.version ?? "0.0.0";
29
+ }
30
+ catch (error) {
31
+ const code = typeof error === "object" && error !== null && "code" in error
32
+ ? String(error.code)
33
+ : null;
34
+ if (code !== "ENOENT") {
35
+ throw error;
36
+ }
37
+ }
38
+ }
39
+ return "0.0.0";
23
40
  }
24
41
  export function createDefaultContext(overrides = {}) {
25
42
  return {
@@ -11,6 +11,8 @@ export declare function writeMutationCommandSuccess(parsed: ParsedCliArgs, conte
11
11
  previewData?: unknown;
12
12
  reusedExisting: boolean;
13
13
  reusedMessage: string;
14
+ interactive?: boolean;
15
+ explorerTxid?: string | null;
14
16
  nextSteps: MutationSuccessNextSteps;
15
17
  outcome?: string;
16
18
  text: {
@@ -42,6 +42,8 @@ export function writeMutationCommandSuccess(parsed, context, options) {
42
42
  reusedExisting: options.reusedExisting,
43
43
  reusedMessage: options.reusedMessage,
44
44
  trailerLines: options.nextSteps.text,
45
+ interactive: options.interactive,
46
+ explorerTxid: options.explorerTxid,
45
47
  });
46
48
  return 0;
47
49
  }
@@ -10,4 +10,6 @@ export declare function writeMutationTextResult(stream: WritableLike, options: {
10
10
  reusedExisting?: boolean;
11
11
  reusedMessage?: string;
12
12
  trailerLines?: string[];
13
+ interactive?: boolean;
14
+ explorerTxid?: string | null;
13
15
  }): void;
@@ -1,4 +1,7 @@
1
1
  import { writeLine } from "./io.js";
2
+ function mutationExplorerUrl(txid) {
3
+ return `https://mempool.space/tx/${txid}`;
4
+ }
2
5
  export function writeMutationTextResult(stream, options) {
3
6
  writeLine(stream, options.heading);
4
7
  for (const field of options.fields) {
@@ -13,4 +16,8 @@ export function writeMutationTextResult(stream, options) {
13
16
  for (const line of options.trailerLines ?? []) {
14
17
  writeLine(stream, line);
15
18
  }
19
+ if (options.interactive === true && options.explorerTxid) {
20
+ writeLine(stream, "");
21
+ writeLine(stream, `View at: ${mutationExplorerUrl(options.explorerTxid)}`);
22
+ }
16
23
  }
@@ -188,7 +188,8 @@ export function classifyCliError(error) {
188
188
  }
189
189
  function isBlockedError(message) {
190
190
  if (message === "wallet_control_lock_busy"
191
- || message.startsWith("file_lock_busy_")) {
191
+ || message.startsWith("file_lock_busy_")
192
+ || message.startsWith("wallet_anchor_clear_pending_first_")) {
192
193
  return true;
193
194
  }
194
195
  if (message === "wallet_locked"
@@ -439,6 +440,14 @@ export function createCliErrorPresentation(errorCode, fallbackMessage) {
439
440
  next: "Run `cogcoin repair`, then inspect the domain again before retrying `cogcoin anchor clear`.",
440
441
  };
441
442
  }
443
+ if (errorCode.startsWith("wallet_anchor_clear_pending_first_")) {
444
+ const domainName = errorCode.slice("wallet_anchor_clear_pending_first_".length) || "<domain>";
445
+ return {
446
+ what: `A local pending anchor already exists for "${domainName}".`,
447
+ why: "The wallet found a same-domain anchor reservation that is still local-only and safely clearable, so it stopped before creating a conflicting family.",
448
+ next: `Run \`cogcoin anchor clear ${domainName}\`, then rerun \`cogcoin anchor ${domainName}\`.`,
449
+ };
450
+ }
442
451
  if (errorCode.startsWith("wallet_anchor_clear_not_clearable_")) {
443
452
  return {
444
453
  what: "Pending anchor cannot be cleared safely.",
@@ -488,6 +497,18 @@ export function createCliErrorPresentation(errorCode, fallbackMessage) {
488
497
  next: "Rerun the command and confirm it. If this command uses a plain yes/no path, you can also add `--yes`.",
489
498
  };
490
499
  }
500
+ if (errorCode === "wallet_anchor_invalid_message" || errorCode.startsWith("wallet_anchor_invalid_message_")) {
501
+ const reason = errorCode.startsWith("wallet_anchor_invalid_message_")
502
+ ? errorCode.slice("wallet_anchor_invalid_message_".length).trim()
503
+ : null;
504
+ return {
505
+ what: "Founding message cannot be encoded in canonical Coglex.",
506
+ why: reason === null || reason === ""
507
+ ? "The supplied founding message could not be encoded into the canonical on-chain Coglex sentence format."
508
+ : reason,
509
+ next: "Retry with a different founding message, or rerun `cogcoin anchor <domain>` without `--message` to skip it.",
510
+ };
511
+ }
491
512
  if (errorCode === "wallet_prompt_value_required") {
492
513
  return {
493
514
  what: "Required input was not provided.",
@@ -1,5 +1,6 @@
1
1
  import type { inspectPassiveClientStatus } from "../passive-status.js";
2
2
  import { createRpcClient } from "../bitcoind/node.js";
3
+ import type { ManagedBitcoindProgressEvent } from "../bitcoind/types.js";
3
4
  import { attachOrStartIndexerDaemon, probeIndexerDaemon, readObservedIndexerDaemonStatus, stopIndexerDaemonService } from "../bitcoind/indexer-daemon.js";
4
5
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopManagedBitcoindService } from "../bitcoind/service.js";
5
6
  import { openSqliteStore } from "../sqlite/index.js";
@@ -99,6 +100,7 @@ export interface CliRunnerContext {
99
100
  dataDir?: string;
100
101
  walletRootId?: string;
101
102
  progressOutput?: ProgressOutput;
103
+ onProgress?: (event: ManagedBitcoindProgressEvent) => void;
102
104
  confirmGetblockArchiveRestart?: (options: {
103
105
  currentArchiveEndHeight: number | null;
104
106
  nextArchiveEndHeight: number;
@@ -1,7 +1,7 @@
1
1
  import type { WalletDomainView, WalletReadContext } from "../wallet/read/index.js";
2
2
  export declare function getRepairRecommendation(context: WalletReadContext): string | null;
3
3
  export declare function getMutationRecommendation(context: WalletReadContext): string | null;
4
- export declare function formatWalletOverviewReport(context: WalletReadContext): string;
4
+ export declare function formatWalletOverviewReport(context: WalletReadContext, version: string): string;
5
5
  export declare function formatDetailedWalletStatusReport(context: WalletReadContext): string;
6
6
  export declare function formatFundingAddressReport(context: WalletReadContext): string;
7
7
  export declare function formatIdentityListReport(context: WalletReadContext, options?: {
@@ -459,9 +459,9 @@ function getOverviewNextStep(context) {
459
459
  }
460
460
  return getMutationRecommendation(context);
461
461
  }
462
- export function formatWalletOverviewReport(context) {
462
+ export function formatWalletOverviewReport(context, version) {
463
463
  const parts = [
464
- "\n⛭ Cogcoin Status ⛭",
464
+ `\n⛭ Cogcoin Status v${version} ⛭`,
465
465
  formatOverviewSection("Paths", buildOverviewPathsSection(context)),
466
466
  formatOverviewSection("Wallet", buildOverviewWalletSection(context)),
467
467
  formatOverviewSection("Services", buildOverviewServicesSection(context)),
@@ -2,7 +2,7 @@ import type { RpcListUnspentEntry, RpcLockedUnspent } from "../bitcoind/types.js
2
2
  import { persistWalletStateUpdate } from "./descriptor-normalization.js";
3
3
  import type { WalletRuntimePaths } from "./runtime.js";
4
4
  import type { OutpointRecord, PortableWalletArchivePayloadV1, UnlockSessionStateV1, WalletStateV1 } from "./types.js";
5
- export declare const DEFAULT_PROACTIVE_RESERVE_SATS = 50000;
5
+ export declare const DEFAULT_PROACTIVE_RESERVE_SATS = 1000;
6
6
  export interface WalletCoinControlRpc {
7
7
  listUnspent(walletName: string, minConf?: number): Promise<RpcListUnspentEntry[]>;
8
8
  listLockUnspent(walletName: string): Promise<RpcLockedUnspent[]>;
@@ -1,7 +1,7 @@
1
1
  import { saveUnlockSession } from "./state/session.js";
2
2
  import { persistWalletStateUpdate } from "./descriptor-normalization.js";
3
3
  import { miningFamilyMayStillExist } from "./mining/state.js";
4
- export const DEFAULT_PROACTIVE_RESERVE_SATS = 50_000;
4
+ export const DEFAULT_PROACTIVE_RESERVE_SATS = 1_000;
5
5
  function btcNumberToSats(value) {
6
6
  return BigInt(Math.round(value * 100_000_000));
7
7
  }
@@ -33,7 +33,11 @@ function normalizeReserveSats(raw) {
33
33
  if (typeof raw !== "number" || !Number.isFinite(raw)) {
34
34
  return DEFAULT_PROACTIVE_RESERVE_SATS;
35
35
  }
36
- return Math.max(0, Math.trunc(raw));
36
+ const normalized = Math.max(0, Math.trunc(raw));
37
+ if (normalized === 0) {
38
+ return 0;
39
+ }
40
+ return DEFAULT_PROACTIVE_RESERVE_SATS;
37
41
  }
38
42
  function sameOutpointList(left, right) {
39
43
  if (left.length !== (right?.length ?? 0)) {
@@ -42,8 +46,12 @@ function sameOutpointList(left, right) {
42
46
  return left.every((outpoint, index) => outpoint.txid === right?.[index]?.txid && outpoint.vout === right?.[index]?.vout);
43
47
  }
44
48
  export function normalizeWalletStateRecord(state) {
45
- const proactiveReserveSats = normalizeReserveSats(state.proactiveReserveSats);
46
- const proactiveReserveOutpoints = normalizeOutpointRecordList(state.proactiveReserveOutpoints);
49
+ const rawProactiveReserveSats = state.proactiveReserveSats;
50
+ const proactiveReserveSats = normalizeReserveSats(rawProactiveReserveSats);
51
+ const reserveValueChanged = proactiveReserveSats !== rawProactiveReserveSats;
52
+ const proactiveReserveOutpoints = normalizeOutpointRecordList(proactiveReserveSats <= 0 || reserveValueChanged
53
+ ? []
54
+ : state.proactiveReserveOutpoints);
47
55
  const pendingMutations = state.pendingMutations ?? [];
48
56
  if (proactiveReserveSats === state.proactiveReserveSats
49
57
  && sameOutpointList(proactiveReserveOutpoints, state.proactiveReserveOutpoints)
@@ -58,8 +66,12 @@ export function normalizeWalletStateRecord(state) {
58
66
  };
59
67
  }
60
68
  export function normalizePortableWalletArchivePayload(payload) {
61
- const proactiveReserveSats = normalizeReserveSats(payload.proactiveReserveSats);
62
- const proactiveReserveOutpoints = normalizeOutpointRecordList(payload.proactiveReserveOutpoints);
69
+ const rawProactiveReserveSats = payload.proactiveReserveSats;
70
+ const proactiveReserveSats = normalizeReserveSats(rawProactiveReserveSats);
71
+ const reserveValueChanged = proactiveReserveSats !== rawProactiveReserveSats;
72
+ const proactiveReserveOutpoints = normalizeOutpointRecordList(proactiveReserveSats <= 0 || reserveValueChanged
73
+ ? []
74
+ : payload.proactiveReserveOutpoints);
63
75
  if (proactiveReserveSats === payload.proactiveReserveSats
64
76
  && sameOutpointList(proactiveReserveOutpoints, payload.proactiveReserveOutpoints)) {
65
77
  return payload;
@@ -161,6 +173,9 @@ export function computeDesignatedProactiveReserveOutpoints(state, spendableUtxos
161
173
  break;
162
174
  }
163
175
  }
176
+ if (total < target) {
177
+ return [];
178
+ }
164
179
  return selected;
165
180
  }
166
181
  function syncStateWithComputedReserve(state, spendableUtxos) {
@@ -275,8 +290,11 @@ function collectPersistentPolicyLockedOutpoints(state, spendableUtxos) {
275
290
  return outpoints;
276
291
  }
277
292
  export async function reconcilePersistentPolicyLocks(options) {
293
+ const rawReserveOutpoints = normalizeOutpointRecordList(options.state.proactiveReserveOutpoints);
278
294
  let state = normalizeWalletStateRecord(options.state);
279
295
  let changed = state !== options.state;
296
+ const fixedInputKeys = new Set((options.fixedInputs ?? []).map((outpoint) => outpointKey(outpoint)));
297
+ const temporarilyUnlockedKeys = new Set((options.temporarilyUnlockedOutpoints ?? []).map((outpoint) => outpointKey(outpoint)));
280
298
  if (options.cleanupInactiveTemporaryBuilderLocks === true) {
281
299
  const cleaned = collectInactiveTemporaryBuilderLockCleanup(state);
282
300
  state = cleaned.state;
@@ -285,12 +303,24 @@ export async function reconcilePersistentPolicyLocks(options) {
285
303
  await options.rpc.lockUnspent(options.walletName, true, cleaned.staleOutpoints).catch(() => undefined);
286
304
  }
287
305
  }
288
- const spendableUtxos = options.spendableUtxos ?? await options.rpc.listUnspent(options.walletName, 0).catch(() => []);
306
+ const lockedBeforeReserveInspection = await options.rpc.listLockUnspent(options.walletName).catch(() => []);
307
+ const lockedBeforeReserveInspectionKeys = new Set(lockedBeforeReserveInspection.map((outpoint) => outpointKey(outpoint)));
308
+ const reserveInspectionUnlocks = rawReserveOutpoints.filter((outpoint) => {
309
+ const key = outpointKey(outpoint);
310
+ return lockedBeforeReserveInspectionKeys.has(key) && !fixedInputKeys.has(key);
311
+ });
312
+ if (reserveInspectionUnlocks.length > 0) {
313
+ await options.rpc.lockUnspent(options.walletName, true, reserveInspectionUnlocks).catch(() => undefined);
314
+ }
315
+ const spendableUtxos = reserveInspectionUnlocks.length > 0 || options.spendableUtxos === undefined
316
+ ? await options.rpc.listUnspent(options.walletName, 0).catch(() => [])
317
+ : options.spendableUtxos;
318
+ const previouslyProtectedUniverse = collectPersistentPolicyLockedOutpoints(state, spendableUtxos);
289
319
  const reserveSynced = syncStateWithComputedReserve(state, spendableUtxos);
290
320
  state = reserveSynced.state;
291
321
  changed ||= reserveSynced.changed;
292
322
  const protectedUniverse = collectPersistentPolicyLockedOutpoints(state, spendableUtxos);
293
- if (protectedUniverse.length === 0) {
323
+ if (protectedUniverse.length === 0 && previouslyProtectedUniverse.length === 0) {
294
324
  return {
295
325
  state,
296
326
  changed,
@@ -298,19 +328,30 @@ export async function reconcilePersistentPolicyLocks(options) {
298
328
  };
299
329
  }
300
330
  const protectedUniverseKeys = new Set(protectedUniverse.map((outpoint) => outpointKey(outpoint)));
301
- const fixedInputKeys = new Set((options.fixedInputs ?? []).map((outpoint) => outpointKey(outpoint)));
302
- const temporarilyUnlockedKeys = new Set((options.temporarilyUnlockedOutpoints ?? []).map((outpoint) => outpointKey(outpoint)));
331
+ const previouslyProtectedUniverseKeys = new Set(previouslyProtectedUniverse.map((outpoint) => outpointKey(outpoint)));
332
+ const managedProtectedKeys = new Set([
333
+ ...protectedUniverseKeys,
334
+ ...previouslyProtectedUniverseKeys,
335
+ ]);
303
336
  const locked = await options.rpc.listLockUnspent(options.walletName).catch(() => []);
304
337
  const spendableKeys = new Set(spendableUtxos.map((entry) => outpointKey(entry)));
338
+ const lockedKeys = new Set(locked.map((outpoint) => outpointKey(outpoint)));
305
339
  const expectedLocked = protectedUniverse.filter((outpoint) => {
306
340
  const key = outpointKey(outpoint);
307
- return spendableKeys.has(key) && !fixedInputKeys.has(key) && !temporarilyUnlockedKeys.has(key);
341
+ return (spendableKeys.has(key) || lockedKeys.has(key))
342
+ && !fixedInputKeys.has(key)
343
+ && !temporarilyUnlockedKeys.has(key);
308
344
  });
309
345
  const expectedLockedKeys = new Set(expectedLocked.map((outpoint) => outpointKey(outpoint)));
310
- const lockedProtected = locked.filter((outpoint) => protectedUniverseKeys.has(outpointKey(outpoint)));
311
- const lockedProtectedKeys = new Set(lockedProtected.map((outpoint) => outpointKey(outpoint)));
312
- const staleLocked = lockedProtected.filter((outpoint) => !expectedLockedKeys.has(outpointKey(outpoint)) || !spendableKeys.has(outpointKey(outpoint)));
313
- const missingLocked = expectedLocked.filter((outpoint) => !lockedProtectedKeys.has(outpointKey(outpoint)));
346
+ const lockedManaged = locked.filter((outpoint) => managedProtectedKeys.has(outpointKey(outpoint)));
347
+ const staleLocked = lockedManaged.filter((outpoint) => !expectedLockedKeys.has(outpointKey(outpoint)));
348
+ const missingLocked = protectedUniverse.filter((outpoint) => {
349
+ const key = outpointKey(outpoint);
350
+ return spendableKeys.has(key)
351
+ && !fixedInputKeys.has(key)
352
+ && !temporarilyUnlockedKeys.has(key)
353
+ && !lockedKeys.has(key);
354
+ });
314
355
  if (staleLocked.length > 0) {
315
356
  await options.rpc.lockUnspent(options.walletName, true, staleLocked).catch(() => undefined);
316
357
  }
@@ -18,6 +18,7 @@ interface WalletAnchorRpcClient extends WalletMutationRpcClient {
18
18
  export interface AnchorDomainOptions {
19
19
  domainName: string;
20
20
  foundingMessageText?: string | null;
21
+ promptForFoundingMessageWhenMissing?: boolean;
21
22
  dataDir: string;
22
23
  databasePath: string;
23
24
  provider?: WalletSecretProvider;
@@ -36,6 +37,7 @@ export interface AnchorDomainResult {
36
37
  dedicatedIndex: number;
37
38
  status: "live" | "confirmed";
38
39
  reusedExisting: boolean;
40
+ foundingMessageText?: string | null;
39
41
  }
40
42
  export interface ClearPendingAnchorOptions {
41
43
  domainName: string;