@cogcoin/client 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +3 -2
  2. package/dist/bitcoind/client/factory.d.ts +0 -8
  3. package/dist/bitcoind/client/factory.js +1 -59
  4. package/dist/bitcoind/client/managed-client.d.ts +1 -3
  5. package/dist/bitcoind/client/managed-client.js +3 -47
  6. package/dist/bitcoind/indexer-daemon-main.js +173 -28
  7. package/dist/bitcoind/indexer-daemon.d.ts +11 -3
  8. package/dist/bitcoind/indexer-daemon.js +123 -57
  9. package/dist/bitcoind/indexer-monitor.d.ts +12 -0
  10. package/dist/bitcoind/indexer-monitor.js +89 -0
  11. package/dist/bitcoind/progress/follow-scene.d.ts +7 -1
  12. package/dist/bitcoind/progress/follow-scene.js +87 -4
  13. package/dist/bitcoind/progress/tty-renderer.d.ts +2 -0
  14. package/dist/bitcoind/progress/tty-renderer.js +2 -0
  15. package/dist/bitcoind/testing.d.ts +0 -1
  16. package/dist/bitcoind/testing.js +0 -1
  17. package/dist/bitcoind/types.d.ts +5 -2
  18. package/dist/cli/commands/follow.js +44 -49
  19. package/dist/cli/commands/mining-admin.js +56 -2
  20. package/dist/cli/commands/mining-read.js +43 -3
  21. package/dist/cli/commands/mining-runtime.js +91 -73
  22. package/dist/cli/commands/service-runtime.js +42 -2
  23. package/dist/cli/commands/status.js +3 -1
  24. package/dist/cli/commands/sync.js +50 -90
  25. package/dist/cli/commands/wallet-admin.js +21 -3
  26. package/dist/cli/commands/wallet-read.js +2 -0
  27. package/dist/cli/context.js +5 -1
  28. package/dist/cli/managed-indexer-observer.d.ts +33 -0
  29. package/dist/cli/managed-indexer-observer.js +163 -0
  30. package/dist/cli/mining-format.d.ts +3 -1
  31. package/dist/cli/mining-format.js +35 -0
  32. package/dist/cli/mining-json.d.ts +11 -1
  33. package/dist/cli/mining-json.js +9 -0
  34. package/dist/cli/output.js +24 -0
  35. package/dist/cli/parse.d.ts +1 -1
  36. package/dist/cli/parse.js +23 -0
  37. package/dist/cli/read-json.d.ts +13 -1
  38. package/dist/cli/read-json.js +31 -0
  39. package/dist/cli/runner.js +4 -2
  40. package/dist/cli/signals.d.ts +12 -0
  41. package/dist/cli/signals.js +31 -13
  42. package/dist/cli/types.d.ts +8 -4
  43. package/dist/cli/update-service.d.ts +2 -12
  44. package/dist/cli/update-service.js +2 -68
  45. package/dist/semver.d.ts +12 -0
  46. package/dist/semver.js +68 -0
  47. package/dist/wallet/lifecycle.js +0 -6
  48. package/dist/wallet/mining/config.js +54 -3
  49. package/dist/wallet/mining/control.d.ts +5 -2
  50. package/dist/wallet/mining/control.js +153 -34
  51. package/dist/wallet/mining/domain-prompts.d.ts +17 -0
  52. package/dist/wallet/mining/domain-prompts.js +130 -0
  53. package/dist/wallet/mining/index.d.ts +2 -1
  54. package/dist/wallet/mining/index.js +1 -0
  55. package/dist/wallet/mining/runner.d.ts +58 -2
  56. package/dist/wallet/mining/runner.js +553 -331
  57. package/dist/wallet/mining/sentence-protocol.d.ts +1 -0
  58. package/dist/wallet/mining/sentences.js +7 -4
  59. package/dist/wallet/mining/types.d.ts +26 -0
  60. package/dist/wallet/mining/visualizer.d.ts +3 -0
  61. package/dist/wallet/mining/visualizer.js +106 -12
  62. package/dist/wallet/read/context.d.ts +1 -0
  63. package/dist/wallet/read/context.js +15 -10
  64. package/dist/wallet/reset.js +0 -1
  65. package/dist/wallet/state/client-password-agent.js +4 -1
  66. package/dist/wallet/state/client-password.js +15 -8
  67. package/dist/wallet/tx/anchor.js +0 -1
  68. package/dist/wallet/tx/bitcoin-transfer.js +0 -1
  69. package/dist/wallet/tx/cog.js +0 -3
  70. package/dist/wallet/tx/common.js +1 -1
  71. package/dist/wallet/tx/domain-admin.js +0 -1
  72. package/dist/wallet/tx/domain-market.js +0 -3
  73. package/dist/wallet/tx/field.js +0 -1
  74. package/dist/wallet/tx/register.js +0 -1
  75. package/dist/wallet/tx/reputation.js +0 -1
  76. package/package.json +1 -1
@@ -16,6 +16,7 @@ export interface MiningSentenceGenerationRequestV1 {
16
16
  domainId: number;
17
17
  domainName: string;
18
18
  requiredWords: [string, string, string, string, string];
19
+ extraPrompt: string | null;
19
20
  }>;
20
21
  }
21
22
  export interface MiningSentenceCandidateV1 {
@@ -25,9 +25,13 @@ function buildSystemPrompt(extraPrompt) {
25
25
  "Every sentence must be a single natural-language sentence.",
26
26
  "Do not add commentary, markdown, or code fences.",
27
27
  "Do not invent domain IDs or request IDs.",
28
+ "Each rootDomains entry may include an extraPrompt that applies only to that domain.",
29
+ "If rootDomains[i].extraPrompt is present, use it only for candidates for that domainId.",
30
+ "If rootDomains[i].extraPrompt is null, fall back to the request-level extraPrompt when it is present.",
31
+ "Never apply one domain's prompt to another domain's candidates.",
28
32
  ];
29
33
  if (extraPrompt !== null && extraPrompt.trim().length > 0) {
30
- lines.push(`Extra instruction: ${extraPrompt.trim()}`);
34
+ lines.push(`Request-level fallback instruction: ${extraPrompt.trim()}`);
31
35
  }
32
36
  return lines.join("\n");
33
37
  }
@@ -86,7 +90,7 @@ async function requestBuiltInSentences(options) {
86
90
  input: [
87
91
  {
88
92
  role: "system",
89
- content: buildSystemPrompt(options.extraPrompt),
93
+ content: buildSystemPrompt(options.request.extraPrompt),
90
94
  },
91
95
  {
92
96
  role: "user",
@@ -133,7 +137,7 @@ async function requestBuiltInSentences(options) {
133
137
  body: JSON.stringify({
134
138
  model,
135
139
  max_tokens: 1_200,
136
- system: buildSystemPrompt(options.extraPrompt),
140
+ system: buildSystemPrompt(options.request.extraPrompt),
137
141
  messages: [
138
142
  {
139
143
  role: "user",
@@ -245,7 +249,6 @@ export async function generateMiningSentences(request, options) {
245
249
  provider: builtIn.provider,
246
250
  apiKey: builtIn.apiKey,
247
251
  modelOverride: builtIn.modelOverride,
248
- extraPrompt: builtIn.extraPrompt ?? request.extraPrompt,
249
252
  request,
250
253
  fetchImpl: options.fetchImpl,
251
254
  signal: options.signal,
@@ -10,12 +10,38 @@ export interface MiningProviderConfigRecord {
10
10
  modelSelectionSource: MiningModelSelectionSource;
11
11
  updatedAtUnixMs: number;
12
12
  }
13
+ export type MiningProviderConfigByProvider = Partial<Record<MiningProviderKind, MiningProviderConfigRecord>>;
13
14
  export interface ClientConfigV1 {
14
15
  schemaVersion: 1;
15
16
  mining: {
16
17
  builtIn: MiningProviderConfigRecord | null;
18
+ builtInByProvider?: MiningProviderConfigByProvider;
19
+ domainExtraPrompts: Record<string, string>;
17
20
  };
18
21
  }
22
+ export interface MiningDomainPromptEntry {
23
+ domain: {
24
+ name: string;
25
+ domainId: number | null;
26
+ };
27
+ mineable: boolean;
28
+ prompt: string | null;
29
+ effectivePromptSource: "domain" | "global-fallback" | "none";
30
+ }
31
+ export interface MiningDomainPromptListResult {
32
+ fallbackPromptConfigured: boolean;
33
+ prompts: MiningDomainPromptEntry[];
34
+ }
35
+ export interface MiningDomainPromptMutationResult {
36
+ domain: {
37
+ name: string;
38
+ domainId: number | null;
39
+ };
40
+ previousPrompt: string | null;
41
+ prompt: string | null;
42
+ status: "updated" | "cleared";
43
+ fallbackPromptConfigured: boolean;
44
+ }
19
45
  export interface MiningEventRecord {
20
46
  schemaVersion: 1;
21
47
  timestampUnixMs: number;
@@ -11,6 +11,7 @@ export interface MiningSentenceBoardEntry {
11
11
  rank: number;
12
12
  domainName: string;
13
13
  sentence: string;
14
+ requiredWords: readonly string[];
14
15
  }
15
16
  export interface MiningProvisionalSentenceEntry {
16
17
  domainName: string | null;
@@ -44,6 +45,8 @@ export declare class MiningFollowVisualizer {
44
45
  platform?: NodeJS.Platform;
45
46
  env?: NodeJS.ProcessEnv;
46
47
  clock?: RenderClock;
48
+ clientVersion?: string | null;
49
+ updateAvailable?: boolean;
47
50
  rendererFactory?: (stream: TtyRenderStream) => VisualizerRendererLike;
48
51
  });
49
52
  update(snapshot: MiningRuntimeStatusV1, uiState?: MiningFollowVisualizerState): void;
@@ -1,9 +1,11 @@
1
1
  import { createBootstrapProgress } from "../../bitcoind/progress/formatting.js";
2
+ import { normalizeInlineText, truncateLine } from "../../bitcoind/progress/formatting.js";
2
3
  import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
3
4
  import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
4
5
  import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
5
6
  const MINING_ARTWORK_COG_WIDTH = 22;
6
7
  const MINING_SENTENCE_BOARD_SIZE = 5;
8
+ const MINING_SENTENCE_BOARD_WRAP_WIDTH = 80;
7
9
  function formatCogAmountWithDecimals(value, { maxFractionDigits, minFractionDigits, }) {
8
10
  const sign = value < 0n ? "-" : "";
9
11
  const absolute = value < 0n ? -value : value;
@@ -44,14 +46,86 @@ function formatCompactCogBalanceText(balanceCogtoshi) {
44
46
  function formatCompactSatBalanceText(balanceSats) {
45
47
  return balanceSats === null ? null : `${balanceSats.toString()} SAT`;
46
48
  }
49
+ function formatArtworkVersionText(clientVersion) {
50
+ if (typeof clientVersion !== "string") {
51
+ return null;
52
+ }
53
+ const normalizedVersion = normalizeInlineText(clientVersion);
54
+ if (normalizedVersion.length === 0) {
55
+ return null;
56
+ }
57
+ return normalizedVersion.startsWith("v")
58
+ ? normalizedVersion
59
+ : `v${normalizedVersion}`;
60
+ }
47
61
  function formatRewardCogAmount(value) {
48
62
  return `${formatCogAmountWithDecimals(value, {
49
63
  maxFractionDigits: 8,
50
64
  minFractionDigits: 1,
51
65
  })} COG`;
52
66
  }
53
- function formatSentenceRow(rank, domainName, sentence) {
54
- return `${rank}. @${domainName}: ${sentence}`;
67
+ function escapeRegExp(value) {
68
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
69
+ }
70
+ function consumeWrappedLine(text, capacity) {
71
+ const remaining = text.trimStart();
72
+ if (remaining.length <= capacity) {
73
+ return {
74
+ line: remaining,
75
+ remaining: "",
76
+ };
77
+ }
78
+ const candidate = remaining.slice(0, capacity + 1);
79
+ const breakIndex = candidate.lastIndexOf(" ");
80
+ if (breakIndex > 0) {
81
+ return {
82
+ line: remaining.slice(0, breakIndex).trimEnd(),
83
+ remaining: remaining.slice(breakIndex + 1).trimStart(),
84
+ };
85
+ }
86
+ return {
87
+ line: remaining.slice(0, capacity),
88
+ remaining: remaining.slice(capacity).trimStart(),
89
+ };
90
+ }
91
+ function highlightRequiredWords(sentence, requiredWords) {
92
+ const uniqueWords = [...new Set(requiredWords
93
+ .map((word) => word.trim().toLowerCase())
94
+ .filter((word) => word.length > 0))].sort((left, right) => right.length - left.length);
95
+ if (uniqueWords.length === 0) {
96
+ return sentence;
97
+ }
98
+ const pattern = new RegExp(`\\b(?:${uniqueWords.map(escapeRegExp).join("|")})\\b`, "gi");
99
+ return sentence.replace(pattern, (match) => match.toUpperCase());
100
+ }
101
+ function formatSentenceSlot(prefix, sentence, requiredWords, lineCount) {
102
+ if (sentence === null) {
103
+ return [
104
+ prefix.trimEnd(),
105
+ ...Array.from({ length: Math.max(0, lineCount - 1) }, () => ""),
106
+ ];
107
+ }
108
+ const normalizedSentence = highlightRequiredWords(normalizeInlineText(sentence), requiredWords);
109
+ const continuationPrefix = " ".repeat(prefix.length);
110
+ const lines = [];
111
+ let remaining = normalizedSentence;
112
+ for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
113
+ const linePrefix = lineIndex === 0 ? prefix : continuationPrefix;
114
+ const capacity = Math.max(0, MINING_SENTENCE_BOARD_WRAP_WIDTH - linePrefix.length);
115
+ const wrapped = consumeWrappedLine(remaining, capacity);
116
+ const isLastLine = lineIndex === lineCount - 1;
117
+ const lineContent = isLastLine && wrapped.remaining.length > 0
118
+ ? truncateLine(`${wrapped.line}\u2026`, capacity)
119
+ : wrapped.line;
120
+ lines.push(lineContent.length === 0
121
+ ? ""
122
+ : `${linePrefix}${lineContent}`);
123
+ remaining = wrapped.remaining;
124
+ }
125
+ return lines;
126
+ }
127
+ function formatSentenceRow(entry) {
128
+ return formatSentenceSlot(`${entry.rank}. @${entry.domainName}: `, entry.sentence, entry.requiredWords, 2);
55
129
  }
56
130
  function formatRequiredWordsLine(words) {
57
131
  if (words.length === 0) {
@@ -59,11 +133,11 @@ function formatRequiredWordsLine(words) {
59
133
  }
60
134
  return `Required words: ${words.map((word) => word.toUpperCase()).join(", ")}`;
61
135
  }
62
- function formatProvisionalSentenceRow(entry) {
136
+ function formatProvisionalSentenceRow(entry, requiredWords) {
63
137
  if (entry.domainName === null || entry.sentence === null) {
64
- return "";
138
+ return ["", "", ""];
65
139
  }
66
- return `@${entry.domainName}: ${entry.sentence}`;
140
+ return formatSentenceSlot(`@${entry.domainName}: `, entry.sentence, requiredWords, 3);
67
141
  }
68
142
  export function createEmptyMiningFollowVisualizerState() {
69
143
  return {
@@ -93,6 +167,7 @@ function cloneMiningFollowVisualizerState(state) {
93
167
  visibleBlockTimesByHeight: { ...state.visibleBlockTimesByHeight },
94
168
  settledBoardEntries: state.settledBoardEntries.map((entry) => ({
95
169
  ...entry,
170
+ requiredWords: [...entry.requiredWords],
96
171
  })),
97
172
  provisionalRequiredWords: [...state.provisionalRequiredWords],
98
173
  provisionalEntry: {
@@ -105,6 +180,10 @@ function cloneMiningFollowVisualizerState(state) {
105
180
  },
106
181
  };
107
182
  }
183
+ function miningFollowSceneShouldSettle(snapshot, nowUnixMs) {
184
+ return ((snapshot.tipSettledUntilUnixMs ?? 0) > nowUnixMs
185
+ || (snapshot.reconnectSettledUntilUnixMs ?? 0) > nowUnixMs);
186
+ }
108
187
  const VISUALIZER_PROGRESS_SNAPSHOT = {
109
188
  url: "",
110
189
  filename: "mining-follow-visualizer",
@@ -195,6 +274,8 @@ export function describeMiningVisualizerProgress(snapshot) {
195
274
  }
196
275
  export class MiningFollowVisualizer {
197
276
  #renderer;
277
+ #artworkStatusLeftText;
278
+ #artworkStatusRightText;
198
279
  #clock;
199
280
  #renderThrottle;
200
281
  #progress = createBootstrapProgress("follow_tip", VISUALIZER_PROGRESS_SNAPSHOT);
@@ -210,6 +291,9 @@ export class MiningFollowVisualizer {
210
291
  env: options.env,
211
292
  });
212
293
  this.#clock = options.clock ?? DEFAULT_RENDER_CLOCK;
294
+ const artworkVersionText = formatArtworkVersionText(options.clientVersion);
295
+ this.#artworkStatusLeftText = options.updateAvailable === true ? "UPDATE" : null;
296
+ this.#artworkStatusRightText = artworkVersionText;
213
297
  this.#renderer = renderPolicy.enabled
214
298
  ? options.rendererFactory?.(stream) ?? new TtyProgressRenderer(stream)
215
299
  : null;
@@ -238,10 +322,13 @@ export class MiningFollowVisualizer {
238
322
  replaceFollowBlockTimes(this.#scene, this.#latestUiState.visibleBlockTimesByHeight);
239
323
  const indexedHeight = this.#latestSnapshot.indexerTipHeight ?? this.#latestSnapshot.coreBestHeight ?? null;
240
324
  const nodeHeight = this.#latestSnapshot.coreBestHeight ?? indexedHeight;
325
+ const settleLatest = miningFollowSceneShouldSettle(this.#latestSnapshot, this.#clock.now());
241
326
  syncFollowSceneState(this.#scene, {
242
327
  indexedHeight,
243
328
  nodeHeight,
244
329
  liveActivated: true,
330
+ authoritativeTip: true,
331
+ settleLatest,
245
332
  });
246
333
  this.#renderThrottle.request();
247
334
  }
@@ -275,22 +362,29 @@ export class MiningFollowVisualizer {
275
362
  this.#progress.targetHeight = nodeHeight;
276
363
  this.#progress.etaSeconds = null;
277
364
  this.#progress.lastError = snapshot.lastError;
278
- this.#renderer.renderFollowScene(this.#progress, indexedHeight, nodeHeight, this.#scene, describeMiningVisualizerStatus(snapshot, uiState), {
365
+ const renderOptions = {
279
366
  artworkCogText: formatCompactCogBalanceText(uiState.balanceCogtoshi),
280
367
  artworkSatText: formatCompactSatBalanceText(uiState.balanceSats),
281
368
  extraLines: [
282
- `✎ Indexed Block #${uiState.settledBlockHeight ?? "-----"} Sentences ✎`,
369
+ `✎ Block #${uiState.settledBlockHeight ?? "-----"} Sentences ✎`,
283
370
  "",
284
371
  ...Array.from({ length: MINING_SENTENCE_BOARD_SIZE }, (_value, index) => {
285
372
  const entry = uiState.settledBoardEntries[index];
286
373
  return entry === undefined
287
- ? `${index + 1}.`
288
- : formatSentenceRow(entry.rank, entry.domainName, entry.sentence);
289
- }),
374
+ ? [`${index + 1}.`, ""]
375
+ : formatSentenceRow(entry);
376
+ }).flat(),
290
377
  "----------",
291
378
  formatRequiredWordsLine(uiState.provisionalRequiredWords),
292
- formatProvisionalSentenceRow(uiState.provisionalEntry),
379
+ ...formatProvisionalSentenceRow(uiState.provisionalEntry, uiState.provisionalRequiredWords),
293
380
  ],
294
- });
381
+ };
382
+ if (this.#artworkStatusLeftText !== null) {
383
+ renderOptions.artworkStatusLeftText = this.#artworkStatusLeftText;
384
+ }
385
+ if (this.#artworkStatusRightText !== null) {
386
+ renderOptions.artworkStatusRightText = this.#artworkStatusRightText;
387
+ }
388
+ this.#renderer.renderFollowScene(this.#progress, indexedHeight, nodeHeight, this.#scene, describeMiningVisualizerStatus(snapshot, uiState), renderOptions);
295
389
  }
296
390
  }
@@ -15,6 +15,7 @@ export declare function openWalletReadContext(options: {
15
15
  secretProvider?: WalletSecretProvider;
16
16
  walletControlLockHeld?: boolean;
17
17
  startupTimeoutMs?: number;
18
+ expectedIndexerBinaryVersion?: string | null;
18
19
  now?: number;
19
20
  paths?: WalletRuntimePaths;
20
21
  }): Promise<WalletReadContext>;
@@ -1,6 +1,6 @@
1
1
  import { access, constants } from "node:fs/promises";
2
2
  import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
3
- import { attachOrStartIndexerDaemon, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
3
+ import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
4
4
  import { createRpcClient } from "../../bitcoind/node.js";
5
5
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
6
6
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
@@ -76,7 +76,6 @@ async function normalizeLoadedWalletStateForRead(options) {
76
76
  dataDir: options.dataDir,
77
77
  chain: "main",
78
78
  startHeight: 0,
79
- serviceLifetime: "ephemeral",
80
79
  walletRootId: options.loaded.state.walletRootId,
81
80
  });
82
81
  try {
@@ -246,6 +245,11 @@ function mapIndexerStartupError(message) {
246
245
  health: "unavailable",
247
246
  message: "The live indexer daemon socket responded with an invalid or incomplete protocol exchange.",
248
247
  };
248
+ case INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED:
249
+ return {
250
+ health: "failed",
251
+ message: "The managed indexer daemon could not recover automatic background follow.",
252
+ };
249
253
  default:
250
254
  return {
251
255
  health: "unavailable",
@@ -436,7 +440,6 @@ async function attachNodeStatus(options) {
436
440
  dataDir: options.dataDir,
437
441
  chain: "main",
438
442
  startHeight: resolveCogcoinProcessingStartHeight(genesis),
439
- serviceLifetime: "ephemeral",
440
443
  walletRootId: options.walletRootId,
441
444
  startupTimeoutMs: options.startupTimeoutMs,
442
445
  });
@@ -534,18 +537,15 @@ export async function openWalletReadContext(options) {
534
537
  dataDir: options.dataDir,
535
538
  walletRootId,
536
539
  });
537
- if (probe.compatibility === "compatible") {
538
- daemonClient = probe.client;
539
- observedDaemonStatus = probe.status;
540
- indexerSource = "probe";
541
- }
542
- else if (probe.compatibility === "unreachable") {
540
+ if (probe.compatibility === "compatible" || probe.compatibility === "unreachable") {
541
+ await probe.client?.close().catch(() => undefined);
543
542
  daemonClient = await attachOrStartIndexerDaemon({
544
543
  dataDir: options.dataDir,
545
544
  databasePath: options.databasePath,
546
- serviceLifetime: "ephemeral",
547
545
  walletRootId,
548
546
  startupTimeoutMs,
547
+ ensureBackgroundFollow: true,
548
+ expectedBinaryVersion: options.expectedIndexerBinaryVersion,
549
549
  });
550
550
  }
551
551
  else {
@@ -570,6 +570,11 @@ export async function openWalletReadContext(options) {
570
570
  }
571
571
  catch (error) {
572
572
  daemonError = error instanceof Error ? error.message : String(error);
573
+ if (daemonError === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
574
+ await daemonClient?.close().catch(() => undefined);
575
+ await node.handle?.stop().catch(() => undefined);
576
+ throw error;
577
+ }
573
578
  if (observedDaemonStatus === null) {
574
579
  observedDaemonStatus = await readObservedIndexerDaemonStatus({
575
580
  dataDir: options.dataDir,
@@ -232,7 +232,6 @@ async function recreateManagedCoreWalletReplicaForReset(options) {
232
232
  dataDir: options.dataDir,
233
233
  chain: "main",
234
234
  startHeight: 0,
235
- serviceLifetime: "ephemeral",
236
235
  walletRootId: options.state.walletRootId,
237
236
  managedWalletPassphrase: options.state.managedCoreWallet.internalPassphrase,
238
237
  });
@@ -1,6 +1,9 @@
1
1
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
2
  import net from "node:net";
3
3
  import { rm } from "node:fs/promises";
4
+ function shouldRemoveAgentEndpointPath(endpoint) {
5
+ return !endpoint.startsWith("\\\\.\\pipe\\");
6
+ }
4
7
  function zeroizeBuffer(buffer) {
5
8
  if (buffer != null) {
6
9
  buffer.fill(0);
@@ -67,7 +70,7 @@ async function main() {
67
70
  }
68
71
  zeroizeBuffer(key);
69
72
  key = Buffer.alloc(0);
70
- if (!bootstrap.endpoint.startsWith("\\\\.\\")) {
73
+ if (shouldRemoveAgentEndpointPath(bootstrap.endpoint)) {
71
74
  await rm(bootstrap.endpoint, { force: true }).catch(() => undefined);
72
75
  }
73
76
  process.exit(0);
@@ -33,13 +33,18 @@ function resolveClientPasswordStatePath(directoryPath) {
33
33
  function resolveClientPasswordRotationJournalPath(directoryPath) {
34
34
  return join(directoryPath, "client-password-rotation.json");
35
35
  }
36
- function resolveAgentEndpoint(platform, stateRoot) {
36
+ function resolveAgentEndpoint(stateRoot) {
37
37
  const hash = createHash("sha256").update(stateRoot).digest("hex").slice(0, 24);
38
- if (platform === "win32") {
38
+ // Wallet provider tests simulate foreign platforms, but the local agent transport
39
+ // still has to follow the real host runtime.
40
+ if (process.platform === "win32") {
39
41
  return `\\\\.\\pipe\\cogcoin-client-password-${hash}`;
40
42
  }
41
43
  return join(tmpdir(), `cogcoin-client-password-${hash}.sock`);
42
44
  }
45
+ function shouldRemoveAgentEndpointPath(endpoint) {
46
+ return !endpoint.startsWith("\\\\.\\pipe\\");
47
+ }
43
48
  function isMissingFileError(error) {
44
49
  return error instanceof Error
45
50
  && "code" in error
@@ -352,7 +357,7 @@ async function openAgentConnection(endpoint) {
352
357
  });
353
358
  }
354
359
  async function requestAgent(options, request) {
355
- const endpoint = resolveAgentEndpoint(options.platform, options.stateRoot);
360
+ const endpoint = resolveAgentEndpoint(options.stateRoot);
356
361
  const socket = await openAgentConnection(endpoint);
357
362
  return await new Promise((resolve, reject) => {
358
363
  let received = "";
@@ -418,8 +423,9 @@ async function requestAgentOrNull(options, request) {
418
423
  ? String(error.code ?? "")
419
424
  : "";
420
425
  if (code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE") {
421
- if (options.platform !== "win32") {
422
- await rm(resolveAgentEndpoint(options.platform, options.stateRoot), { force: true }).catch(() => undefined);
426
+ const endpoint = resolveAgentEndpoint(options.stateRoot);
427
+ if (shouldRemoveAgentEndpointPath(endpoint)) {
428
+ await rm(endpoint, { force: true }).catch(() => undefined);
423
429
  }
424
430
  return null;
425
431
  }
@@ -441,8 +447,9 @@ export async function readClientPasswordSessionStatus(options) {
441
447
  }
442
448
  export async function lockClientPasswordSession(options) {
443
449
  await requestAgentOrNull(options, { command: "lock" }).catch(() => null);
444
- if (options.platform !== "win32") {
445
- await rm(resolveAgentEndpoint(options.platform, options.stateRoot), { force: true }).catch(() => undefined);
450
+ const endpoint = resolveAgentEndpoint(options.stateRoot);
451
+ if (shouldRemoveAgentEndpointPath(endpoint)) {
452
+ await rm(endpoint, { force: true }).catch(() => undefined);
446
453
  }
447
454
  return {
448
455
  unlocked: false,
@@ -499,7 +506,7 @@ async function startClientPasswordSession(options) {
499
506
  }
500
507
  async function startClientPasswordSessionWithExpiry(options) {
501
508
  const unlockUntilUnixMs = options.unlockUntilUnixMs;
502
- const endpoint = resolveAgentEndpoint(options.platform, options.stateRoot);
509
+ const endpoint = resolveAgentEndpoint(options.stateRoot);
503
510
  await lockClientPasswordSession(options).catch(() => undefined);
504
511
  await mkdir(options.runtimeRoot, { recursive: true }).catch(() => undefined);
505
512
  const child = spawn(process.execPath, [fileURLToPath(new URL("./client-password-agent.js", import.meta.url)), endpoint, String(unlockUntilUnixMs)], {
@@ -394,7 +394,6 @@ export async function anchorDomain(options) {
394
394
  dataDir: options.dataDir,
395
395
  chain: "main",
396
396
  startHeight: 0,
397
- serviceLifetime: "ephemeral",
398
397
  walletRootId: state.walletRootId,
399
398
  });
400
399
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -131,7 +131,6 @@ export async function transferBitcoin(options) {
131
131
  dataDir: options.dataDir,
132
132
  chain: "main",
133
133
  startHeight: 0,
134
- serviceLifetime: "ephemeral",
135
134
  walletRootId: state.walletRootId,
136
135
  });
137
136
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -534,7 +534,6 @@ export async function sendCog(options) {
534
534
  dataDir: options.dataDir,
535
535
  chain: "main",
536
536
  startHeight: 0,
537
- serviceLifetime: "ephemeral",
538
537
  walletRootId: operation.state.walletRootId,
539
538
  });
540
539
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -717,7 +716,6 @@ export async function lockCogToDomain(options) {
717
716
  dataDir: options.dataDir,
718
717
  chain: "main",
719
718
  startHeight: 0,
720
- serviceLifetime: "ephemeral",
721
719
  walletRootId: operation.state.walletRootId,
722
720
  });
723
721
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -877,7 +875,6 @@ async function runClaimLikeMutation(options, reclaim) {
877
875
  dataDir: options.dataDir,
878
876
  chain: "main",
879
877
  startHeight: 0,
880
- serviceLifetime: "ephemeral",
881
878
  walletRootId: operation.state.walletRootId,
882
879
  });
883
880
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -435,7 +435,7 @@ export async function buildWalletMutationTransaction(options) {
435
435
  const funded = await options.rpc.walletCreateFundedPsbt(options.walletName, options.plan.fixedInputs, options.plan.outputs, 0, {
436
436
  add_inputs: true,
437
437
  include_unsafe: false,
438
- minconf: 1,
438
+ minconf: availableFundingMinConf,
439
439
  changeAddress: options.plan.changeAddress,
440
440
  ...(options.plan.changePosition == null ? {} : { changePosition: options.plan.changePosition }),
441
441
  lockUnspents: false,
@@ -518,7 +518,6 @@ async function submitDomainAdminMutation(options) {
518
518
  dataDir: options.dataDir,
519
519
  chain: "main",
520
520
  startHeight: 0,
521
- serviceLifetime: "ephemeral",
522
521
  walletRootId: operation.state.walletRootId,
523
522
  });
524
523
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -609,7 +609,6 @@ export async function transferDomain(options) {
609
609
  dataDir: options.dataDir,
610
610
  chain: "main",
611
611
  startHeight: 0,
612
- serviceLifetime: "ephemeral",
613
612
  walletRootId: operation.state.walletRootId,
614
613
  });
615
614
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -868,7 +867,6 @@ async function runSellMutation(options) {
868
867
  dataDir: options.dataDir,
869
868
  chain: "main",
870
869
  startHeight: 0,
871
- serviceLifetime: "ephemeral",
872
870
  walletRootId: operation.state.walletRootId,
873
871
  });
874
872
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -1138,7 +1136,6 @@ export async function buyDomain(options) {
1138
1136
  dataDir: options.dataDir,
1139
1137
  chain: "main",
1140
1138
  startHeight: 0,
1141
- serviceLifetime: "ephemeral",
1142
1139
  walletRootId: operation.state.walletRootId,
1143
1140
  });
1144
1141
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -686,7 +686,6 @@ async function submitStandaloneFieldMutation(options) {
686
686
  dataDir: options.dataDir,
687
687
  chain: "main",
688
688
  startHeight: 0,
689
- serviceLifetime: "ephemeral",
690
689
  walletRootId: operation.state.walletRootId,
691
690
  });
692
691
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -532,7 +532,6 @@ export async function registerDomain(options) {
532
532
  dataDir: options.dataDir,
533
533
  chain: "main",
534
534
  startHeight: 0,
535
- serviceLifetime: "ephemeral",
536
535
  walletRootId: state.walletRootId,
537
536
  });
538
537
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
@@ -513,7 +513,6 @@ async function submitReputationMutation(options) {
513
513
  dataDir: options.dataDir,
514
514
  chain: "main",
515
515
  startHeight: 0,
516
- serviceLifetime: "ephemeral",
517
516
  walletRootId: operation.state.walletRootId,
518
517
  });
519
518
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",