@cogcoin/client 0.5.15 → 1.0.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.
Files changed (174) hide show
  1. package/README.md +80 -25
  2. package/dist/app-paths.d.ts +5 -6
  3. package/dist/app-paths.js +8 -16
  4. package/dist/art/balance.txt +10 -0
  5. package/dist/art/welcome.txt +16 -0
  6. package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
  7. package/dist/bitcoind/bootstrap/controller.js +53 -1
  8. package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
  9. package/dist/bitcoind/client/follow-block-times.js +1 -1
  10. package/dist/bitcoind/client/internal-types.d.ts +7 -3
  11. package/dist/bitcoind/client/managed-client.d.ts +4 -2
  12. package/dist/bitcoind/client/managed-client.js +14 -0
  13. package/dist/bitcoind/client/sync-engine.js +72 -11
  14. package/dist/bitcoind/hash-order.d.ts +4 -0
  15. package/dist/bitcoind/hash-order.js +13 -0
  16. package/dist/bitcoind/indexer-daemon-main.js +11 -3
  17. package/dist/bitcoind/normalize.js +3 -2
  18. package/dist/bitcoind/processing-start-height.d.ts +5 -0
  19. package/dist/bitcoind/processing-start-height.js +7 -0
  20. package/dist/bitcoind/progress/constants.d.ts +4 -0
  21. package/dist/bitcoind/progress/constants.js +4 -0
  22. package/dist/bitcoind/progress/controller.d.ts +2 -1
  23. package/dist/bitcoind/progress/controller.js +3 -3
  24. package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
  25. package/dist/bitcoind/progress/follow-scene.js +29 -6
  26. package/dist/bitcoind/progress/formatting.d.ts +1 -0
  27. package/dist/bitcoind/progress/formatting.js +6 -0
  28. package/dist/bitcoind/progress/train-scene.js +37 -18
  29. package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
  30. package/dist/bitcoind/progress/tty-renderer.js +8 -4
  31. package/dist/bitcoind/rpc.d.ts +2 -1
  32. package/dist/bitcoind/rpc.js +3 -0
  33. package/dist/bitcoind/types.d.ts +6 -0
  34. package/dist/bytes.d.ts +1 -0
  35. package/dist/bytes.js +3 -0
  36. package/dist/cli/art.d.ts +2 -0
  37. package/dist/cli/art.js +37 -0
  38. package/dist/cli/commands/client-admin.d.ts +2 -0
  39. package/dist/cli/commands/client-admin.js +91 -0
  40. package/dist/cli/commands/follow.js +0 -2
  41. package/dist/cli/commands/mining-admin.js +6 -47
  42. package/dist/cli/commands/mining-read.js +11 -50
  43. package/dist/cli/commands/mining-runtime.js +142 -5
  44. package/dist/cli/commands/service-runtime.js +0 -2
  45. package/dist/cli/commands/status.js +8 -2
  46. package/dist/cli/commands/sync.js +49 -92
  47. package/dist/cli/commands/wallet-admin.js +142 -136
  48. package/dist/cli/commands/wallet-mutation.js +91 -79
  49. package/dist/cli/commands/wallet-read.js +15 -18
  50. package/dist/cli/context.js +5 -14
  51. package/dist/cli/mining-format.d.ts +0 -1
  52. package/dist/cli/mining-format.js +5 -37
  53. package/dist/cli/mining-json.d.ts +0 -18
  54. package/dist/cli/mining-json.js +0 -35
  55. package/dist/cli/mutation-command-groups.d.ts +1 -2
  56. package/dist/cli/mutation-command-groups.js +0 -5
  57. package/dist/cli/mutation-json.d.ts +24 -145
  58. package/dist/cli/mutation-json.js +30 -136
  59. package/dist/cli/mutation-resolved-json.d.ts +0 -7
  60. package/dist/cli/mutation-resolved-json.js +4 -10
  61. package/dist/cli/mutation-success.d.ts +2 -0
  62. package/dist/cli/mutation-success.js +11 -1
  63. package/dist/cli/mutation-text-format.js +1 -3
  64. package/dist/cli/output.d.ts +1 -1
  65. package/dist/cli/output.js +254 -231
  66. package/dist/cli/parse.d.ts +1 -1
  67. package/dist/cli/parse.js +93 -122
  68. package/dist/cli/preview-json.d.ts +17 -120
  69. package/dist/cli/preview-json.js +14 -97
  70. package/dist/cli/prompt.js +8 -13
  71. package/dist/cli/read-json.d.ts +15 -37
  72. package/dist/cli/read-json.js +44 -140
  73. package/dist/cli/runner.js +10 -13
  74. package/dist/cli/sync-progress.d.ts +6 -0
  75. package/dist/cli/sync-progress.js +91 -0
  76. package/dist/cli/types.d.ts +9 -17
  77. package/dist/cli/types.js +0 -2
  78. package/dist/cli/wallet-format.d.ts +1 -0
  79. package/dist/cli/wallet-format.js +208 -144
  80. package/dist/cli/workflow-hints.d.ts +3 -3
  81. package/dist/cli/workflow-hints.js +11 -8
  82. package/dist/client/default-client.d.ts +3 -1
  83. package/dist/client/default-client.js +45 -2
  84. package/dist/client/factory.js +1 -1
  85. package/dist/client/initialization.js +23 -0
  86. package/dist/client/persistence.js +5 -5
  87. package/dist/client/store-adapter.js +1 -0
  88. package/dist/sqlite/checkpoints.d.ts +1 -0
  89. package/dist/sqlite/checkpoints.js +7 -0
  90. package/dist/sqlite/store.js +14 -1
  91. package/dist/types.d.ts +1 -0
  92. package/dist/wallet/coin-control.d.ts +41 -12
  93. package/dist/wallet/coin-control.js +100 -428
  94. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  95. package/dist/wallet/descriptor-normalization.js +0 -16
  96. package/dist/wallet/lifecycle.d.ts +7 -99
  97. package/dist/wallet/lifecycle.js +513 -968
  98. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  99. package/dist/wallet/managed-core-wallet.js +20 -0
  100. package/dist/wallet/mining/constants.d.ts +5 -12
  101. package/dist/wallet/mining/constants.js +5 -12
  102. package/dist/wallet/mining/control.d.ts +1 -13
  103. package/dist/wallet/mining/control.js +45 -349
  104. package/dist/wallet/mining/index.d.ts +4 -5
  105. package/dist/wallet/mining/index.js +2 -3
  106. package/dist/wallet/mining/runner.d.ts +123 -13
  107. package/dist/wallet/mining/runner.js +899 -511
  108. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  109. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  110. package/dist/wallet/mining/sentence-protocol.js +123 -0
  111. package/dist/wallet/mining/sentences.d.ts +4 -8
  112. package/dist/wallet/mining/sentences.js +3 -52
  113. package/dist/wallet/mining/state.d.ts +11 -6
  114. package/dist/wallet/mining/state.js +7 -6
  115. package/dist/wallet/mining/types.d.ts +2 -30
  116. package/dist/wallet/mining/visualizer.d.ts +31 -3
  117. package/dist/wallet/mining/visualizer.js +135 -13
  118. package/dist/wallet/read/context.d.ts +0 -2
  119. package/dist/wallet/read/context.js +119 -140
  120. package/dist/wallet/read/filter.js +2 -11
  121. package/dist/wallet/read/index.d.ts +1 -1
  122. package/dist/wallet/read/project.js +24 -77
  123. package/dist/wallet/read/types.d.ts +10 -25
  124. package/dist/wallet/reset.d.ts +0 -1
  125. package/dist/wallet/reset.js +60 -138
  126. package/dist/wallet/root-resolution.d.ts +1 -5
  127. package/dist/wallet/root-resolution.js +0 -18
  128. package/dist/wallet/runtime.d.ts +0 -6
  129. package/dist/wallet/runtime.js +0 -8
  130. package/dist/wallet/state/client-password-agent.js +208 -0
  131. package/dist/wallet/state/client-password.d.ts +65 -0
  132. package/dist/wallet/state/client-password.js +952 -0
  133. package/dist/wallet/state/crypto.d.ts +1 -20
  134. package/dist/wallet/state/crypto.js +0 -63
  135. package/dist/wallet/state/provider.d.ts +23 -11
  136. package/dist/wallet/state/provider.js +248 -290
  137. package/dist/wallet/state/storage.d.ts +2 -2
  138. package/dist/wallet/state/storage.js +48 -16
  139. package/dist/wallet/tx/anchor.d.ts +3 -28
  140. package/dist/wallet/tx/anchor.js +349 -1250
  141. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  142. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  143. package/dist/wallet/tx/cog.d.ts +5 -1
  144. package/dist/wallet/tx/cog.js +149 -185
  145. package/dist/wallet/tx/common.d.ts +61 -8
  146. package/dist/wallet/tx/common.js +266 -146
  147. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  148. package/dist/wallet/tx/domain-admin.js +61 -99
  149. package/dist/wallet/tx/domain-market.d.ts +5 -1
  150. package/dist/wallet/tx/domain-market.js +221 -228
  151. package/dist/wallet/tx/field.d.ts +4 -10
  152. package/dist/wallet/tx/field.js +83 -924
  153. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  154. package/dist/wallet/tx/identity-selector.js +17 -35
  155. package/dist/wallet/tx/index.d.ts +3 -1
  156. package/dist/wallet/tx/index.js +2 -1
  157. package/dist/wallet/tx/register.d.ts +3 -1
  158. package/dist/wallet/tx/register.js +62 -220
  159. package/dist/wallet/tx/reputation.d.ts +3 -1
  160. package/dist/wallet/tx/reputation.js +58 -95
  161. package/dist/wallet/types.d.ts +8 -122
  162. package/package.json +5 -5
  163. package/dist/wallet/archive.d.ts +0 -4
  164. package/dist/wallet/archive.js +0 -41
  165. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  166. package/dist/wallet/mining/hook-protocol.js +0 -161
  167. package/dist/wallet/mining/hook-runner.js +0 -52
  168. package/dist/wallet/mining/hooks.d.ts +0 -38
  169. package/dist/wallet/mining/hooks.js +0 -520
  170. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  171. package/dist/wallet/state/explicit-lock.js +0 -19
  172. package/dist/wallet/state/session.d.ts +0 -12
  173. package/dist/wallet/state/session.js +0 -23
  174. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
package/dist/bytes.js CHANGED
@@ -9,6 +9,9 @@ export function hexToBytes(hex) {
9
9
  export function cloneBytes(bytes) {
10
10
  return new Uint8Array(bytes);
11
11
  }
12
+ export function reverseBytes(bytes) {
13
+ return Uint8Array.from([...bytes].reverse());
14
+ }
12
15
  export function encodeText(value) {
13
16
  return textEncoder.encode(value);
14
17
  }
@@ -0,0 +1,2 @@
1
+ export declare function loadWelcomeArtText(): string;
2
+ export declare function loadBalanceArtText(): string;
@@ -0,0 +1,37 @@
1
+ import { readFileSync } from "node:fs";
2
+ let welcomeArtCache = null;
3
+ let balanceArtCache = null;
4
+ const FIXED_ART_WIDTH = 80;
5
+ const BALANCE_ART_HEIGHT = 10;
6
+ function normalizeFixedArtText(raw, options) {
7
+ const lines = raw.replaceAll("\r\n", "\n").split("\n");
8
+ if (lines[lines.length - 1] === "") {
9
+ lines.pop();
10
+ }
11
+ if (lines.length !== options.height) {
12
+ throw new Error(`${options.name}_art_height_invalid_${lines.length}`);
13
+ }
14
+ for (const line of lines) {
15
+ if (line.length !== FIXED_ART_WIDTH) {
16
+ throw new Error(`${options.name}_art_width_invalid_${line.length}`);
17
+ }
18
+ }
19
+ return lines.join("\n");
20
+ }
21
+ export function loadWelcomeArtText() {
22
+ if (welcomeArtCache !== null) {
23
+ return welcomeArtCache;
24
+ }
25
+ welcomeArtCache = readFileSync(new URL("../art/welcome.txt", import.meta.url), "utf8");
26
+ return welcomeArtCache;
27
+ }
28
+ export function loadBalanceArtText() {
29
+ if (balanceArtCache !== null) {
30
+ return balanceArtCache;
31
+ }
32
+ balanceArtCache = normalizeFixedArtText(readFileSync(new URL("../art/balance.txt", import.meta.url), "utf8"), {
33
+ name: "balance",
34
+ height: BALANCE_ART_HEIGHT,
35
+ });
36
+ return balanceArtCache;
37
+ }
@@ -0,0 +1,2 @@
1
+ import type { ParsedCliArgs, RequiredCliRunnerContext } from "../types.js";
2
+ export declare function runClientAdminCommand(parsed: ParsedCliArgs, context: RequiredCliRunnerContext): Promise<number>;
@@ -0,0 +1,91 @@
1
+ import { writeLine } from "../io.js";
2
+ import { createTerminalPrompter } from "../prompt.js";
3
+ import { createMutationSuccessEnvelope, describeCanonicalCommand, resolveStableMutationJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
4
+ import { changeClientPassword, lockClientPassword, unlockClientPassword, } from "../../wallet/state/provider.js";
5
+ function createCommandPrompter(parsed, context) {
6
+ return parsed.outputMode !== "text"
7
+ ? createTerminalPrompter(context.stdin, context.stderr)
8
+ : context.createPrompter();
9
+ }
10
+ export async function runClientAdminCommand(parsed, context) {
11
+ try {
12
+ if (parsed.command === "client-lock") {
13
+ const status = await lockClientPassword(context.walletSecretProvider);
14
+ if (parsed.outputMode === "json") {
15
+ writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "locked", {
16
+ resultType: "operation",
17
+ operation: {
18
+ kind: "client-lock",
19
+ locked: true,
20
+ unlockUntilUnixMs: status.unlockUntilUnixMs,
21
+ },
22
+ state: {
23
+ locked: true,
24
+ unlockUntilUnixMs: status.unlockUntilUnixMs,
25
+ },
26
+ }));
27
+ return 0;
28
+ }
29
+ writeLine(context.stdout, "Client locked.");
30
+ return 0;
31
+ }
32
+ if (parsed.command === "client-unlock") {
33
+ const prompter = createCommandPrompter(parsed, context);
34
+ const status = await unlockClientPassword(context.walletSecretProvider, prompter);
35
+ if (parsed.outputMode === "json") {
36
+ writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "unlocked", {
37
+ resultType: "operation",
38
+ operation: {
39
+ kind: "client-unlock",
40
+ unlocked: status.unlocked,
41
+ unlockUntilUnixMs: status.unlockUntilUnixMs,
42
+ },
43
+ state: {
44
+ unlocked: status.unlocked,
45
+ unlockUntilUnixMs: status.unlockUntilUnixMs,
46
+ },
47
+ }));
48
+ return 0;
49
+ }
50
+ writeLine(context.stdout, status.unlockUntilUnixMs === null
51
+ ? "Client unlocked."
52
+ : `Client unlocked until ${new Date(status.unlockUntilUnixMs).toISOString()}.`);
53
+ return 0;
54
+ }
55
+ if (parsed.command === "client-change-password") {
56
+ const prompter = createCommandPrompter(parsed, context);
57
+ const status = await changeClientPassword(context.walletSecretProvider, prompter);
58
+ if (parsed.outputMode === "json") {
59
+ writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "changed", {
60
+ resultType: "operation",
61
+ operation: {
62
+ kind: "client-change-password",
63
+ changed: true,
64
+ unlocked: status.unlocked,
65
+ unlockUntilUnixMs: status.unlockUntilUnixMs,
66
+ },
67
+ state: {
68
+ changed: true,
69
+ unlocked: status.unlocked,
70
+ unlockUntilUnixMs: status.unlockUntilUnixMs,
71
+ },
72
+ }));
73
+ return 0;
74
+ }
75
+ writeLine(context.stdout, status.unlockUntilUnixMs === null
76
+ ? "Client password changed."
77
+ : `Client password changed. Client unlocked until ${new Date(status.unlockUntilUnixMs).toISOString()}.`);
78
+ return 0;
79
+ }
80
+ writeLine(context.stderr, `client admin command not implemented: ${parsed.command}`);
81
+ return 1;
82
+ }
83
+ catch (error) {
84
+ return writeHandledCliError({
85
+ parsed,
86
+ stdout: context.stdout,
87
+ stderr: context.stderr,
88
+ error,
89
+ });
90
+ }
91
+ }
@@ -16,8 +16,6 @@ export async function runFollowCommand(parsed, context) {
16
16
  paths: runtimePaths,
17
17
  provider: context.walletSecretProvider,
18
18
  loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
19
- loadUnlockSession: context.loadUnlockSession,
20
- loadWalletExplicitLock: context.loadWalletExplicitLock,
21
19
  });
22
20
  try {
23
21
  controlLock = await acquireFileLock(runtimePaths.walletControlLockPath, {
@@ -1,9 +1,10 @@
1
- import { buildHooksDisableMiningData, buildHooksEnableMiningData, buildMineSetupData, } from "../mining-json.js";
2
- import { buildHooksPreviewData } from "../preview-json.js";
1
+ import { buildMineSetupData, } from "../mining-json.js";
2
+ import { buildMineSetupPreviewData } from "../preview-json.js";
3
3
  import { writeLine } from "../io.js";
4
4
  import { createTerminalPrompter } from "../prompt.js";
5
5
  import { createPreviewSuccessEnvelope, createMutationSuccessEnvelope, describeCanonicalCommand, resolvePreviewJsonSchema, resolveStableMiningControlJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
6
- import { formatNextStepLines, getHooksEnableMiningNextSteps, getMineSetupNextSteps, } from "../workflow-hints.js";
6
+ import { formatNextStepLines, getMineSetupNextSteps, } from "../workflow-hints.js";
7
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
7
8
  function createCommandPrompter(parsed, context) {
8
9
  return parsed.outputMode !== "text"
9
10
  ? createTerminalPrompter(context.stdin, context.stderr)
@@ -11,52 +12,10 @@ function createCommandPrompter(parsed, context) {
11
12
  }
12
13
  export async function runMiningAdminCommand(parsed, context) {
13
14
  try {
14
- const provider = context.walletSecretProvider;
15
15
  const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
16
- if (parsed.command === "hooks-mining-enable") {
17
- const prompter = createCommandPrompter(parsed, context);
18
- const view = await context.enableMiningHooks({
19
- provider,
20
- prompter,
21
- paths: runtimePaths,
22
- });
23
- const nextSteps = getHooksEnableMiningNextSteps();
24
- if (parsed.outputMode === "preview-json") {
25
- writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "enabled", buildHooksPreviewData("hooks-enable-mining", view), {
26
- nextSteps,
27
- }));
28
- return 0;
29
- }
30
- if (parsed.outputMode === "json") {
31
- writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMiningControlJsonSchema(parsed), "cogcoin hooks enable mining", "enabled", buildHooksEnableMiningData(view), {
32
- nextSteps,
33
- }));
34
- return 0;
35
- }
36
- writeLine(context.stdout, "Custom mining hook enabled.");
37
- for (const line of formatNextStepLines(nextSteps)) {
38
- writeLine(context.stdout, line);
39
- }
40
- return 0;
41
- }
42
- if (parsed.command === "hooks-mining-disable") {
43
- const view = await context.disableMiningHooks({
44
- provider,
45
- paths: runtimePaths,
46
- });
47
- if (parsed.outputMode === "preview-json") {
48
- writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "disabled", buildHooksPreviewData("hooks-disable-mining", view)));
49
- return 0;
50
- }
51
- if (parsed.outputMode === "json") {
52
- writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMiningControlJsonSchema(parsed), "cogcoin hooks disable mining", "disabled", buildHooksDisableMiningData(view)));
53
- return 0;
54
- }
55
- writeLine(context.stdout, "Mining hooks switched back to builtin mode.");
56
- return 0;
57
- }
58
16
  if (parsed.command === "mine-setup") {
59
17
  const prompter = createCommandPrompter(parsed, context);
18
+ const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
60
19
  const view = await context.setupBuiltInMining({
61
20
  provider,
62
21
  prompter,
@@ -64,7 +23,7 @@ export async function runMiningAdminCommand(parsed, context) {
64
23
  });
65
24
  const nextSteps = getMineSetupNextSteps();
66
25
  if (parsed.outputMode === "preview-json") {
67
- writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "configured", buildHooksPreviewData("mine-setup", view), {
26
+ writeJsonValue(context.stdout, createPreviewSuccessEnvelope(resolvePreviewJsonSchema(parsed), describeCanonicalCommand(parsed), "configured", buildMineSetupPreviewData(view), {
68
27
  nextSteps,
69
28
  }));
70
29
  return 0;
@@ -1,10 +1,10 @@
1
1
  import { dirname } from "node:path";
2
2
  import { stat } from "node:fs/promises";
3
- import { formatHooksStatusReport, formatMineStatusReport, formatMiningEventRecord } from "../mining-format.js";
3
+ import { formatMineStatusReport, formatMiningEventRecord } from "../mining-format.js";
4
4
  import { writeLine } from "../io.js";
5
- import { inspectWalletLocalState } from "../../wallet/read/index.js";
6
5
  import { createErrorEnvelope, createSuccessEnvelope, describeCanonicalCommand, normalizeListPage, writeJsonValue, } from "../output.js";
7
- import { buildHooksStatusJson, buildMineLogJson, buildMineStatusJson } from "../read-json.js";
6
+ import { buildMineLogJson, buildMineStatusJson } from "../read-json.js";
7
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
8
8
  async function readRotationIndices(paths) {
9
9
  const rotation = [];
10
10
  for (let index = 1; index <= 4; index += 1) {
@@ -26,46 +26,6 @@ export async function runMiningReadCommand(parsed, context) {
26
26
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
27
27
  const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
28
28
  await context.ensureDirectory(dirname(dbPath));
29
- if (parsed.command === "hooks-mining-status") {
30
- const localState = await inspectWalletLocalState({
31
- secretProvider: context.walletSecretProvider,
32
- paths: runtimePaths,
33
- });
34
- const view = await context.inspectMiningControlPlane({
35
- provider: context.walletSecretProvider,
36
- localState,
37
- bitcoind: {
38
- health: "unavailable",
39
- status: null,
40
- message: "Managed bitcoind status unavailable during hook inspection.",
41
- },
42
- nodeStatus: null,
43
- nodeHealth: "unavailable",
44
- indexer: {
45
- health: "unavailable",
46
- status: null,
47
- message: null,
48
- snapshotTip: null,
49
- source: "none",
50
- daemonInstanceId: null,
51
- snapshotSeq: null,
52
- openedAtUnixMs: null,
53
- },
54
- verify: parsed.verify,
55
- paths: runtimePaths,
56
- });
57
- if (parsed.outputMode === "json") {
58
- const result = buildHooksStatusJson(view);
59
- writeJsonValue(context.stdout, createSuccessEnvelope("cogcoin/hooks-status/v1", describeCanonicalCommand(parsed), result.data, {
60
- warnings: result.warnings,
61
- explanations: result.explanations,
62
- nextSteps: result.nextSteps,
63
- }));
64
- return 0;
65
- }
66
- writeLine(context.stdout, formatHooksStatusReport(view));
67
- return 0;
68
- }
69
29
  if (parsed.command === "mine-log") {
70
30
  if (!parsed.follow) {
71
31
  const allEvents = await context.readMiningLog({
@@ -128,15 +88,18 @@ export async function runMiningReadCommand(parsed, context) {
128
88
  }
129
89
  return 0;
130
90
  }
91
+ const provider = parsed.outputMode === "text"
92
+ ? withInteractiveWalletSecretProvider(context.walletSecretProvider, context.createPrompter())
93
+ : context.walletSecretProvider;
131
94
  const readContext = await context.openWalletReadContext({
132
95
  dataDir,
133
96
  databasePath: dbPath,
134
- secretProvider: context.walletSecretProvider,
97
+ secretProvider: provider,
135
98
  paths: runtimePaths,
136
99
  });
137
100
  try {
138
101
  const mining = readContext.mining ?? await context.inspectMiningControlPlane({
139
- provider: context.walletSecretProvider,
102
+ provider,
140
103
  localState: readContext.localState,
141
104
  bitcoind: readContext.bitcoind,
142
105
  nodeStatus: readContext.nodeStatus,
@@ -163,11 +126,9 @@ export async function runMiningReadCommand(parsed, context) {
163
126
  catch (error) {
164
127
  const message = error instanceof Error ? error.message : String(error);
165
128
  if (parsed.outputMode === "json") {
166
- writeJsonValue(context.stdout, createErrorEnvelope(parsed.command === "hooks-mining-status"
167
- ? "cogcoin/hooks-status/v1"
168
- : parsed.command === "mine-log"
169
- ? "cogcoin/mine-log/v1"
170
- : "cogcoin/mine-status/v1", describeCanonicalCommand(parsed), message, message));
129
+ writeJsonValue(context.stdout, createErrorEnvelope(parsed.command === "mine-log"
130
+ ? "cogcoin/mine-log/v1"
131
+ : "cogcoin/mine-status/v1", describeCanonicalCommand(parsed), message, message));
171
132
  return 5;
172
133
  }
173
134
  writeLine(context.stderr, message);
@@ -1,23 +1,136 @@
1
1
  import { dirname } from "node:path";
2
+ import { FileLockBusyError, acquireFileLock } from "../../wallet/fs/lock.js";
3
+ import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
4
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
2
5
  import { buildMineStartData, buildMineStopData, } from "../mining-json.js";
3
6
  import { buildMineStartPreviewData, buildMineStopPreviewData, } from "../preview-json.js";
4
- import { writeLine } from "../io.js";
7
+ import { usesTtyProgress, writeLine } from "../io.js";
5
8
  import { createTerminalPrompter } from "../prompt.js";
6
9
  import { createPreviewSuccessEnvelope, createMutationSuccessEnvelope, describeCanonicalCommand, resolvePreviewJsonSchema, resolveStableMiningControlJsonSchema, writeHandledCliError, writeJsonValue, } from "../output.js";
7
10
  import { formatNextStepLines, getMineStopNextSteps, } from "../workflow-hints.js";
11
+ import { createStopSignalWatcher, waitForCompletionOrStop } from "../signals.js";
12
+ import { createSyncProgressReporter } from "../sync-progress.js";
8
13
  function createCommandPrompter(parsed, context) {
9
14
  return parsed.outputMode !== "text"
10
15
  ? createTerminalPrompter(context.stdin, context.stderr)
11
16
  : context.createPrompter();
12
17
  }
18
+ async function ensureMiningProviderSetup(options) {
19
+ const setupReady = await options.context.ensureBuiltInMiningSetupIfNeeded({
20
+ provider: options.provider,
21
+ prompter: options.prompter,
22
+ paths: options.runtimePaths,
23
+ });
24
+ if (!setupReady) {
25
+ throw new Error("Built-in mining provider is not configured. Run `cogcoin mine setup`.");
26
+ }
27
+ }
28
+ async function syncManagedMiningReadiness(options) {
29
+ const ttyProgressActive = usesTtyProgress(options.parsed.progressOutput, options.context.stderr);
30
+ let controlLock = null;
31
+ let store = null;
32
+ let storeOwned = true;
33
+ let client = null;
34
+ let clientClosed = false;
35
+ try {
36
+ const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
37
+ paths: options.runtimePaths,
38
+ provider: options.provider,
39
+ loadRawWalletStateEnvelope: options.context.loadRawWalletStateEnvelope,
40
+ });
41
+ try {
42
+ controlLock = await acquireFileLock(options.runtimePaths.walletControlLockPath, {
43
+ purpose: "managed-sync",
44
+ walletRootId: walletRoot.walletRootId,
45
+ });
46
+ }
47
+ catch (error) {
48
+ if (error instanceof FileLockBusyError) {
49
+ throw new Error("wallet_control_lock_busy");
50
+ }
51
+ throw error;
52
+ }
53
+ await options.context.ensureDirectory(dirname(options.databasePath));
54
+ store = await options.context.openSqliteStore({ filename: options.databasePath });
55
+ client = await options.context.openManagedBitcoindClient({
56
+ store,
57
+ databasePath: options.databasePath,
58
+ dataDir: options.dataDir,
59
+ walletRootId: walletRoot.walletRootId,
60
+ progressOutput: options.parsed.progressOutput,
61
+ onProgress: ttyProgressActive ? undefined : createSyncProgressReporter({
62
+ progressOutput: options.parsed.progressOutput,
63
+ write: (line) => {
64
+ writeLine(options.context.stderr, line);
65
+ },
66
+ }),
67
+ });
68
+ storeOwned = false;
69
+ const stopWatcher = createStopSignalWatcher(options.context.signalSource, options.context.stderr, client, options.context.forceExit, [options.runtimePaths.walletControlLockPath]);
70
+ try {
71
+ const syncOutcome = await waitForCompletionOrStop(client.syncToTip(), stopWatcher);
72
+ if (syncOutcome.kind === "stopped") {
73
+ return syncOutcome.code;
74
+ }
75
+ const result = syncOutcome.value;
76
+ if (result.endingHeight !== null && result.endingHeight === result.bestHeight) {
77
+ stopWatcher.cleanup();
78
+ const detachPromise = typeof client.detachToBackgroundFollow === "function"
79
+ ? client.detachToBackgroundFollow()
80
+ : Promise.resolve();
81
+ try {
82
+ await detachPromise;
83
+ await client.close();
84
+ clientClosed = true;
85
+ writeLine(options.context.stderr, "Detached cleanly; background indexer follow resumed.");
86
+ return null;
87
+ }
88
+ catch {
89
+ writeLine(options.context.stderr, "Detach failed before background indexer follow was confirmed.");
90
+ return 1;
91
+ }
92
+ }
93
+ throw new Error("Managed sync did not reach the current Bitcoin tip.");
94
+ }
95
+ finally {
96
+ stopWatcher.cleanup();
97
+ if (!clientClosed) {
98
+ await client.close();
99
+ }
100
+ }
101
+ }
102
+ finally {
103
+ if (storeOwned && store !== null) {
104
+ await store.close().catch(() => undefined);
105
+ }
106
+ await controlLock?.release().catch(() => undefined);
107
+ }
108
+ }
13
109
  export async function runMiningRuntimeCommand(parsed, context) {
14
110
  try {
15
111
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
16
112
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
17
- const provider = context.walletSecretProvider;
18
113
  const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
19
- await context.ensureDirectory(dirname(dbPath));
20
114
  if (parsed.command === "mine") {
115
+ const prompter = context.createPrompter();
116
+ const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
117
+ await ensureMiningProviderSetup({
118
+ context,
119
+ provider,
120
+ prompter,
121
+ runtimePaths,
122
+ });
123
+ const preflightCode = await syncManagedMiningReadiness({
124
+ parsed,
125
+ context,
126
+ dataDir,
127
+ databasePath: dbPath,
128
+ provider,
129
+ runtimePaths,
130
+ });
131
+ if (preflightCode !== null) {
132
+ return preflightCode;
133
+ }
21
134
  const abortController = new AbortController();
22
135
  const onStop = () => {
23
136
  abortController.abort();
@@ -29,11 +142,12 @@ export async function runMiningRuntimeCommand(parsed, context) {
29
142
  dataDir,
30
143
  databasePath: dbPath,
31
144
  provider,
32
- prompter: context.createPrompter(),
145
+ prompter,
33
146
  signal: abortController.signal,
34
147
  stdout: context.stdout,
35
148
  stderr: context.stderr,
36
149
  progressOutput: parsed.progressOutput,
150
+ builtInSetupEnsured: true,
37
151
  paths: runtimePaths,
38
152
  });
39
153
  }
@@ -44,11 +158,31 @@ export async function runMiningRuntimeCommand(parsed, context) {
44
158
  return 0;
45
159
  }
46
160
  if (parsed.command === "mine-start") {
161
+ const prompter = createCommandPrompter(parsed, context);
162
+ const provider = withInteractiveWalletSecretProvider(context.walletSecretProvider, prompter);
163
+ await ensureMiningProviderSetup({
164
+ context,
165
+ provider,
166
+ prompter,
167
+ runtimePaths,
168
+ });
169
+ const preflightCode = await syncManagedMiningReadiness({
170
+ parsed,
171
+ context,
172
+ dataDir,
173
+ databasePath: dbPath,
174
+ provider,
175
+ runtimePaths,
176
+ });
177
+ if (preflightCode !== null) {
178
+ return preflightCode;
179
+ }
47
180
  const result = await context.startBackgroundMining({
48
181
  dataDir,
49
182
  databasePath: dbPath,
50
183
  provider,
51
- prompter: createCommandPrompter(parsed, context),
184
+ prompter,
185
+ builtInSetupEnsured: true,
52
186
  paths: runtimePaths,
53
187
  });
54
188
  if (parsed.outputMode === "preview-json") {
@@ -73,6 +207,9 @@ export async function runMiningRuntimeCommand(parsed, context) {
73
207
  return 0;
74
208
  }
75
209
  if (parsed.command === "mine-stop") {
210
+ const provider = parsed.outputMode === "text"
211
+ ? withInteractiveWalletSecretProvider(context.walletSecretProvider, context.createPrompter())
212
+ : context.walletSecretProvider;
76
213
  const snapshot = await context.stopBackgroundMining({
77
214
  dataDir,
78
215
  databasePath: dbPath,
@@ -38,8 +38,6 @@ async function resolveEffectiveWalletRootId(context) {
38
38
  paths: context.resolveWalletRuntimePaths(),
39
39
  provider: context.walletSecretProvider,
40
40
  loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
41
- loadUnlockSession: context.loadUnlockSession,
42
- loadWalletExplicitLock: context.loadWalletExplicitLock,
43
41
  }).catch(() => ({
44
42
  walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
45
43
  source: "default-uninitialized",
@@ -1,17 +1,22 @@
1
1
  import { dirname } from "node:path";
2
2
  import { buildStatusJson } from "../read-json.js";
3
- import { formatWalletOverviewReport } from "../wallet-format.js";
3
+ import { formatBalanceReport, formatWalletOverviewReport } from "../wallet-format.js";
4
4
  import { writeLine } from "../io.js";
5
+ import { createTerminalPrompter } from "../prompt.js";
5
6
  import { createSuccessEnvelope, describeCanonicalCommand, writeJsonValue } from "../output.js";
7
+ import { withInteractiveWalletSecretProvider } from "../../wallet/state/provider.js";
6
8
  export async function runStatusCommand(parsed, context) {
7
9
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
8
10
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
9
11
  const runtimePaths = context.resolveWalletRuntimePaths(parsed.seedName);
10
12
  await context.ensureDirectory(dirname(dbPath));
13
+ const provider = parsed.outputMode === "text"
14
+ ? withInteractiveWalletSecretProvider(context.walletSecretProvider, context.createPrompter?.() ?? createTerminalPrompter(context.stdin, context.stdout))
15
+ : context.walletSecretProvider;
11
16
  const readContext = await context.openWalletReadContext({
12
17
  dataDir,
13
18
  databasePath: dbPath,
14
- secretProvider: context.walletSecretProvider,
19
+ secretProvider: provider,
15
20
  paths: runtimePaths,
16
21
  });
17
22
  try {
@@ -25,6 +30,7 @@ export async function runStatusCommand(parsed, context) {
25
30
  return 0;
26
31
  }
27
32
  writeLine(context.stdout, formatWalletOverviewReport(readContext, await context.readPackageVersion()));
33
+ writeLine(context.stdout, formatBalanceReport(readContext));
28
34
  return 0;
29
35
  }
30
36
  finally {