@cogcoin/client 0.5.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 (289) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/dist/app-paths.d.ts +38 -0
  4. package/dist/app-paths.js +121 -0
  5. package/dist/art/banner.txt +13 -0
  6. package/dist/art/scroll.txt +13 -0
  7. package/dist/art/train-car.txt +6 -0
  8. package/dist/art/train-smoke.txt +6 -0
  9. package/dist/art/train.txt +6 -0
  10. package/dist/bitcoind/bootstrap/chainstate.d.ts +4 -0
  11. package/dist/bitcoind/bootstrap/chainstate.js +13 -0
  12. package/dist/bitcoind/bootstrap/constants.d.ts +7 -0
  13. package/dist/bitcoind/bootstrap/constants.js +12 -0
  14. package/dist/bitcoind/bootstrap/controller.d.ts +29 -0
  15. package/dist/bitcoind/bootstrap/controller.js +101 -0
  16. package/dist/bitcoind/bootstrap/download.d.ts +2 -0
  17. package/dist/bitcoind/bootstrap/download.js +196 -0
  18. package/dist/bitcoind/bootstrap/headers.d.ts +13 -0
  19. package/dist/bitcoind/bootstrap/headers.js +61 -0
  20. package/dist/bitcoind/bootstrap/paths.d.ts +4 -0
  21. package/dist/bitcoind/bootstrap/paths.js +15 -0
  22. package/dist/bitcoind/bootstrap/snapshot-file.d.ts +7 -0
  23. package/dist/bitcoind/bootstrap/snapshot-file.js +42 -0
  24. package/dist/bitcoind/bootstrap/state.d.ts +40 -0
  25. package/dist/bitcoind/bootstrap/state.js +70 -0
  26. package/dist/bitcoind/bootstrap/types.d.ts +28 -0
  27. package/dist/bitcoind/bootstrap/types.js +1 -0
  28. package/dist/bitcoind/bootstrap.d.ts +8 -0
  29. package/dist/bitcoind/bootstrap.js +7 -0
  30. package/dist/bitcoind/client/factory.d.ts +3 -0
  31. package/dist/bitcoind/client/factory.js +57 -0
  32. package/dist/bitcoind/client/follow-block-times.d.ts +8 -0
  33. package/dist/bitcoind/client/follow-block-times.js +25 -0
  34. package/dist/bitcoind/client/follow-loop.d.ts +10 -0
  35. package/dist/bitcoind/client/follow-loop.js +57 -0
  36. package/dist/bitcoind/client/internal-types.d.ts +63 -0
  37. package/dist/bitcoind/client/internal-types.js +18 -0
  38. package/dist/bitcoind/client/managed-client.d.ts +20 -0
  39. package/dist/bitcoind/client/managed-client.js +197 -0
  40. package/dist/bitcoind/client/rate-tracker.d.ts +2 -0
  41. package/dist/bitcoind/client/rate-tracker.js +24 -0
  42. package/dist/bitcoind/client/sync-engine.d.ts +3 -0
  43. package/dist/bitcoind/client/sync-engine.js +143 -0
  44. package/dist/bitcoind/client.d.ts +1 -0
  45. package/dist/bitcoind/client.js +1 -0
  46. package/dist/bitcoind/errors.d.ts +1 -0
  47. package/dist/bitcoind/errors.js +49 -0
  48. package/dist/bitcoind/index.d.ts +2 -0
  49. package/dist/bitcoind/index.js +1 -0
  50. package/dist/bitcoind/indexer-daemon-main.d.ts +1 -0
  51. package/dist/bitcoind/indexer-daemon-main.js +472 -0
  52. package/dist/bitcoind/indexer-daemon.d.ts +107 -0
  53. package/dist/bitcoind/indexer-daemon.js +391 -0
  54. package/dist/bitcoind/node.d.ts +8 -0
  55. package/dist/bitcoind/node.js +219 -0
  56. package/dist/bitcoind/normalize.d.ts +3 -0
  57. package/dist/bitcoind/normalize.js +47 -0
  58. package/dist/bitcoind/progress/assets.d.ts +10 -0
  59. package/dist/bitcoind/progress/assets.js +90 -0
  60. package/dist/bitcoind/progress/constants.d.ts +48 -0
  61. package/dist/bitcoind/progress/constants.js +53 -0
  62. package/dist/bitcoind/progress/controller.d.ts +28 -0
  63. package/dist/bitcoind/progress/controller.js +188 -0
  64. package/dist/bitcoind/progress/follow-scene.d.ts +40 -0
  65. package/dist/bitcoind/progress/follow-scene.js +367 -0
  66. package/dist/bitcoind/progress/formatting.d.ts +23 -0
  67. package/dist/bitcoind/progress/formatting.js +227 -0
  68. package/dist/bitcoind/progress/quote-scene.d.ts +4 -0
  69. package/dist/bitcoind/progress/quote-scene.js +137 -0
  70. package/dist/bitcoind/progress/train-scene.d.ts +9 -0
  71. package/dist/bitcoind/progress/train-scene.js +92 -0
  72. package/dist/bitcoind/progress/tty-renderer.d.ts +18 -0
  73. package/dist/bitcoind/progress/tty-renderer.js +150 -0
  74. package/dist/bitcoind/progress.d.ts +7 -0
  75. package/dist/bitcoind/progress.js +7 -0
  76. package/dist/bitcoind/quotes.d.ts +24 -0
  77. package/dist/bitcoind/quotes.js +195 -0
  78. package/dist/bitcoind/rpc.d.ts +71 -0
  79. package/dist/bitcoind/rpc.js +322 -0
  80. package/dist/bitcoind/service-paths.d.ts +19 -0
  81. package/dist/bitcoind/service-paths.js +49 -0
  82. package/dist/bitcoind/service.d.ts +40 -0
  83. package/dist/bitcoind/service.js +735 -0
  84. package/dist/bitcoind/testing.d.ts +9 -0
  85. package/dist/bitcoind/testing.js +9 -0
  86. package/dist/bitcoind/types.d.ts +396 -0
  87. package/dist/bitcoind/types.js +3 -0
  88. package/dist/bytes.d.ts +9 -0
  89. package/dist/bytes.js +36 -0
  90. package/dist/cli/commands/follow.d.ts +2 -0
  91. package/dist/cli/commands/follow.js +43 -0
  92. package/dist/cli/commands/mining-admin.d.ts +2 -0
  93. package/dist/cli/commands/mining-admin.js +92 -0
  94. package/dist/cli/commands/mining-read.d.ts +2 -0
  95. package/dist/cli/commands/mining-read.js +173 -0
  96. package/dist/cli/commands/mining-runtime.d.ts +2 -0
  97. package/dist/cli/commands/mining-runtime.js +108 -0
  98. package/dist/cli/commands/status.d.ts +2 -0
  99. package/dist/cli/commands/status.js +31 -0
  100. package/dist/cli/commands/sync.d.ts +2 -0
  101. package/dist/cli/commands/sync.js +52 -0
  102. package/dist/cli/commands/wallet-admin.d.ts +2 -0
  103. package/dist/cli/commands/wallet-admin.js +175 -0
  104. package/dist/cli/commands/wallet-mutation.d.ts +2 -0
  105. package/dist/cli/commands/wallet-mutation.js +681 -0
  106. package/dist/cli/commands/wallet-read.d.ts +2 -0
  107. package/dist/cli/commands/wallet-read.js +265 -0
  108. package/dist/cli/context.d.ts +3 -0
  109. package/dist/cli/context.js +75 -0
  110. package/dist/cli/io.d.ts +3 -0
  111. package/dist/cli/io.js +12 -0
  112. package/dist/cli/mining-format.d.ts +5 -0
  113. package/dist/cli/mining-format.js +156 -0
  114. package/dist/cli/mining-json.d.ts +49 -0
  115. package/dist/cli/mining-json.js +89 -0
  116. package/dist/cli/mutation-command-groups.d.ts +15 -0
  117. package/dist/cli/mutation-command-groups.js +71 -0
  118. package/dist/cli/mutation-json.d.ts +430 -0
  119. package/dist/cli/mutation-json.js +311 -0
  120. package/dist/cli/mutation-resolved-json.d.ts +124 -0
  121. package/dist/cli/mutation-resolved-json.js +129 -0
  122. package/dist/cli/mutation-success.d.ts +20 -0
  123. package/dist/cli/mutation-success.js +47 -0
  124. package/dist/cli/mutation-text-format.d.ts +22 -0
  125. package/dist/cli/mutation-text-format.js +171 -0
  126. package/dist/cli/mutation-text-write.d.ts +13 -0
  127. package/dist/cli/mutation-text-write.js +16 -0
  128. package/dist/cli/output.d.ts +185 -0
  129. package/dist/cli/output.js +1085 -0
  130. package/dist/cli/parse.d.ts +3 -0
  131. package/dist/cli/parse.js +971 -0
  132. package/dist/cli/preview-json.d.ts +416 -0
  133. package/dist/cli/preview-json.js +293 -0
  134. package/dist/cli/prompt.d.ts +3 -0
  135. package/dist/cli/prompt.js +33 -0
  136. package/dist/cli/read-json.d.ts +187 -0
  137. package/dist/cli/read-json.js +675 -0
  138. package/dist/cli/runner.d.ts +2 -0
  139. package/dist/cli/runner.js +129 -0
  140. package/dist/cli/signals.d.ts +3 -0
  141. package/dist/cli/signals.js +63 -0
  142. package/dist/cli/status-format.d.ts +2 -0
  143. package/dist/cli/status-format.js +48 -0
  144. package/dist/cli/types.d.ts +148 -0
  145. package/dist/cli/types.js +2 -0
  146. package/dist/cli/wallet-format.d.ts +29 -0
  147. package/dist/cli/wallet-format.js +637 -0
  148. package/dist/cli/workflow-hints.d.ts +13 -0
  149. package/dist/cli/workflow-hints.js +94 -0
  150. package/dist/cli-runner.d.ts +3 -0
  151. package/dist/cli-runner.js +3 -0
  152. package/dist/cli.d.ts +2 -0
  153. package/dist/cli.js +6 -0
  154. package/dist/client/default-client.d.ts +11 -0
  155. package/dist/client/default-client.js +118 -0
  156. package/dist/client/factory.d.ts +2 -0
  157. package/dist/client/factory.js +15 -0
  158. package/dist/client/initialization.d.ts +6 -0
  159. package/dist/client/initialization.js +30 -0
  160. package/dist/client/persistence.d.ts +5 -0
  161. package/dist/client/persistence.js +28 -0
  162. package/dist/client/store-adapter.d.ts +3 -0
  163. package/dist/client/store-adapter.js +20 -0
  164. package/dist/client.d.ts +2 -0
  165. package/dist/client.js +2 -0
  166. package/dist/index.d.ts +2 -0
  167. package/dist/index.js +1 -0
  168. package/dist/passive-status.d.ts +36 -0
  169. package/dist/passive-status.js +100 -0
  170. package/dist/sqlite/better-sqlite3.d.ts +26 -0
  171. package/dist/sqlite/better-sqlite3.js +4 -0
  172. package/dist/sqlite/checkpoints.d.ts +11 -0
  173. package/dist/sqlite/checkpoints.js +27 -0
  174. package/dist/sqlite/driver.d.ts +17 -0
  175. package/dist/sqlite/driver.js +98 -0
  176. package/dist/sqlite/index.d.ts +4 -0
  177. package/dist/sqlite/index.js +9 -0
  178. package/dist/sqlite/migrate.d.ts +2 -0
  179. package/dist/sqlite/migrate.js +37 -0
  180. package/dist/sqlite/store.d.ts +3 -0
  181. package/dist/sqlite/store.js +122 -0
  182. package/dist/sqlite/tip-meta.d.ts +26 -0
  183. package/dist/sqlite/tip-meta.js +97 -0
  184. package/dist/sqlite/types.d.ts +10 -0
  185. package/dist/sqlite/types.js +1 -0
  186. package/dist/types.d.ts +55 -0
  187. package/dist/types.js +1 -0
  188. package/dist/wallet/archive.d.ts +4 -0
  189. package/dist/wallet/archive.js +39 -0
  190. package/dist/wallet/cogop/constants.d.ts +32 -0
  191. package/dist/wallet/cogop/constants.js +32 -0
  192. package/dist/wallet/cogop/index.d.ts +32 -0
  193. package/dist/wallet/cogop/index.js +213 -0
  194. package/dist/wallet/cogop/numeric.d.ts +3 -0
  195. package/dist/wallet/cogop/numeric.js +24 -0
  196. package/dist/wallet/cogop/scriptpubkey.d.ts +2 -0
  197. package/dist/wallet/cogop/scriptpubkey.js +13 -0
  198. package/dist/wallet/cogop/validate-name.d.ts +2 -0
  199. package/dist/wallet/cogop/validate-name.js +18 -0
  200. package/dist/wallet/fs/atomic.d.ts +6 -0
  201. package/dist/wallet/fs/atomic.js +46 -0
  202. package/dist/wallet/fs/lock.d.ts +19 -0
  203. package/dist/wallet/fs/lock.js +61 -0
  204. package/dist/wallet/fs/status-file.d.ts +1 -0
  205. package/dist/wallet/fs/status-file.js +4 -0
  206. package/dist/wallet/lifecycle.d.ts +193 -0
  207. package/dist/wallet/lifecycle.js +1475 -0
  208. package/dist/wallet/material.d.ts +45 -0
  209. package/dist/wallet/material.js +118 -0
  210. package/dist/wallet/mining/config.d.ts +18 -0
  211. package/dist/wallet/mining/config.js +44 -0
  212. package/dist/wallet/mining/constants.d.ts +24 -0
  213. package/dist/wallet/mining/constants.js +24 -0
  214. package/dist/wallet/mining/control.d.ts +53 -0
  215. package/dist/wallet/mining/control.js +758 -0
  216. package/dist/wallet/mining/coordination.d.ts +40 -0
  217. package/dist/wallet/mining/coordination.js +121 -0
  218. package/dist/wallet/mining/hook-protocol.d.ts +47 -0
  219. package/dist/wallet/mining/hook-protocol.js +161 -0
  220. package/dist/wallet/mining/hook-runner.d.ts +1 -0
  221. package/dist/wallet/mining/hook-runner.js +52 -0
  222. package/dist/wallet/mining/hooks.d.ts +38 -0
  223. package/dist/wallet/mining/hooks.js +520 -0
  224. package/dist/wallet/mining/index.d.ts +8 -0
  225. package/dist/wallet/mining/index.js +6 -0
  226. package/dist/wallet/mining/runner.d.ts +155 -0
  227. package/dist/wallet/mining/runner.js +2574 -0
  228. package/dist/wallet/mining/runtime-artifacts.d.ts +17 -0
  229. package/dist/wallet/mining/runtime-artifacts.js +166 -0
  230. package/dist/wallet/mining/sentences.d.ts +23 -0
  231. package/dist/wallet/mining/sentences.js +281 -0
  232. package/dist/wallet/mining/state.d.ts +9 -0
  233. package/dist/wallet/mining/state.js +75 -0
  234. package/dist/wallet/mining/types.d.ts +141 -0
  235. package/dist/wallet/mining/types.js +1 -0
  236. package/dist/wallet/mining/visualizer.d.ts +19 -0
  237. package/dist/wallet/mining/visualizer.js +134 -0
  238. package/dist/wallet/mining/worker-main.d.ts +1 -0
  239. package/dist/wallet/mining/worker-main.js +17 -0
  240. package/dist/wallet/read/context.d.ts +20 -0
  241. package/dist/wallet/read/context.js +532 -0
  242. package/dist/wallet/read/filter.d.ts +9 -0
  243. package/dist/wallet/read/filter.js +42 -0
  244. package/dist/wallet/read/index.d.ts +4 -0
  245. package/dist/wallet/read/index.js +3 -0
  246. package/dist/wallet/read/project.d.ts +11 -0
  247. package/dist/wallet/read/project.js +300 -0
  248. package/dist/wallet/read/types.d.ts +144 -0
  249. package/dist/wallet/read/types.js +1 -0
  250. package/dist/wallet/runtime.d.ts +26 -0
  251. package/dist/wallet/runtime.js +28 -0
  252. package/dist/wallet/state/crypto.d.ts +31 -0
  253. package/dist/wallet/state/crypto.js +127 -0
  254. package/dist/wallet/state/provider.d.ts +37 -0
  255. package/dist/wallet/state/provider.js +312 -0
  256. package/dist/wallet/state/session.d.ts +12 -0
  257. package/dist/wallet/state/session.js +23 -0
  258. package/dist/wallet/state/storage.d.ts +19 -0
  259. package/dist/wallet/state/storage.js +55 -0
  260. package/dist/wallet/tx/anchor.d.ts +40 -0
  261. package/dist/wallet/tx/anchor.js +1210 -0
  262. package/dist/wallet/tx/cog.d.ts +92 -0
  263. package/dist/wallet/tx/cog.js +1055 -0
  264. package/dist/wallet/tx/common.d.ts +89 -0
  265. package/dist/wallet/tx/common.js +156 -0
  266. package/dist/wallet/tx/confirm.d.ts +15 -0
  267. package/dist/wallet/tx/confirm.js +24 -0
  268. package/dist/wallet/tx/domain-admin.d.ts +105 -0
  269. package/dist/wallet/tx/domain-admin.js +869 -0
  270. package/dist/wallet/tx/domain-market.d.ts +112 -0
  271. package/dist/wallet/tx/domain-market.js +1365 -0
  272. package/dist/wallet/tx/field.d.ts +101 -0
  273. package/dist/wallet/tx/field.js +1853 -0
  274. package/dist/wallet/tx/identity-selector.d.ts +12 -0
  275. package/dist/wallet/tx/identity-selector.js +52 -0
  276. package/dist/wallet/tx/index.d.ts +7 -0
  277. package/dist/wallet/tx/index.js +7 -0
  278. package/dist/wallet/tx/journal.d.ts +5 -0
  279. package/dist/wallet/tx/journal.js +31 -0
  280. package/dist/wallet/tx/register.d.ts +68 -0
  281. package/dist/wallet/tx/register.js +952 -0
  282. package/dist/wallet/tx/reputation.d.ts +72 -0
  283. package/dist/wallet/tx/reputation.js +693 -0
  284. package/dist/wallet/tx/targets.d.ts +7 -0
  285. package/dist/wallet/tx/targets.js +122 -0
  286. package/dist/wallet/types.d.ts +249 -0
  287. package/dist/wallet/types.js +1 -0
  288. package/dist/writing_quotes.json +1654 -0
  289. package/package.json +78 -0
@@ -0,0 +1,735 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { execFile, spawn } from "node:child_process";
3
+ import { access, constants, mkdir, readFile, readdir, rm } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import net from "node:net";
7
+ import { getBitcoindPath } from "@cogcoin/bitcoin";
8
+ import { acquireFileLock, FileLockBusyError } from "../wallet/fs/lock.js";
9
+ import { writeFileAtomic } from "../wallet/fs/atomic.js";
10
+ import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
11
+ import { createRpcClient, validateNodeConfigForTesting } from "./node.js";
12
+ import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
13
+ import { MANAGED_BITCOIND_SERVICE_API_VERSION as MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE } from "./types.js";
14
+ const execFileAsync = promisify(execFile);
15
+ const LOCAL_HOST = "127.0.0.1";
16
+ const SUPPORTED_BITCOIND_VERSION = "30.2.0";
17
+ const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
18
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 15_000;
19
+ function sleep(ms) {
20
+ return new Promise((resolve) => {
21
+ setTimeout(resolve, ms);
22
+ });
23
+ }
24
+ function getWalletReplicaName(walletRootId) {
25
+ return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
26
+ }
27
+ async function readJsonFile(filePath) {
28
+ try {
29
+ return JSON.parse(await readFile(filePath, "utf8"));
30
+ }
31
+ catch (error) {
32
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
33
+ return null;
34
+ }
35
+ throw error;
36
+ }
37
+ }
38
+ async function listManagedBitcoindStatusCandidates(options) {
39
+ const candidates = new Map();
40
+ const addCandidate = async (statusPath, allowDataDirMismatch = false) => {
41
+ const status = await readJsonFile(statusPath);
42
+ if (status === null) {
43
+ return;
44
+ }
45
+ if (!allowDataDirMismatch && status.dataDir !== options.dataDir) {
46
+ return;
47
+ }
48
+ candidates.set(statusPath, status);
49
+ };
50
+ await addCandidate(options.expectedStatusPath, true);
51
+ try {
52
+ const entries = await readdir(options.runtimeRoot, {
53
+ withFileTypes: true,
54
+ });
55
+ for (const entry of entries) {
56
+ if (!entry.isDirectory()) {
57
+ continue;
58
+ }
59
+ const statusPath = join(options.runtimeRoot, entry.name, "bitcoind-status.json");
60
+ if (statusPath === options.expectedStatusPath) {
61
+ continue;
62
+ }
63
+ await addCandidate(statusPath);
64
+ }
65
+ }
66
+ catch {
67
+ // Missing runtime roots are handled by returning no candidates.
68
+ }
69
+ return [...candidates.entries()].map(([statusPath, status]) => ({
70
+ statusPath,
71
+ status,
72
+ }));
73
+ }
74
+ async function allocatePort() {
75
+ return new Promise((resolve, reject) => {
76
+ const server = net.createServer();
77
+ server.listen(0, LOCAL_HOST, () => {
78
+ const address = server.address();
79
+ if (!address || typeof address === "string") {
80
+ server.close();
81
+ reject(new Error("bitcoind_port_allocation_failed"));
82
+ return;
83
+ }
84
+ const { port } = address;
85
+ server.close((error) => {
86
+ if (error) {
87
+ reject(error);
88
+ return;
89
+ }
90
+ resolve(port);
91
+ });
92
+ });
93
+ server.on("error", reject);
94
+ });
95
+ }
96
+ async function allocateDistinctPort(reserved) {
97
+ while (true) {
98
+ const port = await allocatePort();
99
+ if (!reserved.has(port)) {
100
+ reserved.add(port);
101
+ return port;
102
+ }
103
+ }
104
+ }
105
+ async function verifyBitcoindVersion(bitcoindPath) {
106
+ const { stdout } = await execFileAsync(bitcoindPath, ["-nosettings=1", "-version"]);
107
+ if (!stdout.includes("Bitcoin Core") || !stdout.includes(`v${SUPPORTED_BITCOIND_VERSION}`)) {
108
+ throw new Error("bitcoind_version_unsupported");
109
+ }
110
+ }
111
+ function getCookieFile(dataDir, chain) {
112
+ return chain === "main" ? join(dataDir, ".cookie") : join(dataDir, chain, ".cookie");
113
+ }
114
+ function createMissingManagedWalletReplicaStatus(walletRootId, message) {
115
+ return {
116
+ walletRootId,
117
+ walletName: getWalletReplicaName(walletRootId),
118
+ loaded: false,
119
+ descriptors: false,
120
+ privateKeysEnabled: false,
121
+ created: false,
122
+ proofStatus: "missing",
123
+ descriptorChecksum: null,
124
+ fundingAddress0: null,
125
+ fundingScriptPubKeyHex0: null,
126
+ message,
127
+ };
128
+ }
129
+ async function isProcessAlive(pid) {
130
+ if (pid === null) {
131
+ return false;
132
+ }
133
+ try {
134
+ process.kill(pid, 0);
135
+ return true;
136
+ }
137
+ catch (error) {
138
+ if (error instanceof Error && "code" in error && error.code === "ESRCH") {
139
+ return false;
140
+ }
141
+ return true;
142
+ }
143
+ }
144
+ async function waitForCookie(cookieFile, timeoutMs) {
145
+ const deadline = Date.now() + timeoutMs;
146
+ while (Date.now() < deadline) {
147
+ try {
148
+ await access(cookieFile, constants.R_OK);
149
+ return;
150
+ }
151
+ catch {
152
+ await sleep(250);
153
+ }
154
+ }
155
+ throw new Error("bitcoind_cookie_timeout");
156
+ }
157
+ async function waitForRpcReady(rpc, cookieFile, expectedChain, timeoutMs) {
158
+ await waitForCookie(cookieFile, timeoutMs);
159
+ const deadline = Date.now() + timeoutMs;
160
+ let lastError = null;
161
+ while (Date.now() < deadline) {
162
+ try {
163
+ const info = await rpc.getBlockchainInfo();
164
+ if (info.chain !== expectedChain) {
165
+ throw new Error(`bitcoind_chain_expected_${expectedChain}_got_${info.chain}`);
166
+ }
167
+ return;
168
+ }
169
+ catch (error) {
170
+ lastError = error;
171
+ await sleep(250);
172
+ }
173
+ }
174
+ throw lastError instanceof Error ? lastError : new Error("bitcoind_rpc_timeout");
175
+ }
176
+ function validateManagedBitcoindStatus(status, options, runtimeRoot) {
177
+ if (status.serviceApiVersion !== MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE) {
178
+ throw new Error("managed_bitcoind_service_version_mismatch");
179
+ }
180
+ if (status.walletRootId !== (options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID)) {
181
+ throw new Error("managed_bitcoind_wallet_root_mismatch");
182
+ }
183
+ if (status.chain !== options.chain
184
+ || status.dataDir !== (options.dataDir ?? "")
185
+ || status.runtimeRoot !== runtimeRoot) {
186
+ throw new Error("managed_bitcoind_runtime_mismatch");
187
+ }
188
+ }
189
+ function isRuntimeMismatchError(error) {
190
+ if (!(error instanceof Error)) {
191
+ return false;
192
+ }
193
+ return error.message.startsWith("bitcoind_chain_expected_")
194
+ || error.message === "managed_bitcoind_runtime_mismatch";
195
+ }
196
+ function isUnreachableManagedBitcoindError(error) {
197
+ if (error instanceof Error) {
198
+ if ("code" in error) {
199
+ const code = error.code;
200
+ return code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET";
201
+ }
202
+ return error.message === "bitcoind_cookie_timeout"
203
+ || error.message.includes("cookie file is unavailable")
204
+ || error.message.includes("ECONNREFUSED")
205
+ || error.message.includes("ECONNRESET")
206
+ || error.message.includes("socket hang up");
207
+ }
208
+ return false;
209
+ }
210
+ function createBitcoindServiceStatus(options) {
211
+ return {
212
+ serviceApiVersion: MANAGED_BITCOIND_SERVICE_API_VERSION_VALUE,
213
+ binaryVersion: options.binaryVersion,
214
+ buildId: null,
215
+ serviceInstanceId: options.serviceInstanceId,
216
+ state: options.state,
217
+ processId: options.processId,
218
+ walletRootId: options.walletRootId,
219
+ chain: options.chain,
220
+ dataDir: options.dataDir,
221
+ runtimeRoot: options.runtimeRoot,
222
+ startHeight: options.startHeight,
223
+ rpc: options.rpc,
224
+ zmq: options.zmq,
225
+ p2pPort: options.p2pPort,
226
+ walletReplica: options.walletReplica,
227
+ startedAtUnixMs: options.startedAtUnixMs,
228
+ heartbeatAtUnixMs: options.heartbeatAtUnixMs,
229
+ updatedAtUnixMs: options.heartbeatAtUnixMs,
230
+ lastError: options.lastError,
231
+ };
232
+ }
233
+ function mapManagedBitcoindValidationError(error) {
234
+ return {
235
+ compatibility: error instanceof Error
236
+ ? error.message === "managed_bitcoind_service_version_mismatch"
237
+ ? "service-version-mismatch"
238
+ : error.message === "managed_bitcoind_wallet_root_mismatch"
239
+ ? "wallet-root-mismatch"
240
+ : "runtime-mismatch"
241
+ : "protocol-error",
242
+ status: null,
243
+ error: error instanceof Error ? error.message : "managed_bitcoind_protocol_error",
244
+ };
245
+ }
246
+ async function probeManagedBitcoindStatusCandidate(status, options, runtimeRoot) {
247
+ try {
248
+ validateManagedBitcoindStatus(status, options, runtimeRoot);
249
+ }
250
+ catch (error) {
251
+ const mapped = mapManagedBitcoindValidationError(error);
252
+ return {
253
+ ...mapped,
254
+ status,
255
+ };
256
+ }
257
+ const rpc = createRpcClient(status.rpc);
258
+ try {
259
+ await waitForRpcReady(rpc, status.rpc.cookieFile, status.chain, options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS);
260
+ await validateNodeConfigForTesting(rpc, status.chain, status.zmq.endpoint);
261
+ return {
262
+ compatibility: "compatible",
263
+ status,
264
+ error: null,
265
+ };
266
+ }
267
+ catch (error) {
268
+ if (isRuntimeMismatchError(error)) {
269
+ return {
270
+ compatibility: "runtime-mismatch",
271
+ status,
272
+ error: "managed_bitcoind_runtime_mismatch",
273
+ };
274
+ }
275
+ if (isUnreachableManagedBitcoindError(error)) {
276
+ return {
277
+ compatibility: "unreachable",
278
+ status,
279
+ error: null,
280
+ };
281
+ }
282
+ return {
283
+ compatibility: "protocol-error",
284
+ status,
285
+ error: "managed_bitcoind_protocol_error",
286
+ };
287
+ }
288
+ }
289
+ async function resolveRuntimeConfig(statusPath, configPath, options) {
290
+ const previousStatus = await readJsonFile(statusPath);
291
+ const previousConfig = await readJsonFile(configPath);
292
+ const reserved = new Set();
293
+ const rpcPort = options.rpcPort
294
+ ?? previousStatus?.rpc.port
295
+ ?? previousConfig?.rpc?.port
296
+ ?? await allocateDistinctPort(reserved);
297
+ reserved.add(rpcPort);
298
+ const zmqPort = options.zmqPort
299
+ ?? previousStatus?.zmq.port
300
+ ?? previousConfig?.zmqPort
301
+ ?? await allocateDistinctPort(reserved);
302
+ reserved.add(zmqPort);
303
+ const p2pPort = options.p2pPort
304
+ ?? previousStatus?.p2pPort
305
+ ?? previousConfig?.p2pPort
306
+ ?? await allocateDistinctPort(reserved);
307
+ return {
308
+ chain: options.chain,
309
+ rpc: {
310
+ url: `http://${LOCAL_HOST}:${rpcPort}`,
311
+ cookieFile: getCookieFile(options.dataDir ?? "", options.chain),
312
+ port: rpcPort,
313
+ },
314
+ zmqPort,
315
+ p2pPort,
316
+ };
317
+ }
318
+ async function writeBitcoinConf(filePath, options, runtimeConfig) {
319
+ const walletDir = join(options.dataDir ?? "", "wallets");
320
+ await mkdir(dirname(filePath), { recursive: true });
321
+ await mkdir(walletDir, { recursive: true });
322
+ const lines = [
323
+ "server=1",
324
+ "prune=0",
325
+ "dnsseed=1",
326
+ "listen=0",
327
+ `rpcbind=${LOCAL_HOST}`,
328
+ `rpcallowip=${LOCAL_HOST}`,
329
+ `rpcport=${runtimeConfig.rpc.port}`,
330
+ `port=${runtimeConfig.p2pPort}`,
331
+ `zmqpubhashblock=tcp://${LOCAL_HOST}:${runtimeConfig.zmqPort}`,
332
+ `walletdir=${walletDir}`,
333
+ ];
334
+ await writeFileAtomic(filePath, `${lines.join("\n")}\n`, { mode: 0o600 });
335
+ }
336
+ function buildManagedServiceArgs(options, runtimeConfig) {
337
+ const walletDir = join(options.dataDir ?? "", "wallets");
338
+ const args = [
339
+ "-nosettings=1",
340
+ `-datadir=${options.dataDir}`,
341
+ `-rpcbind=${LOCAL_HOST}`,
342
+ `-rpcallowip=${LOCAL_HOST}`,
343
+ `-rpcport=${runtimeConfig.rpc.port}`,
344
+ `-port=${runtimeConfig.p2pPort}`,
345
+ `-zmqpubhashblock=tcp://${LOCAL_HOST}:${runtimeConfig.zmqPort}`,
346
+ `-walletdir=${walletDir}`,
347
+ "-server=1",
348
+ "-prune=0",
349
+ "-dnsseed=1",
350
+ "-listen=0",
351
+ ];
352
+ if (options.chain === "regtest") {
353
+ args.push("-chain=regtest");
354
+ }
355
+ return args;
356
+ }
357
+ function isMissingWalletError(message) {
358
+ return message.includes("bitcoind_rpc_loadwallet_-18_")
359
+ || message.includes("Path does not exist")
360
+ || message.includes("not found");
361
+ }
362
+ async function loadManagedWalletReplicaIfPresent(rpc, walletRootId, dataDir) {
363
+ const walletName = getWalletReplicaName(walletRootId);
364
+ const loadedWallets = await rpc.listWallets();
365
+ let loaded = loadedWallets.includes(walletName);
366
+ if (!loaded) {
367
+ try {
368
+ await rpc.loadWallet(walletName, false);
369
+ loaded = true;
370
+ }
371
+ catch (error) {
372
+ const message = error instanceof Error ? error.message : String(error);
373
+ if (!isMissingWalletError(message)) {
374
+ return {
375
+ walletRootId,
376
+ walletName,
377
+ loaded: false,
378
+ descriptors: false,
379
+ privateKeysEnabled: false,
380
+ created: false,
381
+ proofStatus: "mismatch",
382
+ descriptorChecksum: null,
383
+ fundingAddress0: null,
384
+ fundingScriptPubKeyHex0: null,
385
+ message,
386
+ };
387
+ }
388
+ const walletDir = join(dataDir, "wallets", walletName);
389
+ const walletDirExists = await access(walletDir, constants.F_OK).then(() => true).catch(() => false);
390
+ return createMissingManagedWalletReplicaStatus(walletRootId, walletDirExists
391
+ ? "Managed Core wallet replica exists on disk but is not loaded."
392
+ : "Managed Core wallet replica is missing.");
393
+ }
394
+ }
395
+ const info = await rpc.getWalletInfo(walletName);
396
+ if (!info.descriptors || !info.private_keys_enabled) {
397
+ return {
398
+ walletRootId,
399
+ walletName,
400
+ loaded: true,
401
+ descriptors: info.descriptors,
402
+ privateKeysEnabled: info.private_keys_enabled,
403
+ created: false,
404
+ proofStatus: "mismatch",
405
+ descriptorChecksum: null,
406
+ fundingAddress0: null,
407
+ fundingScriptPubKeyHex0: null,
408
+ message: "Managed Core wallet replica is not an encrypted descriptor wallet with private keys enabled.",
409
+ };
410
+ }
411
+ try {
412
+ await rpc.walletLock(walletName);
413
+ }
414
+ catch {
415
+ // A freshly created encrypted wallet may already be locked.
416
+ }
417
+ return {
418
+ walletRootId,
419
+ walletName,
420
+ loaded: true,
421
+ descriptors: info.descriptors,
422
+ privateKeysEnabled: info.private_keys_enabled,
423
+ created: false,
424
+ proofStatus: "not-proven",
425
+ descriptorChecksum: null,
426
+ fundingAddress0: null,
427
+ fundingScriptPubKeyHex0: null,
428
+ message: null,
429
+ };
430
+ }
431
+ export async function createManagedWalletReplica(rpc, walletRootId, options = {}) {
432
+ const walletName = getWalletReplicaName(walletRootId);
433
+ const loadedWallets = await rpc.listWallets();
434
+ let created = false;
435
+ if (!loadedWallets.includes(walletName)) {
436
+ try {
437
+ await rpc.loadWallet(walletName, false);
438
+ }
439
+ catch (error) {
440
+ const message = error instanceof Error ? error.message : String(error);
441
+ if (!isMissingWalletError(message)) {
442
+ throw error;
443
+ }
444
+ await rpc.createWallet(walletName, {
445
+ blank: true,
446
+ descriptors: true,
447
+ disablePrivateKeys: false,
448
+ loadOnStartup: false,
449
+ passphrase: options.managedWalletPassphrase ?? randomBytes(32).toString("hex"),
450
+ });
451
+ created = true;
452
+ }
453
+ }
454
+ const info = await rpc.getWalletInfo(walletName);
455
+ if (!info.descriptors || !info.private_keys_enabled) {
456
+ throw new Error("managed_bitcoind_wallet_replica_invalid");
457
+ }
458
+ try {
459
+ await rpc.walletLock(walletName);
460
+ }
461
+ catch {
462
+ // A freshly created encrypted wallet may already be locked.
463
+ }
464
+ return {
465
+ walletRootId,
466
+ walletName,
467
+ loaded: true,
468
+ descriptors: info.descriptors,
469
+ privateKeysEnabled: info.private_keys_enabled,
470
+ created,
471
+ proofStatus: "not-proven",
472
+ descriptorChecksum: null,
473
+ fundingAddress0: null,
474
+ fundingScriptPubKeyHex0: null,
475
+ message: null,
476
+ };
477
+ }
478
+ async function writeBitcoindStatus(paths, status) {
479
+ await mkdir(paths.walletRuntimeRoot, { recursive: true });
480
+ await writeRuntimeStatusFile(paths.bitcoindStatusPath, status);
481
+ await writeFileAtomic(paths.bitcoindPidPath, `${status.processId ?? ""}\n`, { mode: 0o600 });
482
+ await writeFileAtomic(paths.bitcoindReadyPath, `${status.updatedAtUnixMs}\n`, { mode: 0o600 });
483
+ await writeRuntimeStatusFile(paths.bitcoindWalletStatusPath, status.walletReplica ?? createMissingManagedWalletReplicaStatus(status.walletRootId, "Managed Core wallet replica is missing."));
484
+ await writeRuntimeStatusFile(paths.bitcoindRuntimeConfigPath, {
485
+ chain: status.chain,
486
+ rpc: status.rpc,
487
+ zmqPort: status.zmq.port,
488
+ p2pPort: status.p2pPort,
489
+ });
490
+ }
491
+ async function refreshManagedBitcoindStatus(status, paths, options) {
492
+ const nowUnixMs = Date.now();
493
+ const rpc = createRpcClient(status.rpc);
494
+ try {
495
+ await waitForRpcReady(rpc, status.rpc.cookieFile, status.chain, options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS);
496
+ await validateNodeConfigForTesting(rpc, status.chain, status.zmq.endpoint);
497
+ const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, status.walletRootId, status.dataDir);
498
+ const nextStatus = {
499
+ ...status,
500
+ state: "ready",
501
+ processId: await isProcessAlive(status.processId) ? status.processId : null,
502
+ walletReplica,
503
+ heartbeatAtUnixMs: nowUnixMs,
504
+ updatedAtUnixMs: nowUnixMs,
505
+ lastError: walletReplica.message ?? null,
506
+ };
507
+ await writeBitcoindStatus(paths, nextStatus);
508
+ return nextStatus;
509
+ }
510
+ catch (error) {
511
+ const nextStatus = {
512
+ ...status,
513
+ state: "failed",
514
+ processId: await isProcessAlive(status.processId) ? status.processId : null,
515
+ heartbeatAtUnixMs: nowUnixMs,
516
+ updatedAtUnixMs: nowUnixMs,
517
+ lastError: error instanceof Error ? error.message : String(error),
518
+ };
519
+ await writeBitcoindStatus(paths, nextStatus);
520
+ return nextStatus;
521
+ }
522
+ }
523
+ function createNodeHandle(status, paths, options) {
524
+ let currentStatus = status;
525
+ const rpc = createRpcClient(currentStatus.rpc);
526
+ return {
527
+ rpc: currentStatus.rpc,
528
+ zmq: currentStatus.zmq,
529
+ pid: currentStatus.processId,
530
+ expectedChain: currentStatus.chain,
531
+ startHeight: currentStatus.startHeight,
532
+ dataDir: currentStatus.dataDir,
533
+ walletRootId: currentStatus.walletRootId,
534
+ runtimeRoot: paths.walletRuntimeRoot,
535
+ async validate() {
536
+ await validateNodeConfigForTesting(rpc, currentStatus.chain, currentStatus.zmq.endpoint);
537
+ },
538
+ async refreshServiceStatus() {
539
+ currentStatus = await refreshManagedBitcoindStatus(currentStatus, paths, options);
540
+ return currentStatus;
541
+ },
542
+ async stop() {
543
+ // Public managed clients detach from the persistent service instead of
544
+ // shutting it down on ordinary command exit.
545
+ return;
546
+ },
547
+ };
548
+ }
549
+ async function tryAttachExistingManagedBitcoindService(options) {
550
+ const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
551
+ const paths = resolveManagedServicePaths(options.dataDir ?? "", walletRootId);
552
+ const probe = await probeManagedBitcoindService(options);
553
+ if (probe.compatibility !== "compatible" || probe.status === null) {
554
+ return null;
555
+ }
556
+ const refreshed = await refreshManagedBitcoindStatus(probe.status, paths, options);
557
+ return createNodeHandle(refreshed, paths, options);
558
+ }
559
+ async function waitForManagedBitcoindService(options, timeoutMs) {
560
+ const deadline = Date.now() + timeoutMs;
561
+ while (Date.now() < deadline) {
562
+ const attached = await tryAttachExistingManagedBitcoindService(options).catch(() => null);
563
+ if (attached !== null) {
564
+ return attached;
565
+ }
566
+ await sleep(250);
567
+ }
568
+ throw new Error("managed_bitcoind_service_start_timeout");
569
+ }
570
+ export async function probeManagedBitcoindService(options) {
571
+ const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
572
+ const paths = resolveManagedServicePaths(options.dataDir ?? "", walletRootId);
573
+ const candidates = await listManagedBitcoindStatusCandidates({
574
+ dataDir: options.dataDir ?? "",
575
+ runtimeRoot: paths.runtimeRoot,
576
+ expectedStatusPath: paths.bitcoindStatusPath,
577
+ });
578
+ const expectedCandidate = candidates.find((candidate) => candidate.statusPath === paths.bitcoindStatusPath) ?? null;
579
+ for (const candidate of candidates) {
580
+ if (!await isProcessAlive(candidate.status.processId)) {
581
+ continue;
582
+ }
583
+ return probeManagedBitcoindStatusCandidate(candidate.status, options, paths.walletRuntimeRoot);
584
+ }
585
+ if (expectedCandidate !== null) {
586
+ return {
587
+ compatibility: "unreachable",
588
+ status: expectedCandidate.status,
589
+ error: null,
590
+ };
591
+ }
592
+ return {
593
+ compatibility: "unreachable",
594
+ status: candidates[0]?.status ?? null,
595
+ error: null,
596
+ };
597
+ }
598
+ export async function attachOrStartManagedBitcoindService(options) {
599
+ const resolvedOptions = {
600
+ ...options,
601
+ dataDir: options.dataDir,
602
+ walletRootId: options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID,
603
+ };
604
+ const existingProbe = await probeManagedBitcoindService(resolvedOptions);
605
+ if (existingProbe.compatibility === "compatible") {
606
+ const existing = await tryAttachExistingManagedBitcoindService(resolvedOptions);
607
+ if (existing !== null) {
608
+ return existing;
609
+ }
610
+ }
611
+ if (existingProbe.compatibility !== "unreachable") {
612
+ throw new Error(existingProbe.error ?? "managed_bitcoind_protocol_error");
613
+ }
614
+ const paths = resolveManagedServicePaths(resolvedOptions.dataDir ?? "", resolvedOptions.walletRootId);
615
+ const startupTimeoutMs = resolvedOptions.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
616
+ try {
617
+ const lock = await acquireFileLock(paths.bitcoindLockPath, {
618
+ purpose: "managed-bitcoind-start",
619
+ walletRootId: resolvedOptions.walletRootId,
620
+ dataDir: resolvedOptions.dataDir,
621
+ });
622
+ try {
623
+ const liveProbe = await probeManagedBitcoindService(resolvedOptions);
624
+ if (liveProbe.compatibility === "compatible") {
625
+ const reattached = await tryAttachExistingManagedBitcoindService(resolvedOptions);
626
+ if (reattached !== null) {
627
+ return reattached;
628
+ }
629
+ }
630
+ if (liveProbe.compatibility !== "unreachable") {
631
+ throw new Error(liveProbe.error ?? "managed_bitcoind_protocol_error");
632
+ }
633
+ const bitcoindPath = await getBitcoindPath();
634
+ await verifyBitcoindVersion(bitcoindPath);
635
+ const binaryVersion = SUPPORTED_BITCOIND_VERSION;
636
+ await mkdir(resolvedOptions.dataDir ?? "", { recursive: true });
637
+ const runtimeConfig = await resolveRuntimeConfig(paths.bitcoindStatusPath, paths.bitcoindRuntimeConfigPath, resolvedOptions);
638
+ await writeBitcoinConf(paths.bitcoinConfPath, resolvedOptions, runtimeConfig);
639
+ const rpcConfig = runtimeConfig.rpc;
640
+ const zmqConfig = {
641
+ endpoint: `tcp://${LOCAL_HOST}:${runtimeConfig.zmqPort}`,
642
+ topic: "hashblock",
643
+ port: runtimeConfig.zmqPort,
644
+ pollIntervalMs: resolvedOptions.pollIntervalMs ?? 15_000,
645
+ };
646
+ const child = spawn(bitcoindPath, buildManagedServiceArgs(resolvedOptions, runtimeConfig), {
647
+ detached: true,
648
+ stdio: "ignore",
649
+ });
650
+ child.unref();
651
+ const rpc = createRpcClient(rpcConfig);
652
+ try {
653
+ await waitForRpcReady(rpc, rpcConfig.cookieFile, resolvedOptions.chain, startupTimeoutMs);
654
+ await validateNodeConfigForTesting(rpc, resolvedOptions.chain, zmqConfig.endpoint);
655
+ }
656
+ catch (error) {
657
+ if (child.pid !== undefined) {
658
+ try {
659
+ process.kill(child.pid, "SIGTERM");
660
+ }
661
+ catch {
662
+ // ignore kill failures during startup cleanup
663
+ }
664
+ }
665
+ throw error;
666
+ }
667
+ const nowUnixMs = Date.now();
668
+ const walletRootId = resolvedOptions.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
669
+ const walletReplica = await loadManagedWalletReplicaIfPresent(rpc, walletRootId, resolvedOptions.dataDir ?? "");
670
+ const status = createBitcoindServiceStatus({
671
+ binaryVersion,
672
+ serviceInstanceId: randomBytes(16).toString("hex"),
673
+ state: "ready",
674
+ processId: child.pid ?? null,
675
+ walletRootId,
676
+ chain: resolvedOptions.chain,
677
+ dataDir: resolvedOptions.dataDir ?? "",
678
+ runtimeRoot: paths.walletRuntimeRoot,
679
+ startHeight: resolvedOptions.startHeight,
680
+ rpc: rpcConfig,
681
+ zmq: zmqConfig,
682
+ p2pPort: runtimeConfig.p2pPort,
683
+ walletReplica,
684
+ startedAtUnixMs: nowUnixMs,
685
+ heartbeatAtUnixMs: nowUnixMs,
686
+ lastError: walletReplica.message ?? null,
687
+ });
688
+ await writeBitcoindStatus(paths, status);
689
+ return createNodeHandle(status, paths, resolvedOptions);
690
+ }
691
+ finally {
692
+ await lock.release();
693
+ }
694
+ }
695
+ catch (error) {
696
+ if (error instanceof FileLockBusyError) {
697
+ return waitForManagedBitcoindService(resolvedOptions, startupTimeoutMs);
698
+ }
699
+ throw error;
700
+ }
701
+ }
702
+ export async function readManagedBitcoindServiceStatusForTesting(dataDir, walletRootId = UNINITIALIZED_WALLET_ROOT_ID) {
703
+ const paths = resolveManagedServicePaths(dataDir, walletRootId);
704
+ return readJsonFile(paths.bitcoindStatusPath);
705
+ }
706
+ export async function shutdownManagedBitcoindServiceForTesting(options) {
707
+ const walletRootId = options.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
708
+ const paths = resolveManagedServicePaths(options.dataDir, walletRootId);
709
+ const status = await readJsonFile(paths.bitcoindStatusPath);
710
+ if (status === null) {
711
+ return;
712
+ }
713
+ const rpc = createRpcClient(status.rpc);
714
+ try {
715
+ await rpc.stop();
716
+ }
717
+ catch {
718
+ if (status.processId !== null) {
719
+ try {
720
+ process.kill(status.processId, "SIGTERM");
721
+ }
722
+ catch {
723
+ // ignore
724
+ }
725
+ }
726
+ }
727
+ const deadline = Date.now() + (options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS);
728
+ while (Date.now() < deadline) {
729
+ if (!await isProcessAlive(status.processId)) {
730
+ break;
731
+ }
732
+ await sleep(250);
733
+ }
734
+ await rm(paths.bitcoindReadyPath, { force: true }).catch(() => undefined);
735
+ }