@cogcoin/client 0.5.14 → 1.0.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 (172) 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 +16 -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 +38 -3
  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 +51 -4
  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 +4 -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/types.d.ts +8 -17
  75. package/dist/cli/types.js +0 -2
  76. package/dist/cli/wallet-format.d.ts +1 -0
  77. package/dist/cli/wallet-format.js +205 -144
  78. package/dist/cli/workflow-hints.d.ts +3 -3
  79. package/dist/cli/workflow-hints.js +11 -8
  80. package/dist/client/default-client.d.ts +3 -1
  81. package/dist/client/default-client.js +45 -2
  82. package/dist/client/factory.js +1 -1
  83. package/dist/client/initialization.js +23 -0
  84. package/dist/client/persistence.js +5 -5
  85. package/dist/client/store-adapter.js +1 -0
  86. package/dist/sqlite/checkpoints.d.ts +1 -0
  87. package/dist/sqlite/checkpoints.js +7 -0
  88. package/dist/sqlite/store.js +14 -1
  89. package/dist/types.d.ts +1 -0
  90. package/dist/wallet/coin-control.d.ts +41 -11
  91. package/dist/wallet/coin-control.js +100 -357
  92. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  93. package/dist/wallet/descriptor-normalization.js +0 -16
  94. package/dist/wallet/lifecycle.d.ts +7 -99
  95. package/dist/wallet/lifecycle.js +513 -968
  96. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  97. package/dist/wallet/managed-core-wallet.js +20 -0
  98. package/dist/wallet/mining/constants.d.ts +5 -12
  99. package/dist/wallet/mining/constants.js +5 -12
  100. package/dist/wallet/mining/control.d.ts +1 -13
  101. package/dist/wallet/mining/control.js +45 -349
  102. package/dist/wallet/mining/index.d.ts +3 -4
  103. package/dist/wallet/mining/index.js +1 -2
  104. package/dist/wallet/mining/runner.d.ts +179 -6
  105. package/dist/wallet/mining/runner.js +891 -501
  106. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  107. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  108. package/dist/wallet/mining/sentence-protocol.js +123 -0
  109. package/dist/wallet/mining/sentences.d.ts +4 -8
  110. package/dist/wallet/mining/sentences.js +3 -52
  111. package/dist/wallet/mining/state.d.ts +11 -6
  112. package/dist/wallet/mining/state.js +7 -6
  113. package/dist/wallet/mining/types.d.ts +2 -30
  114. package/dist/wallet/mining/visualizer.d.ts +31 -3
  115. package/dist/wallet/mining/visualizer.js +135 -13
  116. package/dist/wallet/read/context.d.ts +0 -2
  117. package/dist/wallet/read/context.js +119 -140
  118. package/dist/wallet/read/filter.js +2 -11
  119. package/dist/wallet/read/index.d.ts +1 -1
  120. package/dist/wallet/read/project.js +24 -77
  121. package/dist/wallet/read/types.d.ts +10 -25
  122. package/dist/wallet/reset.d.ts +0 -1
  123. package/dist/wallet/reset.js +60 -138
  124. package/dist/wallet/root-resolution.d.ts +1 -5
  125. package/dist/wallet/root-resolution.js +0 -18
  126. package/dist/wallet/runtime.d.ts +0 -6
  127. package/dist/wallet/runtime.js +0 -8
  128. package/dist/wallet/state/client-password-agent.js +208 -0
  129. package/dist/wallet/state/client-password.d.ts +65 -0
  130. package/dist/wallet/state/client-password.js +952 -0
  131. package/dist/wallet/state/crypto.d.ts +1 -20
  132. package/dist/wallet/state/crypto.js +0 -63
  133. package/dist/wallet/state/provider.d.ts +23 -11
  134. package/dist/wallet/state/provider.js +248 -290
  135. package/dist/wallet/state/storage.d.ts +2 -2
  136. package/dist/wallet/state/storage.js +48 -16
  137. package/dist/wallet/tx/anchor.d.ts +3 -28
  138. package/dist/wallet/tx/anchor.js +349 -1240
  139. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  140. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  141. package/dist/wallet/tx/cog.d.ts +5 -1
  142. package/dist/wallet/tx/cog.js +149 -185
  143. package/dist/wallet/tx/common.d.ts +74 -10
  144. package/dist/wallet/tx/common.js +315 -138
  145. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  146. package/dist/wallet/tx/domain-admin.js +61 -99
  147. package/dist/wallet/tx/domain-market.d.ts +5 -1
  148. package/dist/wallet/tx/domain-market.js +221 -228
  149. package/dist/wallet/tx/field.d.ts +4 -10
  150. package/dist/wallet/tx/field.js +84 -914
  151. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  152. package/dist/wallet/tx/identity-selector.js +17 -35
  153. package/dist/wallet/tx/index.d.ts +3 -1
  154. package/dist/wallet/tx/index.js +2 -1
  155. package/dist/wallet/tx/register.d.ts +3 -1
  156. package/dist/wallet/tx/register.js +62 -220
  157. package/dist/wallet/tx/reputation.d.ts +3 -1
  158. package/dist/wallet/tx/reputation.js +58 -95
  159. package/dist/wallet/types.d.ts +8 -122
  160. package/package.json +5 -5
  161. package/dist/wallet/archive.d.ts +0 -4
  162. package/dist/wallet/archive.js +0 -41
  163. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  164. package/dist/wallet/mining/hook-protocol.js +0 -161
  165. package/dist/wallet/mining/hook-runner.js +0 -52
  166. package/dist/wallet/mining/hooks.d.ts +0 -38
  167. package/dist/wallet/mining/hooks.js +0 -520
  168. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  169. package/dist/wallet/state/explicit-lock.js +0 -19
  170. package/dist/wallet/state/session.d.ts +0 -12
  171. package/dist/wallet/state/session.js +0 -23
  172. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
@@ -7,25 +7,21 @@ import { attachOrStartManagedBitcoindService, createManagedWalletReplica, probeM
7
7
  import { resolveManagedServicePaths } from "../bitcoind/service-paths.js";
8
8
  import { createRpcClient } from "../bitcoind/node.js";
9
9
  import { openSqliteStore } from "../sqlite/index.js";
10
- import { readPortableWalletArchive, writePortableWalletArchive } from "./archive.js";
11
- import { DEFAULT_PROACTIVE_RESERVE_SATS, normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded, } from "./coin-control.js";
10
+ import { normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded, } from "./coin-control.js";
12
11
  import { normalizeWalletDescriptorState, persistNormalizedWalletDescriptorStateIfNeeded, persistWalletStateUpdate, resolveNormalizedWalletDescriptorState, stripDescriptorChecksum, } from "./descriptor-normalization.js";
13
- import { acquireFileLock, clearOrphanedFileLock } from "./fs/lock.js";
12
+ import { acquireFileLock, clearOrphanedFileLock, readLockMetadata } from "./fs/lock.js";
14
13
  import { createInternalCoreWalletPassphrase, createMnemonicConfirmationChallenge, deriveWalletMaterialFromMnemonic, generateWalletMaterial, isEnglishMnemonicWord, validateEnglishMnemonic, } from "./material.js";
15
14
  import { deriveWalletRuntimePathsForSeed, resolveWalletRuntimePathsForTesting, } from "./runtime.js";
16
- import { requestMiningGenerationPreemption } from "./mining/coordination.js";
15
+ import { readMiningGenerationActivity } from "./mining/coordination.js";
17
16
  import { loadClientConfig } from "./mining/config.js";
18
- import { inspectMiningHookState } from "./mining/hooks.js";
19
17
  import { loadMiningRuntimeStatus, saveMiningRuntimeStatus } from "./mining/runtime-artifacts.js";
20
18
  import { normalizeMiningStateRecord } from "./mining/state.js";
21
19
  import { renderWalletMnemonicRevealArt } from "./mnemonic-art.js";
22
- import { clearWalletExplicitLock, loadWalletExplicitLock, saveWalletExplicitLock, } from "./state/explicit-lock.js";
23
20
  import { clearWalletPendingInitializationState, loadWalletPendingInitializationStateOrNull, saveWalletPendingInitializationState, } from "./state/pending-init.js";
24
- import { clearUnlockSession, loadUnlockSession, saveUnlockSession } from "./state/session.js";
25
21
  import { addImportedWalletSeedRecord, assertValidImportedWalletSeedName, ensureMainWalletSeedIndexRecord, findWalletSeedRecord, loadWalletSeedIndex, removeWalletSeedRecord, } from "./state/seed-index.js";
26
- import { createDefaultWalletSecretProvider, createWalletPendingInitSecretReference, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
22
+ import { createDefaultWalletSecretProvider, ensureClientPasswordConfigured, createWalletPendingInitSecretReference, createWalletRootId, createWalletSecretReference, withInteractiveWalletSecretProvider, } from "./state/provider.js";
23
+ import { clearLegacyWalletLockArtifacts, withUnlockedManagedCoreWallet, } from "./managed-core-wallet.js";
27
24
  import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, saveWalletState, } from "./state/storage.js";
28
- export const DEFAULT_UNLOCK_DURATION_MS = 15 * 60 * 1000;
29
25
  export { previewResetWallet, resetWallet, } from "./reset.js";
30
26
  function sanitizeWalletName(walletRootId) {
31
27
  return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
@@ -99,16 +95,12 @@ async function loadOrCreatePendingInitializationMaterial(options) {
99
95
  }
100
96
  function createInitialWalletState(options) {
101
97
  return {
102
- schemaVersion: 1,
98
+ schemaVersion: 5,
103
99
  stateRevision: 1,
104
100
  lastWrittenAtUnixMs: options.nowUnixMs,
105
101
  walletRootId: options.walletRootId,
106
102
  network: "mainnet",
107
- anchorValueSats: 2_000,
108
- proactiveReserveSats: DEFAULT_PROACTIVE_RESERVE_SATS,
109
- proactiveReserveOutpoints: [],
110
- nextDedicatedIndex: 1,
111
- fundingIndex: 0,
103
+ localScriptPubKeyHexes: [options.material.funding.scriptPubKeyHex],
112
104
  mnemonic: {
113
105
  phrase: options.material.mnemonic.phrase,
114
106
  language: options.material.mnemonic.language,
@@ -135,21 +127,12 @@ function createInitialWalletState(options) {
135
127
  walletName: sanitizeWalletName(options.walletRootId),
136
128
  internalPassphrase: options.internalCoreWalletPassphrase,
137
129
  descriptorChecksum: null,
138
- fundingAddress0: null,
139
- fundingScriptPubKeyHex0: null,
130
+ walletAddress: null,
131
+ walletScriptPubKeyHex: null,
140
132
  proofStatus: "not-proven",
141
133
  lastImportedAtUnixMs: null,
142
134
  lastVerifiedAtUnixMs: null,
143
135
  },
144
- identities: [
145
- {
146
- index: 0,
147
- scriptPubKeyHex: options.material.funding.scriptPubKeyHex,
148
- address: options.material.funding.address,
149
- status: "funding",
150
- assignedDomainNames: [],
151
- },
152
- ],
153
136
  domains: [],
154
137
  miningState: {
155
138
  runMode: "stopped",
@@ -172,7 +155,7 @@ function createInitialWalletState(options) {
172
155
  currentBlockTargetHeight: null,
173
156
  currentReferencedBlockHashDisplay: null,
174
157
  currentIntentFingerprintHex: null,
175
- liveMiningFamilyInMempool: null,
158
+ livePublishInMempool: null,
176
159
  currentPublishDecision: null,
177
160
  replacementCount: 0,
178
161
  currentBlockFeeSpentSats: "0",
@@ -180,44 +163,11 @@ function createInitialWalletState(options) {
180
163
  lifetimeFeeSpentSats: "0",
181
164
  sharedMiningConflictOutpoint: null,
182
165
  },
183
- hookClientState: {
184
- mining: {
185
- mode: "builtin",
186
- validationState: "never",
187
- lastValidationAtUnixMs: null,
188
- lastValidationError: null,
189
- validatedLaunchFingerprint: null,
190
- validatedFullFingerprint: null,
191
- fullTrustWarningAcknowledgedAtUnixMs: null,
192
- consecutiveFailureCount: 0,
193
- cooldownUntilUnixMs: null,
194
- },
195
- },
196
- proactiveFamilies: [],
197
166
  pendingMutations: [],
198
167
  };
199
168
  }
200
- function createUnlockSession(state, unlockUntilUnixMs, secretKeyId, nowUnixMs) {
201
- return {
202
- schemaVersion: 1,
203
- walletRootId: state.walletRootId,
204
- sessionId: randomBytes(16).toString("hex"),
205
- createdAtUnixMs: nowUnixMs,
206
- unlockUntilUnixMs,
207
- sourceStateRevision: state.stateRevision,
208
- wrappedSessionKeyMaterial: secretKeyId,
209
- };
210
- }
211
- function createWalletExplicitLock(walletRootId, nowUnixMs) {
212
- return {
213
- schemaVersion: 1,
214
- walletRootId,
215
- lockedAtUnixMs: nowUnixMs,
216
- };
217
- }
218
- async function normalizeUnlockedWalletStateIfNeeded(options) {
169
+ async function normalizeLoadedWalletStateIfNeeded(options) {
219
170
  let state = options.state;
220
- let session = options.session;
221
171
  let source = options.source;
222
172
  if (options.dataDir !== undefined) {
223
173
  const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
@@ -233,14 +183,12 @@ async function normalizeUnlockedWalletStateIfNeeded(options) {
233
183
  provider: options.provider,
234
184
  secretReference: createWalletSecretReference(state.walletRootId),
235
185
  },
236
- session,
237
186
  paths: options.paths,
238
187
  nowUnixMs: options.nowUnixMs,
239
188
  replacePrimary: options.source === "backup",
240
189
  rpc: (options.rpcFactory ?? createRpcClient)(node.rpc),
241
190
  });
242
191
  state = normalized.state;
243
- session = normalized.session ?? session;
244
192
  source = normalized.changed ? "primary" : options.source;
245
193
  const coinControl = await persistWalletCoinControlStateIfNeeded({
246
194
  state,
@@ -248,14 +196,12 @@ async function normalizeUnlockedWalletStateIfNeeded(options) {
248
196
  provider: options.provider,
249
197
  secretReference: createWalletSecretReference(state.walletRootId),
250
198
  },
251
- session,
252
199
  paths: options.paths,
253
200
  nowUnixMs: options.nowUnixMs,
254
201
  replacePrimary: source === "backup",
255
202
  rpc: createRpcClient(node.rpc),
256
203
  });
257
204
  state = coinControl.state;
258
- session = coinControl.session ?? session;
259
205
  source = coinControl.changed ? "primary" : source;
260
206
  }
261
207
  finally {
@@ -263,7 +209,6 @@ async function normalizeUnlockedWalletStateIfNeeded(options) {
263
209
  }
264
210
  }
265
211
  return {
266
- session,
267
212
  state: normalizeWalletStateRecord({
268
213
  ...state,
269
214
  miningState: normalizeMiningStateRecord(state.miningState),
@@ -271,106 +216,6 @@ async function normalizeUnlockedWalletStateIfNeeded(options) {
271
216
  source,
272
217
  };
273
218
  }
274
- function createPortableWalletArchivePayload(state, exportedAtUnixMs) {
275
- return {
276
- schemaVersion: 1,
277
- exportedAtUnixMs,
278
- walletRootId: state.walletRootId,
279
- network: state.network,
280
- anchorValueSats: state.anchorValueSats,
281
- proactiveReserveSats: state.proactiveReserveSats,
282
- proactiveReserveOutpoints: state.proactiveReserveOutpoints,
283
- nextDedicatedIndex: state.nextDedicatedIndex,
284
- fundingIndex: state.fundingIndex,
285
- mnemonic: {
286
- phrase: state.mnemonic.phrase,
287
- language: state.mnemonic.language,
288
- },
289
- expected: {
290
- masterFingerprintHex: state.keys.masterFingerprintHex,
291
- accountPath: state.keys.accountPath,
292
- accountXpub: state.keys.accountXpub,
293
- publicExternalDescriptor: stripDescriptorChecksum(state.descriptor.publicExternal),
294
- descriptorChecksum: state.descriptor.checksum,
295
- rangeEnd: state.descriptor.rangeEnd,
296
- safetyMargin: state.descriptor.safetyMargin,
297
- fundingAddress0: state.funding.address,
298
- fundingScriptPubKeyHex0: state.funding.scriptPubKeyHex,
299
- walletBirthTime: state.walletBirthTime,
300
- },
301
- identities: state.identities,
302
- domains: state.domains,
303
- miningState: normalizeMiningStateRecord(state.miningState),
304
- hookClientState: state.hookClientState,
305
- proactiveFamilies: state.proactiveFamilies.filter((family) => family.status === "confirmed" || family.status === "canceled"),
306
- };
307
- }
308
- function createWalletStateFromPortableArchive(options) {
309
- const material = deriveWalletMaterialFromMnemonic(options.payload.mnemonic.phrase);
310
- if (material.keys.masterFingerprintHex !== options.payload.expected.masterFingerprintHex
311
- || material.keys.accountPath !== options.payload.expected.accountPath
312
- || material.keys.accountXpub !== options.payload.expected.accountXpub
313
- || stripDescriptorChecksum(material.descriptor.publicExternal) !== stripDescriptorChecksum(options.payload.expected.publicExternalDescriptor)
314
- || material.funding.address !== options.payload.expected.fundingAddress0
315
- || material.funding.scriptPubKeyHex !== options.payload.expected.fundingScriptPubKeyHex0) {
316
- throw new Error("wallet_import_material_mismatch");
317
- }
318
- const baseState = createInitialWalletState({
319
- walletRootId: options.payload.walletRootId,
320
- nowUnixMs: options.nowUnixMs,
321
- material,
322
- internalCoreWalletPassphrase: options.internalCoreWalletPassphrase,
323
- });
324
- return {
325
- ...baseState,
326
- walletRootId: options.payload.walletRootId,
327
- network: options.payload.network,
328
- anchorValueSats: options.payload.anchorValueSats,
329
- proactiveReserveSats: options.payload.proactiveReserveSats,
330
- proactiveReserveOutpoints: options.payload.proactiveReserveOutpoints,
331
- nextDedicatedIndex: options.payload.nextDedicatedIndex,
332
- fundingIndex: options.payload.fundingIndex,
333
- walletBirthTime: options.payload.expected.walletBirthTime,
334
- descriptor: {
335
- ...baseState.descriptor,
336
- checksum: options.payload.expected.descriptorChecksum,
337
- rangeEnd: options.payload.expected.rangeEnd,
338
- safetyMargin: options.payload.expected.safetyMargin,
339
- },
340
- identities: options.payload.identities,
341
- domains: options.payload.domains,
342
- miningState: normalizeMiningStateRecord(options.payload.miningState),
343
- hookClientState: options.payload.hookClientState,
344
- proactiveFamilies: options.payload.proactiveFamilies,
345
- pendingMutations: [],
346
- };
347
- }
348
- function isExportBlockedByLocalState(state) {
349
- if (state.miningState.state === "repair-required"
350
- || state.miningState.currentPublishState === "broadcasting"
351
- || state.miningState.currentPublishState === "broadcast-unknown"
352
- || state.miningState.currentPublishState === "in-mempool") {
353
- return "wallet_export_requires_quiescent_local_state";
354
- }
355
- if (state.proactiveFamilies.some((family) => family.status === "draft"
356
- || family.status === "broadcasting"
357
- || family.status === "broadcast-unknown"
358
- || family.status === "live"
359
- || family.status === "repair-required")) {
360
- return "wallet_export_requires_quiescent_local_state";
361
- }
362
- if (state.domains.some((domain) => domain.localAnchorIntent === "repair-required")) {
363
- return "wallet_export_requires_quiescent_local_state";
364
- }
365
- if ((state.pendingMutations ?? []).some((mutation) => mutation.status === "draft"
366
- || mutation.status === "broadcasting"
367
- || mutation.status === "broadcast-unknown"
368
- || mutation.status === "live"
369
- || mutation.status === "repair-required")) {
370
- return "wallet_export_requires_quiescent_local_state";
371
- }
372
- return null;
373
- }
374
219
  async function promptRequiredValue(prompter, message) {
375
220
  const value = (await prompter.prompt(message)).trim();
376
221
  if (value === "") {
@@ -384,14 +229,6 @@ async function promptHiddenValue(prompter, message) {
384
229
  : await prompter.prompt(message);
385
230
  return value.trim();
386
231
  }
387
- async function promptForArchivePassphrase(prompter, promptPrefix) {
388
- const first = await promptRequiredValue(prompter, `${promptPrefix} passphrase: `);
389
- const second = await promptRequiredValue(prompter, `Confirm ${promptPrefix.toLowerCase()} passphrase: `);
390
- if (first !== second) {
391
- throw new Error("wallet_archive_passphrase_mismatch");
392
- }
393
- return first;
394
- }
395
232
  async function promptForRestoreMnemonic(prompter) {
396
233
  const words = [];
397
234
  for (let index = 0; index < 24; index += 1) {
@@ -419,38 +256,12 @@ async function confirmRestoreReplacement(prompter) {
419
256
  throw new Error("wallet_restore_replace_confirmation_required");
420
257
  }
421
258
  }
422
- async function confirmOverwriteIfNeeded(prompter, path) {
423
- if (!await pathExists(path)) {
424
- return;
425
- }
426
- const answer = (await prompter.prompt(`Archive ${path} already exists. Overwrite it? Type yes to continue: `)).trim().toLowerCase();
427
- if (answer !== "yes") {
428
- throw new Error("wallet_export_overwrite_declined");
429
- }
430
- }
431
259
  async function confirmYesNo(prompter, message) {
432
260
  const answer = (await prompter.prompt(message)).trim().toLowerCase();
433
261
  if (answer !== "yes") {
434
262
  throw new Error("wallet_delete_confirmation_required");
435
263
  }
436
264
  }
437
- async function readManagedSnapshotTip(options) {
438
- const daemon = await attachOrStartIndexerDaemon({
439
- dataDir: options.dataDir,
440
- databasePath: options.databasePath,
441
- walletRootId: options.walletRootId,
442
- });
443
- try {
444
- const lease = await readSnapshotWithRetry(daemon, options.walletRootId);
445
- return {
446
- nodeBestHeight: lease.status.coreBestHeight,
447
- snapshotHeight: lease.payload.tip?.height ?? null,
448
- };
449
- }
450
- finally {
451
- await daemon.close().catch(() => undefined);
452
- }
453
- }
454
265
  async function recreateManagedCoreWalletReplica(state, provider, paths, dataDir, nowUnixMs, options = {}) {
455
266
  const walletName = sanitizeWalletName(state.walletRootId);
456
267
  const walletDir = join(dataDir, "wallets", walletName);
@@ -739,14 +550,14 @@ function applyRepairStoppedMiningState(state) {
739
550
  miningState: {
740
551
  ...miningState,
741
552
  runMode: "stopped",
742
- state: miningState.liveMiningFamilyInMempool
553
+ state: miningState.livePublishInMempool
743
554
  ? miningState.state === "paused-stale"
744
555
  ? "paused-stale"
745
556
  : "paused"
746
557
  : miningState.state === "repair-required"
747
558
  ? "repair-required"
748
559
  : "idle",
749
- pauseReason: miningState.liveMiningFamilyInMempool
560
+ pauseReason: miningState.livePublishInMempool
750
561
  ? miningState.state === "paused-stale"
751
562
  ? "stale-block-context"
752
563
  : "wallet-repair"
@@ -766,11 +577,121 @@ function createStoppedBackgroundRuntimeSnapshot(snapshot, nowUnixMs) {
766
577
  backgroundWorkerHeartbeatAtUnixMs: null,
767
578
  backgroundWorkerHealth: null,
768
579
  currentPhase: "idle",
769
- note: snapshot.liveMiningFamilyInMempool
580
+ note: snapshot.livePublishInMempool
770
581
  ? "Background mining stopped for wallet repair. The last mining transaction may still confirm from mempool."
771
582
  : "Background mining stopped for wallet repair.",
772
583
  };
773
584
  }
585
+ function resolveMiningGenerationRequestPath(paths) {
586
+ return join(paths.miningRoot, "generation-request.json");
587
+ }
588
+ function resolveMiningGenerationActivityPath(paths) {
589
+ return join(paths.miningRoot, "generation-activity.json");
590
+ }
591
+ function normalizeRepairMiningPid(value) {
592
+ return typeof value === "number" && Number.isInteger(value) && value > 0
593
+ ? value
594
+ : null;
595
+ }
596
+ function createRepairStoppedMiningNote(livePublishInMempool) {
597
+ return livePublishInMempool
598
+ ? "Background mining stopped for wallet repair. The last mining transaction may still confirm from mempool."
599
+ : "Background mining stopped for wallet repair.";
600
+ }
601
+ function createStoppedMiningRuntimeSnapshotForRepair(options) {
602
+ const stoppedMiningState = normalizeMiningStateRecord(applyRepairStoppedMiningState(options.state).miningState);
603
+ const note = createRepairStoppedMiningNote(stoppedMiningState.livePublishInMempool);
604
+ if (options.snapshot !== null) {
605
+ return {
606
+ ...createStoppedBackgroundRuntimeSnapshot(options.snapshot, options.nowUnixMs),
607
+ miningState: stoppedMiningState.state,
608
+ currentPublishState: stoppedMiningState.currentPublishState,
609
+ targetBlockHeight: stoppedMiningState.currentBlockTargetHeight,
610
+ referencedBlockHashDisplay: stoppedMiningState.currentReferencedBlockHashDisplay,
611
+ currentDomainId: stoppedMiningState.currentDomainId,
612
+ currentDomainName: stoppedMiningState.currentDomain,
613
+ currentSentenceDisplay: stoppedMiningState.currentSentence,
614
+ currentTxid: stoppedMiningState.currentTxid,
615
+ currentWtxid: stoppedMiningState.currentWtxid,
616
+ livePublishInMempool: stoppedMiningState.livePublishInMempool,
617
+ currentFeeRateSatVb: stoppedMiningState.currentFeeRateSatVb,
618
+ currentAbsoluteFeeSats: stoppedMiningState.currentAbsoluteFeeSats,
619
+ currentBlockFeeSpentSats: stoppedMiningState.currentBlockFeeSpentSats,
620
+ sessionFeeSpentSats: stoppedMiningState.sessionFeeSpentSats,
621
+ lifetimeFeeSpentSats: stoppedMiningState.lifetimeFeeSpentSats,
622
+ currentPublishDecision: stoppedMiningState.currentPublishDecision,
623
+ pauseReason: stoppedMiningState.pauseReason,
624
+ note,
625
+ };
626
+ }
627
+ return {
628
+ schemaVersion: 1,
629
+ walletRootId: options.state.walletRootId,
630
+ workerApiVersion: null,
631
+ workerBinaryVersion: null,
632
+ workerBuildId: null,
633
+ updatedAtUnixMs: options.nowUnixMs,
634
+ runMode: "stopped",
635
+ backgroundWorkerPid: null,
636
+ backgroundWorkerRunId: null,
637
+ backgroundWorkerHeartbeatAtUnixMs: null,
638
+ backgroundWorkerHealth: null,
639
+ indexerDaemonState: null,
640
+ indexerDaemonInstanceId: null,
641
+ indexerSnapshotSeq: null,
642
+ indexerSnapshotOpenedAtUnixMs: null,
643
+ indexerTruthSource: undefined,
644
+ indexerHeartbeatAtUnixMs: null,
645
+ coreBestHeight: null,
646
+ coreBestHash: null,
647
+ indexerTipHeight: null,
648
+ indexerTipHash: null,
649
+ indexerReorgDepth: null,
650
+ indexerTipAligned: null,
651
+ corePublishState: null,
652
+ providerState: null,
653
+ lastSuspendDetectedAtUnixMs: null,
654
+ reconnectSettledUntilUnixMs: null,
655
+ tipSettledUntilUnixMs: null,
656
+ miningState: stoppedMiningState.state,
657
+ currentPhase: "idle",
658
+ currentPublishState: stoppedMiningState.currentPublishState,
659
+ targetBlockHeight: stoppedMiningState.currentBlockTargetHeight,
660
+ referencedBlockHashDisplay: stoppedMiningState.currentReferencedBlockHashDisplay,
661
+ currentDomainId: stoppedMiningState.currentDomainId,
662
+ currentDomainName: stoppedMiningState.currentDomain,
663
+ currentSentenceDisplay: stoppedMiningState.currentSentence,
664
+ currentCanonicalBlend: null,
665
+ currentTxid: stoppedMiningState.currentTxid,
666
+ currentWtxid: stoppedMiningState.currentWtxid,
667
+ livePublishInMempool: stoppedMiningState.livePublishInMempool,
668
+ currentFeeRateSatVb: stoppedMiningState.currentFeeRateSatVb,
669
+ currentAbsoluteFeeSats: stoppedMiningState.currentAbsoluteFeeSats,
670
+ currentBlockFeeSpentSats: stoppedMiningState.currentBlockFeeSpentSats,
671
+ sessionFeeSpentSats: stoppedMiningState.sessionFeeSpentSats,
672
+ lifetimeFeeSpentSats: stoppedMiningState.lifetimeFeeSpentSats,
673
+ sameDomainCompetitorSuppressed: null,
674
+ higherRankedCompetitorDomainCount: null,
675
+ dedupedCompetitorDomainCount: null,
676
+ competitivenessGateIndeterminate: null,
677
+ mempoolSequenceCacheStatus: null,
678
+ currentPublishDecision: stoppedMiningState.currentPublishDecision,
679
+ lastMempoolSequence: null,
680
+ lastCompetitivenessGateAtUnixMs: null,
681
+ pauseReason: stoppedMiningState.pauseReason,
682
+ providerConfigured: false,
683
+ providerKind: null,
684
+ bitcoindHealth: "unavailable",
685
+ bitcoindServiceState: null,
686
+ bitcoindReplicaStatus: null,
687
+ nodeHealth: "unavailable",
688
+ indexerHealth: "unavailable",
689
+ tipsAligned: null,
690
+ lastEventAtUnixMs: null,
691
+ lastError: null,
692
+ note,
693
+ };
694
+ }
774
695
  async function persistRepairState(options) {
775
696
  return await persistWalletStateUpdate({
776
697
  state: options.state,
@@ -783,41 +704,57 @@ async function persistRepairState(options) {
783
704
  replacePrimary: options.replacePrimary,
784
705
  });
785
706
  }
786
- async function stopBackgroundMiningForRepair(options) {
787
- const pid = options.snapshot.backgroundWorkerPid;
788
- if (pid !== null) {
789
- try {
790
- process.kill(pid, "SIGKILL");
707
+ async function cleanupMiningForRepair(options) {
708
+ const controlLockMetadata = await readLockMetadata(options.paths.miningControlLockPath).catch(() => null);
709
+ const generationActivity = await readMiningGenerationActivity(options.paths).catch(() => null);
710
+ const controlLockPid = normalizeRepairMiningPid(controlLockMetadata?.processId);
711
+ const backgroundWorkerPid = normalizeRepairMiningPid(options.snapshot?.backgroundWorkerPid);
712
+ const generationOwnerPid = normalizeRepairMiningPid(generationActivity?.generationOwnerPid);
713
+ const discoveredPids = new Set();
714
+ let backgroundWorkerAlive = false;
715
+ let foregroundWorkerAlive = false;
716
+ const pidSources = [
717
+ { pid: backgroundWorkerPid, source: "background" },
718
+ { pid: controlLockPid, source: "foreground" },
719
+ { pid: generationOwnerPid, source: "foreground" },
720
+ ];
721
+ for (const source of pidSources) {
722
+ if (source.pid === null || source.pid === process.pid || !await isProcessAlive(source.pid)) {
723
+ continue;
791
724
  }
792
- catch (error) {
793
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
794
- throw error;
795
- }
725
+ discoveredPids.add(source.pid);
726
+ if (source.source === "background") {
727
+ backgroundWorkerAlive = true;
796
728
  }
797
- await waitForProcessExit(pid, 15_000, "background_mining_stop_timeout");
729
+ else {
730
+ foregroundWorkerAlive = true;
731
+ }
732
+ }
733
+ for (const pid of discoveredPids) {
734
+ await stopRecordedManagedProcess(pid, "mining_process_stop_timeout");
798
735
  }
799
- await saveMiningRuntimeStatus(options.paths.miningStatusPath, createStoppedBackgroundRuntimeSnapshot(options.snapshot, options.nowUnixMs));
736
+ await rm(options.paths.miningControlLockPath, { force: true }).catch(() => undefined);
737
+ await rm(resolveMiningGenerationRequestPath(options.paths), { force: true }).catch(() => undefined);
738
+ await rm(resolveMiningGenerationActivityPath(options.paths), { force: true }).catch(() => undefined);
739
+ await saveMiningRuntimeStatus(options.paths.miningStatusPath, createStoppedMiningRuntimeSnapshotForRepair({
740
+ state: options.state,
741
+ snapshot: options.snapshot,
742
+ nowUnixMs: options.nowUnixMs,
743
+ }));
744
+ return {
745
+ preRepairRunMode: backgroundWorkerAlive
746
+ ? "background"
747
+ : foregroundWorkerAlive
748
+ ? "foreground"
749
+ : "stopped",
750
+ };
800
751
  }
801
752
  async function canResumeBackgroundMiningAfterRepair(options) {
802
- if (options.unlockUntilUnixMs === null
803
- || options.unlockUntilUnixMs <= options.nowUnixMs
804
- || options.bitcoindPostRepairHealth !== "ready"
753
+ if (options.bitcoindPostRepairHealth !== "ready"
805
754
  || options.indexerPostRepairHealth !== "synced"
806
755
  || normalizeMiningStateRecord(options.repairedState.miningState).state === "repair-required") {
807
756
  return false;
808
757
  }
809
- const hookMode = options.repairedState.hookClientState.mining?.mode ?? "builtin";
810
- if (hookMode === "custom") {
811
- const inspection = await inspectMiningHookState({
812
- hookRootPath: options.paths.hooksMiningDir,
813
- entrypointPath: options.paths.hooksMiningEntrypointPath,
814
- packagePath: options.paths.hooksMiningPackageJsonPath,
815
- localState: options.repairedState.hookClientState.mining ?? null,
816
- verify: false,
817
- nowUnixMs: options.nowUnixMs,
818
- });
819
- return inspection.operatorValidationState === "current" && !inspection.cooldownActive;
820
- }
821
758
  try {
822
759
  const config = await loadClientConfig({
823
760
  path: options.paths.clientConfigPath,
@@ -829,29 +766,6 @@ async function canResumeBackgroundMiningAfterRepair(options) {
829
766
  return false;
830
767
  }
831
768
  }
832
- export function parseUnlockDurationToMs(raw) {
833
- if (raw == null || raw.trim() === "") {
834
- return DEFAULT_UNLOCK_DURATION_MS;
835
- }
836
- const match = /^([1-9][0-9]*)([smhd])$/i.exec(raw.trim());
837
- if (match == null) {
838
- throw new Error("wallet_unlock_duration_invalid");
839
- }
840
- const value = Number.parseInt(match[1], 10);
841
- const unit = match[2].toLowerCase();
842
- const multiplier = unit === "s"
843
- ? 1_000
844
- : unit === "m"
845
- ? 60_000
846
- : unit === "h"
847
- ? 3_600_000
848
- : 86_400_000;
849
- const duration = value * multiplier;
850
- if (!Number.isFinite(duration) || duration <= 0) {
851
- throw new Error("wallet_unlock_duration_invalid");
852
- }
853
- return duration;
854
- }
855
769
  async function ensureWalletNotInitialized(paths, provider) {
856
770
  if (await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath)) {
857
771
  await clearPendingInitialization(paths, provider);
@@ -860,8 +774,7 @@ async function ensureWalletNotInitialized(paths, provider) {
860
774
  }
861
775
  function isWalletSecretAccessError(error) {
862
776
  const message = error instanceof Error ? error.message : String(error);
863
- return message === "wallet_envelope_missing_secret_provider"
864
- || message.startsWith("wallet_secret_missing_")
777
+ return message.startsWith("wallet_secret_missing_")
865
778
  || message.startsWith("wallet_secret_provider_");
866
779
  }
867
780
  function writeMnemonicReveal(prompter, phrase, introLines) {
@@ -898,22 +811,23 @@ async function importDescriptorIntoManagedCoreWallet(state, provider, paths, dat
898
811
  });
899
812
  const normalizedDescriptors = await resolveNormalizedWalletDescriptorState(state, rpc);
900
813
  const walletName = sanitizeWalletName(state.walletRootId);
901
- await rpc.walletPassphrase(walletName, state.managedCoreWallet.internalPassphrase, 10);
902
- try {
903
- const importResults = await rpc.importDescriptors(walletName, [{
904
- desc: normalizedDescriptors.privateExternal,
905
- timestamp: state.walletBirthTime,
906
- active: false,
907
- internal: false,
908
- range: [0, state.descriptor.rangeEnd],
909
- }]);
910
- if (!importResults.every((result) => result.success)) {
911
- throw new Error(`wallet_descriptor_import_failed_${JSON.stringify(importResults)}`);
912
- }
913
- }
914
- finally {
915
- await rpc.walletLock(walletName).catch(() => undefined);
916
- }
814
+ await withUnlockedManagedCoreWallet({
815
+ rpc,
816
+ walletName,
817
+ internalPassphrase: state.managedCoreWallet.internalPassphrase,
818
+ run: async () => {
819
+ const importResults = await rpc.importDescriptors(walletName, [{
820
+ desc: normalizedDescriptors.privateExternal,
821
+ timestamp: state.walletBirthTime,
822
+ active: false,
823
+ internal: false,
824
+ range: [0, state.descriptor.rangeEnd],
825
+ }]);
826
+ if (!importResults.every((result) => result.success)) {
827
+ throw new Error(`wallet_descriptor_import_failed_${JSON.stringify(importResults)}`);
828
+ }
829
+ },
830
+ });
917
831
  const derivedFunding = await rpc.deriveAddresses(normalizedDescriptors.publicExternal, [0, 0]);
918
832
  if (derivedFunding[0] !== state.funding.address) {
919
833
  throw new Error("wallet_funding_address_verification_failed");
@@ -950,8 +864,8 @@ async function importDescriptorIntoManagedCoreWallet(state, provider, paths, dat
950
864
  ...state.managedCoreWallet,
951
865
  walletName,
952
866
  descriptorChecksum: normalizedDescriptors.checksum,
953
- fundingAddress0: verifiedReplica.fundingAddress0 ?? null,
954
- fundingScriptPubKeyHex0: verifiedReplica.fundingScriptPubKeyHex0 ?? null,
867
+ walletAddress: verifiedReplica.fundingAddress0 ?? null,
868
+ walletScriptPubKeyHex: verifiedReplica.fundingScriptPubKeyHex0 ?? null,
955
869
  proofStatus: "ready",
956
870
  lastImportedAtUnixMs: nowUnixMs,
957
871
  lastVerifiedAtUnixMs: nowUnixMs,
@@ -991,8 +905,8 @@ export async function verifyManagedCoreWalletReplica(state, dataDir, dependencie
991
905
  created: false,
992
906
  proofStatus: "missing",
993
907
  descriptorChecksum: state.managedCoreWallet.descriptorChecksum,
994
- fundingAddress0: state.managedCoreWallet.fundingAddress0,
995
- fundingScriptPubKeyHex0: state.managedCoreWallet.fundingScriptPubKeyHex0,
908
+ fundingAddress0: state.managedCoreWallet.walletAddress,
909
+ fundingScriptPubKeyHex0: state.managedCoreWallet.walletScriptPubKeyHex,
996
910
  message: "Expected descriptor is missing from the managed Core wallet.",
997
911
  };
998
912
  }
@@ -1036,133 +950,40 @@ export async function verifyManagedCoreWalletReplica(state, dataDir, dependencie
1036
950
  created: false,
1037
951
  proofStatus: "not-proven",
1038
952
  descriptorChecksum: state.managedCoreWallet.descriptorChecksum,
1039
- fundingAddress0: state.managedCoreWallet.fundingAddress0,
1040
- fundingScriptPubKeyHex0: state.managedCoreWallet.fundingScriptPubKeyHex0,
953
+ fundingAddress0: state.managedCoreWallet.walletAddress,
954
+ fundingScriptPubKeyHex0: state.managedCoreWallet.walletScriptPubKeyHex,
1041
955
  message: error instanceof Error ? error.message : String(error),
1042
956
  };
1043
957
  }
1044
958
  }
1045
- export async function loadUnlockedWalletState(options = {}) {
959
+ async function loadWalletStateForAccess(options = {}) {
1046
960
  const provider = options.provider ?? createDefaultWalletSecretProvider();
1047
961
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1048
962
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1049
- try {
1050
- let session = await loadUnlockSession(paths.walletUnlockSessionPath, {
1051
- provider,
1052
- });
1053
- if (session.unlockUntilUnixMs <= nowUnixMs) {
1054
- await clearUnlockSession(paths.walletUnlockSessionPath);
1055
- return null;
1056
- }
1057
- const loaded = await loadWalletState({
1058
- primaryPath: paths.walletStatePath,
1059
- backupPath: paths.walletStateBackupPath,
1060
- }, {
1061
- provider,
1062
- });
1063
- if (loaded.state.walletRootId !== session.walletRootId
1064
- || loaded.state.stateRevision !== session.sourceStateRevision) {
1065
- await clearUnlockSession(paths.walletUnlockSessionPath);
1066
- return null;
1067
- }
1068
- return await normalizeUnlockedWalletStateIfNeeded({
1069
- provider,
1070
- session,
1071
- state: loaded.state,
1072
- source: loaded.source,
1073
- nowUnixMs,
1074
- paths,
1075
- dataDir: options.dataDir,
1076
- attachService: options.attachService,
1077
- rpcFactory: options.rpcFactory,
1078
- });
1079
- }
1080
- catch {
1081
- return null;
1082
- }
1083
- }
1084
- export async function loadOrAutoUnlockWalletState(options = {}) {
1085
- const provider = options.provider ?? createDefaultWalletSecretProvider();
1086
- const nowUnixMs = options.nowUnixMs ?? Date.now();
1087
- const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1088
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1089
- const loadExisting = () => loadUnlockedWalletState({
963
+ const loaded = await loadWalletState({
964
+ primaryPath: paths.walletStatePath,
965
+ backupPath: paths.walletStateBackupPath,
966
+ }, {
967
+ provider,
968
+ });
969
+ return normalizeLoadedWalletStateIfNeeded({
1090
970
  provider,
971
+ state: loaded.state,
972
+ source: loaded.source,
1091
973
  nowUnixMs,
1092
974
  paths,
1093
975
  dataDir: options.dataDir,
1094
976
  attachService: options.attachService,
1095
977
  rpcFactory: options.rpcFactory,
1096
978
  });
1097
- const existing = await loadExisting();
1098
- if (existing !== null) {
1099
- return existing;
1100
- }
1101
- const loadAndMaybeAutoUnlock = async () => {
1102
- const reloaded = await loadExisting();
1103
- if (reloaded !== null) {
1104
- return reloaded;
1105
- }
1106
- let loaded;
1107
- try {
1108
- loaded = await loadWalletState({
1109
- primaryPath: paths.walletStatePath,
1110
- backupPath: paths.walletStateBackupPath,
1111
- }, {
1112
- provider,
1113
- });
1114
- }
1115
- catch {
1116
- return null;
1117
- }
1118
- const explicitLock = await loadWalletExplicitLock(paths.walletExplicitLockPath);
1119
- if (explicitLock !== null) {
1120
- if (explicitLock.walletRootId === loaded.state.walletRootId) {
1121
- await clearUnlockSession(paths.walletUnlockSessionPath);
1122
- return null;
1123
- }
1124
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1125
- }
1126
- const secretReference = createWalletSecretReference(loaded.state.walletRootId);
1127
- const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1128
- const session = createUnlockSession(loaded.state, unlockUntilUnixMs, secretReference.keyId, nowUnixMs);
1129
- await saveUnlockSession(paths.walletUnlockSessionPath, session, {
1130
- provider,
1131
- secretReference,
1132
- });
1133
- return await normalizeUnlockedWalletStateIfNeeded({
1134
- provider,
1135
- session,
1136
- state: loaded.state,
1137
- source: loaded.source,
1138
- nowUnixMs,
1139
- paths,
1140
- dataDir: options.dataDir,
1141
- attachService: options.attachService,
1142
- rpcFactory: options.rpcFactory,
1143
- });
1144
- };
1145
- if (options.controlLockHeld) {
1146
- return await loadAndMaybeAutoUnlock();
1147
- }
1148
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1149
- purpose: "wallet-auto-unlock",
1150
- walletRootId: null,
1151
- });
1152
- try {
1153
- return await loadAndMaybeAutoUnlock();
1154
- }
1155
- finally {
1156
- await controlLock.release();
1157
- }
1158
979
  }
1159
980
  export async function initializeWallet(options) {
1160
981
  if (!options.prompter.isInteractive) {
1161
982
  throw new Error("wallet_init_requires_tty");
1162
983
  }
1163
984
  const provider = options.provider ?? createDefaultWalletSecretProvider();
985
+ const interactiveProvider = withInteractiveWalletSecretProvider(provider, options.prompter);
1164
986
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1165
- const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1166
987
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1167
988
  if (paths.selectedSeedName !== "main") {
1168
989
  throw new Error("wallet_init_seed_not_supported");
@@ -1172,9 +993,28 @@ export async function initializeWallet(options) {
1172
993
  walletRootId: null,
1173
994
  });
1174
995
  try {
1175
- await ensureWalletNotInitialized(paths, provider);
996
+ const passwordAction = await ensureClientPasswordConfigured(provider, options.prompter);
997
+ const hasWalletState = await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath);
998
+ if (hasWalletState) {
999
+ await clearPendingInitialization(paths, interactiveProvider);
1000
+ const loaded = await loadWalletStateForAccess({
1001
+ provider: interactiveProvider,
1002
+ nowUnixMs,
1003
+ paths,
1004
+ dataDir: options.dataDir,
1005
+ attachService: options.attachService,
1006
+ rpcFactory: options.rpcFactory,
1007
+ });
1008
+ return {
1009
+ passwordAction,
1010
+ walletAction: "already-initialized",
1011
+ walletRootId: loaded.state.walletRootId,
1012
+ fundingAddress: loaded.state.funding.address,
1013
+ state: loaded.state,
1014
+ };
1015
+ }
1176
1016
  const material = await loadOrCreatePendingInitializationMaterial({
1177
- provider,
1017
+ provider: interactiveProvider,
1178
1018
  paths,
1179
1019
  nowUnixMs,
1180
1020
  });
@@ -1200,7 +1040,7 @@ export async function initializeWallet(options) {
1200
1040
  const internalCoreWalletPassphrase = createInternalCoreWalletPassphrase();
1201
1041
  const secretReference = createWalletSecretReference(walletRootId);
1202
1042
  const secret = randomBytes(32);
1203
- await provider.storeSecret(secretReference.keyId, secret);
1043
+ await interactiveProvider.storeSecret(secretReference.keyId, secret);
1204
1044
  const initialState = createInitialWalletState({
1205
1045
  walletRootId,
1206
1046
  nowUnixMs,
@@ -1215,27 +1055,23 @@ export async function initializeWallet(options) {
1215
1055
  primaryPath: paths.walletStatePath,
1216
1056
  backupPath: paths.walletStateBackupPath,
1217
1057
  }, initialState, {
1218
- provider,
1058
+ provider: interactiveProvider,
1219
1059
  secretReference,
1220
1060
  });
1221
- return importDescriptorIntoManagedCoreWallet(initialState, provider, paths, options.dataDir, nowUnixMs, options.attachService, options.rpcFactory);
1222
- });
1223
- const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1224
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1225
- await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(verifiedState, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1226
- provider,
1227
- secretReference,
1061
+ return importDescriptorIntoManagedCoreWallet(initialState, interactiveProvider, paths, options.dataDir, nowUnixMs, options.attachService, options.rpcFactory);
1228
1062
  });
1229
- await clearPendingInitialization(paths, provider);
1063
+ await clearLegacyWalletLockArtifacts(paths.walletRuntimeRoot);
1064
+ await clearPendingInitialization(paths, interactiveProvider);
1230
1065
  await ensureMainWalletSeedIndexRecord({
1231
1066
  paths: resolveMainWalletPaths(paths),
1232
1067
  walletRootId,
1233
1068
  nowUnixMs,
1234
1069
  });
1235
1070
  return {
1071
+ passwordAction,
1072
+ walletAction: "initialized",
1236
1073
  walletRootId,
1237
1074
  fundingAddress: verifiedState.funding.address,
1238
- unlockUntilUnixMs,
1239
1075
  state: verifiedState,
1240
1076
  };
1241
1077
  }
@@ -1248,6 +1084,7 @@ export async function showWalletMnemonic(options) {
1248
1084
  throw new Error("wallet_show_mnemonic_requires_tty");
1249
1085
  }
1250
1086
  const provider = options.provider ?? createDefaultWalletSecretProvider();
1087
+ const interactiveProvider = withInteractiveWalletSecretProvider(provider, options.prompter);
1251
1088
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1252
1089
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1253
1090
  const controlLock = await acquireFileLock(paths.walletControlLockPath, {
@@ -1262,32 +1099,19 @@ export async function showWalletMnemonic(options) {
1262
1099
  if (!hasPrimaryStateFile && !hasBackupStateFile) {
1263
1100
  throw new Error("wallet_uninitialized");
1264
1101
  }
1265
- const unlocked = await loadOrAutoUnlockWalletState({
1266
- provider,
1102
+ const loaded = await loadWalletStateForAccess({
1103
+ provider: interactiveProvider,
1267
1104
  nowUnixMs,
1268
1105
  paths,
1269
- controlLockHeld: true,
1270
- });
1271
- if (unlocked === null) {
1272
- try {
1273
- await loadWalletState({
1274
- primaryPath: paths.walletStatePath,
1275
- backupPath: paths.walletStateBackupPath,
1276
- }, {
1277
- provider,
1278
- });
1279
- }
1280
- catch (error) {
1281
- if (isWalletSecretAccessError(error)) {
1282
- throw new Error("wallet_locked");
1283
- }
1284
- throw new Error("local-state-corrupt");
1106
+ }).catch((error) => {
1107
+ if (isWalletSecretAccessError(error)) {
1108
+ throw new Error("wallet_secret_provider_unavailable");
1285
1109
  }
1286
- throw new Error("wallet_locked");
1287
- }
1110
+ throw new Error("local-state-corrupt");
1111
+ });
1288
1112
  await confirmTypedAcknowledgement(options.prompter, "show mnemonic", "Type \"show mnemonic\" to continue: ", "wallet_show_mnemonic_typed_ack_required");
1289
1113
  let mnemonicRevealed = false;
1290
- writeMnemonicReveal(options.prompter, unlocked.state.mnemonic.phrase, [
1114
+ writeMnemonicReveal(options.prompter, loaded.state.mnemonic.phrase, [
1291
1115
  "Cogcoin Wallet Recovery Phrase",
1292
1116
  "This 24-word recovery phrase controls the wallet.",
1293
1117
  "",
@@ -1308,239 +1132,13 @@ export async function showWalletMnemonic(options) {
1308
1132
  await controlLock.release();
1309
1133
  }
1310
1134
  }
1311
- export async function unlockWallet(options = {}) {
1312
- const provider = options.provider ?? createDefaultWalletSecretProvider();
1313
- const nowUnixMs = options.nowUnixMs ?? Date.now();
1314
- const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1315
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1316
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1317
- purpose: "wallet-unlock",
1318
- walletRootId: null,
1319
- });
1320
- try {
1321
- const loaded = await loadWalletState({
1322
- primaryPath: paths.walletStatePath,
1323
- backupPath: paths.walletStateBackupPath,
1324
- }, {
1325
- provider,
1326
- });
1327
- const secretReference = createWalletSecretReference(loaded.state.walletRootId);
1328
- const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1329
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1330
- await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(loaded.state, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1331
- provider,
1332
- secretReference,
1333
- });
1334
- return {
1335
- unlockUntilUnixMs,
1336
- state: loaded.state,
1337
- source: loaded.source,
1338
- };
1339
- }
1340
- finally {
1341
- await controlLock.release();
1342
- }
1343
- }
1344
- export async function lockWallet(options) {
1345
- const provider = options.provider ?? createDefaultWalletSecretProvider();
1346
- const nowUnixMs = options.nowUnixMs ?? Date.now();
1347
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1348
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1349
- purpose: "wallet-lock",
1350
- walletRootId: null,
1351
- });
1352
- try {
1353
- let walletRootId = null;
1354
- let coreLocked = false;
1355
- try {
1356
- const loaded = await loadWalletState({
1357
- primaryPath: paths.walletStatePath,
1358
- backupPath: paths.walletStateBackupPath,
1359
- }, {
1360
- provider,
1361
- });
1362
- walletRootId = loaded.state.walletRootId;
1363
- try {
1364
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
1365
- dataDir: options.dataDir,
1366
- chain: "main",
1367
- startHeight: 0,
1368
- walletRootId,
1369
- });
1370
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
1371
- await rpc.walletLock(loaded.state.managedCoreWallet.walletName).catch(() => undefined);
1372
- coreLocked = true;
1373
- }
1374
- catch {
1375
- coreLocked = false;
1376
- }
1377
- }
1378
- catch {
1379
- walletRootId = null;
1380
- }
1381
- await clearUnlockSession(paths.walletUnlockSessionPath);
1382
- if (walletRootId !== null) {
1383
- await saveWalletExplicitLock(paths.walletExplicitLockPath, createWalletExplicitLock(walletRootId, nowUnixMs));
1384
- }
1385
- return {
1386
- walletRootId,
1387
- coreLocked,
1388
- };
1389
- }
1390
- finally {
1391
- await controlLock.release();
1392
- }
1393
- }
1394
- export async function exportWallet(options) {
1395
- if (!options.prompter.isInteractive) {
1396
- throw new Error("wallet_export_requires_tty");
1397
- }
1398
- const provider = options.provider ?? createDefaultWalletSecretProvider();
1399
- const nowUnixMs = options.nowUnixMs ?? Date.now();
1400
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1401
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1402
- purpose: "wallet-export",
1403
- walletRootId: null,
1404
- });
1405
- try {
1406
- const unlocked = await loadOrAutoUnlockWalletState({
1407
- provider,
1408
- nowUnixMs,
1409
- paths,
1410
- controlLockHeld: true,
1411
- });
1412
- if (unlocked === null) {
1413
- throw new Error("wallet_locked");
1414
- }
1415
- const blockedReason = isExportBlockedByLocalState(unlocked.state);
1416
- if (blockedReason !== null) {
1417
- throw new Error(blockedReason);
1418
- }
1419
- const replica = await verifyManagedCoreWalletReplica(unlocked.state, options.dataDir, {
1420
- attachService: options.attachService,
1421
- rpcFactory: options.rpcFactory,
1422
- });
1423
- if (replica.proofStatus !== "ready") {
1424
- throw new Error("wallet_export_core_replica_not_ready");
1425
- }
1426
- const tips = await (options.readSnapshotTip ?? readManagedSnapshotTip)({
1427
- dataDir: options.dataDir,
1428
- databasePath: options.databasePath,
1429
- walletRootId: unlocked.state.walletRootId,
1430
- });
1431
- if (tips.snapshotHeight === null || tips.nodeBestHeight === null || tips.snapshotHeight !== tips.nodeBestHeight) {
1432
- throw new Error("wallet_export_tip_mismatch");
1433
- }
1434
- await confirmOverwriteIfNeeded(options.prompter, options.archivePath);
1435
- const passphrase = await promptForArchivePassphrase(options.prompter, "Archive");
1436
- await writePortableWalletArchive(options.archivePath, createPortableWalletArchivePayload(unlocked.state, nowUnixMs), passphrase);
1437
- return {
1438
- archivePath: options.archivePath,
1439
- walletRootId: unlocked.state.walletRootId,
1440
- };
1441
- }
1442
- finally {
1443
- await controlLock.release();
1444
- }
1445
- }
1446
- export async function importWallet(options) {
1447
- if (!options.prompter.isInteractive) {
1448
- throw new Error("wallet_import_requires_tty");
1449
- }
1450
- const provider = options.provider ?? createDefaultWalletSecretProvider();
1451
- const nowUnixMs = options.nowUnixMs ?? Date.now();
1452
- const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1453
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1454
- if (paths.selectedSeedName !== "main") {
1455
- throw new Error("wallet_import_seed_not_supported");
1456
- }
1457
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1458
- purpose: "wallet-import",
1459
- walletRootId: null,
1460
- });
1461
- try {
1462
- const archivePassphrase = await promptRequiredValue(options.prompter, "Archive passphrase: ");
1463
- const payload = await readPortableWalletArchive(options.archivePath, archivePassphrase);
1464
- const replacementStateExists = await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath);
1465
- const importedWalletDir = join(options.dataDir, "wallets", sanitizeWalletName(payload.walletRootId));
1466
- const replacementCoreWalletExists = await pathExists(importedWalletDir);
1467
- await clearPendingInitialization(paths, provider);
1468
- if (replacementStateExists || replacementCoreWalletExists) {
1469
- await confirmTypedAcknowledgement(options.prompter, "IMPORT", "Type IMPORT to replace the existing local wallet state and managed Core wallet replica: ");
1470
- }
1471
- let previousWalletRootId = null;
1472
- try {
1473
- const loaded = await loadWalletState({
1474
- primaryPath: paths.walletStatePath,
1475
- backupPath: paths.walletStateBackupPath,
1476
- }, {
1477
- provider,
1478
- });
1479
- previousWalletRootId = loaded.state.walletRootId;
1480
- }
1481
- catch {
1482
- previousWalletRootId = null;
1483
- }
1484
- const secretReference = createWalletSecretReference(payload.walletRootId);
1485
- const replacementSecret = randomBytes(32);
1486
- await provider.storeSecret(secretReference.keyId, replacementSecret);
1487
- const initialState = createWalletStateFromPortableArchive({
1488
- payload,
1489
- nowUnixMs,
1490
- internalCoreWalletPassphrase: createInternalCoreWalletPassphrase(),
1491
- });
1492
- await clearUnlockSession(paths.walletUnlockSessionPath);
1493
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1494
- await saveWalletState({
1495
- primaryPath: paths.walletStatePath,
1496
- backupPath: paths.walletStateBackupPath,
1497
- }, initialState, {
1498
- provider,
1499
- secretReference,
1500
- });
1501
- const importedState = await recreateManagedCoreWalletReplica(initialState, provider, paths, options.dataDir, nowUnixMs, {
1502
- attachService: options.attachService,
1503
- rpcFactory: options.rpcFactory,
1504
- });
1505
- const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1506
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1507
- await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(importedState, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1508
- provider,
1509
- secretReference,
1510
- });
1511
- await clearPendingInitialization(paths, provider);
1512
- if (previousWalletRootId !== null && previousWalletRootId !== payload.walletRootId) {
1513
- await provider.deleteSecret(createWalletSecretReference(previousWalletRootId).keyId).catch(() => undefined);
1514
- }
1515
- await (options.attachIndexerDaemon ?? attachOrStartIndexerDaemon)({
1516
- dataDir: options.dataDir,
1517
- databasePath: options.databasePath,
1518
- walletRootId: importedState.walletRootId,
1519
- }).then((daemon) => daemon.close());
1520
- await ensureMainWalletSeedIndexRecord({
1521
- paths: resolveMainWalletPaths(paths),
1522
- walletRootId: importedState.walletRootId,
1523
- nowUnixMs,
1524
- });
1525
- return {
1526
- archivePath: options.archivePath,
1527
- walletRootId: importedState.walletRootId,
1528
- fundingAddress: importedState.funding.address,
1529
- unlockUntilUnixMs,
1530
- state: importedState,
1531
- };
1532
- }
1533
- finally {
1534
- await controlLock.release();
1535
- }
1536
- }
1537
1135
  export async function restoreWalletFromMnemonic(options) {
1538
1136
  if (!options.prompter.isInteractive) {
1539
1137
  throw new Error("wallet_restore_requires_tty");
1540
1138
  }
1541
1139
  const provider = options.provider ?? createDefaultWalletSecretProvider();
1140
+ const interactiveProvider = withInteractiveWalletSecretProvider(provider, options.prompter);
1542
1141
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1543
- const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1544
1142
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1545
1143
  const seedName = assertValidImportedWalletSeedName(paths.selectedSeedName);
1546
1144
  const controlLock = await acquireFileLock(paths.walletControlLockPath, {
@@ -1556,7 +1154,8 @@ export async function restoreWalletFromMnemonic(options) {
1556
1154
  if (findWalletSeedRecord(seedIndex, seedName) !== null) {
1557
1155
  throw new Error("wallet_seed_name_exists");
1558
1156
  }
1559
- await ensureWalletNotInitialized(paths, provider);
1157
+ const passwordAction = await ensureClientPasswordConfigured(provider, options.prompter);
1158
+ await ensureWalletNotInitialized(paths, interactiveProvider);
1560
1159
  let promptPhaseStarted = false;
1561
1160
  let mnemonicPhrase;
1562
1161
  try {
@@ -1568,39 +1167,33 @@ export async function restoreWalletFromMnemonic(options) {
1568
1167
  await options.prompter.clearSensitiveDisplay?.("restore-mnemonic-entry");
1569
1168
  }
1570
1169
  }
1571
- await clearPendingInitialization(paths, provider);
1170
+ await clearPendingInitialization(paths, interactiveProvider);
1572
1171
  const material = deriveWalletMaterialFromMnemonic(mnemonicPhrase);
1573
1172
  const walletRootId = createWalletRootId();
1574
1173
  const internalCoreWalletPassphrase = createInternalCoreWalletPassphrase();
1575
1174
  const secretReference = createWalletSecretReference(walletRootId);
1576
1175
  const secret = randomBytes(32);
1577
- await provider.storeSecret(secretReference.keyId, secret);
1176
+ await interactiveProvider.storeSecret(secretReference.keyId, secret);
1578
1177
  const initialState = createInitialWalletState({
1579
1178
  walletRootId,
1580
1179
  nowUnixMs,
1581
1180
  material,
1582
1181
  internalCoreWalletPassphrase,
1583
1182
  });
1584
- await clearUnlockSession(paths.walletUnlockSessionPath);
1585
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1183
+ await clearLegacyWalletLockArtifacts(paths.walletRuntimeRoot);
1586
1184
  await saveWalletState({
1587
1185
  primaryPath: paths.walletStatePath,
1588
1186
  backupPath: paths.walletStateBackupPath,
1589
1187
  }, initialState, {
1590
- provider,
1188
+ provider: interactiveProvider,
1591
1189
  secretReference,
1592
1190
  });
1593
- const restoredState = await recreateManagedCoreWalletReplica(initialState, provider, paths, options.dataDir, nowUnixMs, {
1191
+ const restoredState = await recreateManagedCoreWalletReplica(initialState, interactiveProvider, paths, options.dataDir, nowUnixMs, {
1594
1192
  attachService: options.attachService,
1595
1193
  rpcFactory: options.rpcFactory,
1596
1194
  });
1597
- const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1598
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1599
- await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(restoredState, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1600
- provider,
1601
- secretReference,
1602
- });
1603
- await clearPendingInitialization(paths, provider);
1195
+ await clearLegacyWalletLockArtifacts(paths.walletRuntimeRoot);
1196
+ await clearPendingInitialization(paths, interactiveProvider);
1604
1197
  await addImportedWalletSeedRecord({
1605
1198
  paths: mainPaths,
1606
1199
  seedName,
@@ -1608,10 +1201,10 @@ export async function restoreWalletFromMnemonic(options) {
1608
1201
  nowUnixMs,
1609
1202
  });
1610
1203
  return {
1204
+ passwordAction,
1611
1205
  seedName,
1612
1206
  walletRootId,
1613
1207
  fundingAddress: restoredState.funding.address,
1614
- unlockUntilUnixMs,
1615
1208
  state: restoredState,
1616
1209
  warnings: [],
1617
1210
  };
@@ -1670,8 +1263,7 @@ export async function deleteImportedWalletSeed(options) {
1670
1263
  await rpc.unloadWallet(walletName, false).catch(() => undefined);
1671
1264
  }
1672
1265
  }
1673
- await clearUnlockSession(paths.walletUnlockSessionPath).catch(() => undefined);
1674
- await clearWalletExplicitLock(paths.walletExplicitLockPath).catch(() => undefined);
1266
+ await clearLegacyWalletLockArtifacts(paths.walletRuntimeRoot).catch(() => undefined);
1675
1267
  await clearPendingInitialization(paths, provider).catch(() => undefined);
1676
1268
  await provider.deleteSecret(createWalletSecretReference(seedRecord.walletRootId).keyId).catch(() => undefined);
1677
1269
  await rm(paths.walletStateRoot, { recursive: true, force: true }).catch(() => undefined);
@@ -1703,7 +1295,6 @@ export async function repairWallet(options) {
1703
1295
  const attachManagedBitcoind = options.attachService ?? attachOrStartManagedBitcoindService;
1704
1296
  const probeManagedIndexerDaemon = options.probeIndexerDaemon ?? probeIndexerDaemon;
1705
1297
  const attachManagedIndexerDaemon = options.attachIndexerDaemon ?? attachOrStartIndexerDaemon;
1706
- const requestMiningPreemptionForRepair = options.requestMiningPreemption ?? requestMiningGenerationPreemption;
1707
1298
  await clearOrphanedRepairLocks([
1708
1299
  paths.walletControlLockPath,
1709
1300
  paths.miningControlLockPath,
@@ -1713,7 +1304,6 @@ export async function repairWallet(options) {
1713
1304
  walletRootId: null,
1714
1305
  });
1715
1306
  try {
1716
- let miningPreemption = null;
1717
1307
  let loaded;
1718
1308
  try {
1719
1309
  loaded = await loadWalletState({
@@ -1723,7 +1313,7 @@ export async function repairWallet(options) {
1723
1313
  provider,
1724
1314
  });
1725
1315
  }
1726
- catch {
1316
+ catch (error) {
1727
1317
  throw new Error("local-state-corrupt");
1728
1318
  }
1729
1319
  const recoveredFromBackup = loaded.source === "backup";
@@ -1736,25 +1326,15 @@ export async function repairWallet(options) {
1736
1326
  servicePaths.indexerDaemonLockPath,
1737
1327
  ]);
1738
1328
  const preRepairMiningRuntime = await loadMiningRuntimeStatus(paths.miningStatusPath).catch(() => null);
1739
- const backgroundWorkerAlive = preRepairMiningRuntime?.runMode === "background"
1740
- && preRepairMiningRuntime.backgroundWorkerPid !== null
1741
- && await isProcessAlive(preRepairMiningRuntime.backgroundWorkerPid);
1742
- const preRepairUnlockedState = await loadUnlockedWalletState({
1743
- provider,
1744
- nowUnixMs,
1329
+ const miningCleanup = await cleanupMiningForRepair({
1745
1330
  paths,
1331
+ state: repairedState,
1332
+ snapshot: preRepairMiningRuntime,
1333
+ nowUnixMs,
1746
1334
  });
1747
- const miningPreRepairRunMode = backgroundWorkerAlive
1748
- ? "background"
1749
- : preRepairMiningRuntime?.runMode === "foreground"
1750
- ? "foreground"
1751
- : "stopped";
1335
+ const miningPreRepairRunMode = miningCleanup.preRepairRunMode;
1752
1336
  const miningWasResumable = miningPreRepairRunMode === "background"
1753
- && preRepairUnlockedState !== null
1754
1337
  && normalizeMiningStateRecord(repairedState.miningState).state !== "repair-required";
1755
- const savedUnlockUntilUnixMs = miningWasResumable
1756
- ? preRepairUnlockedState?.session.unlockUntilUnixMs ?? null
1757
- : null;
1758
1338
  let initialBitcoindProbe = {
1759
1339
  compatibility: "unreachable",
1760
1340
  status: null,
@@ -1770,340 +1350,305 @@ export async function repairWallet(options) {
1770
1350
  : "none";
1771
1351
  let miningPostRepairRunMode = "stopped";
1772
1352
  let miningResumeError = null;
1353
+ if (miningPreRepairRunMode !== "stopped" || preRepairMiningRuntime?.runMode !== "stopped") {
1354
+ repairedState = applyRepairStoppedMiningState(repairedState);
1355
+ repairStateNeedsPersist = true;
1356
+ }
1357
+ if (!(options.assumeYes ?? false)) {
1358
+ await ensureIndexerDatabaseHealthy({
1359
+ databasePath: options.databasePath,
1360
+ dataDir: options.dataDir,
1361
+ walletRootId: repairedState.walletRootId,
1362
+ resetIfNeeded: false,
1363
+ });
1364
+ }
1365
+ const bitcoindLock = await acquireFileLock(servicePaths.bitcoindLockPath, {
1366
+ purpose: "managed-bitcoind-repair",
1367
+ walletRootId: repairedState.walletRootId,
1368
+ dataDir: options.dataDir,
1369
+ });
1370
+ let resetIndexerDatabase = false;
1371
+ let bitcoindHandle = null;
1372
+ let bitcoindPostRepairHealth = "unavailable";
1773
1373
  try {
1774
- miningPreemption = await requestMiningPreemptionForRepair({
1775
- paths,
1776
- reason: "wallet-repair",
1374
+ initialBitcoindProbe = await probeManagedBitcoind({
1375
+ dataDir: options.dataDir,
1376
+ chain: "main",
1377
+ startHeight: 0,
1378
+ walletRootId: repairedState.walletRootId,
1777
1379
  });
1778
- if (backgroundWorkerAlive && preRepairMiningRuntime !== null) {
1779
- const miningLock = await acquireFileLock(paths.miningControlLockPath, {
1780
- purpose: "wallet-repair-stop-background",
1781
- });
1380
+ bitcoindCompatibilityIssue = mapBitcoindCompatibilityToRepairIssue(initialBitcoindProbe.compatibility);
1381
+ if (initialBitcoindProbe.compatibility === "service-version-mismatch"
1382
+ || initialBitcoindProbe.compatibility === "wallet-root-mismatch"
1383
+ || initialBitcoindProbe.compatibility === "runtime-mismatch") {
1384
+ const processId = initialBitcoindProbe.status?.processId ?? null;
1385
+ if (processId === null) {
1386
+ throw new Error("managed_bitcoind_process_id_unavailable");
1387
+ }
1782
1388
  try {
1783
- await stopBackgroundMiningForRepair({
1784
- paths,
1785
- snapshot: preRepairMiningRuntime,
1786
- nowUnixMs,
1787
- });
1389
+ process.kill(processId, "SIGTERM");
1788
1390
  }
1789
- finally {
1790
- await miningLock.release();
1391
+ catch (error) {
1392
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
1393
+ throw error;
1394
+ }
1791
1395
  }
1792
- repairedState = applyRepairStoppedMiningState(repairedState);
1793
- repairStateNeedsPersist = true;
1794
- }
1795
- if (!(options.assumeYes ?? false)) {
1796
- await ensureIndexerDatabaseHealthy({
1797
- databasePath: options.databasePath,
1798
- dataDir: options.dataDir,
1799
- walletRootId: repairedState.walletRootId,
1800
- resetIfNeeded: false,
1801
- });
1396
+ await waitForProcessExit(processId, 15_000, "managed_bitcoind_stop_timeout");
1397
+ await clearManagedBitcoindArtifacts(servicePaths);
1398
+ bitcoindServiceAction = "stopped-incompatible-service";
1802
1399
  }
1803
- const bitcoindLock = await acquireFileLock(servicePaths.bitcoindLockPath, {
1804
- purpose: "managed-bitcoind-repair",
1805
- walletRootId: repairedState.walletRootId,
1806
- dataDir: options.dataDir,
1807
- });
1808
- let resetIndexerDatabase = false;
1809
- let bitcoindHandle = null;
1810
- let bitcoindPostRepairHealth = "unavailable";
1811
- try {
1812
- initialBitcoindProbe = await probeManagedBitcoind({
1813
- dataDir: options.dataDir,
1814
- chain: "main",
1815
- startHeight: 0,
1816
- walletRootId: repairedState.walletRootId,
1817
- });
1818
- bitcoindCompatibilityIssue = mapBitcoindCompatibilityToRepairIssue(initialBitcoindProbe.compatibility);
1819
- if (initialBitcoindProbe.compatibility === "service-version-mismatch"
1820
- || initialBitcoindProbe.compatibility === "wallet-root-mismatch"
1821
- || initialBitcoindProbe.compatibility === "runtime-mismatch") {
1822
- const processId = initialBitcoindProbe.status?.processId ?? null;
1823
- if (processId === null) {
1824
- throw new Error("managed_bitcoind_process_id_unavailable");
1825
- }
1826
- try {
1827
- process.kill(processId, "SIGTERM");
1828
- }
1829
- catch (error) {
1830
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
1831
- throw error;
1832
- }
1833
- }
1834
- await waitForProcessExit(processId, 15_000, "managed_bitcoind_stop_timeout");
1400
+ else if (initialBitcoindProbe.compatibility === "unreachable") {
1401
+ const hasStaleArtifacts = await pathExists(servicePaths.bitcoindStatusPath)
1402
+ || await pathExists(servicePaths.bitcoindPidPath)
1403
+ || await pathExists(servicePaths.bitcoindReadyPath)
1404
+ || await pathExists(servicePaths.bitcoindWalletStatusPath);
1405
+ if (hasStaleArtifacts) {
1835
1406
  await clearManagedBitcoindArtifacts(servicePaths);
1836
- bitcoindServiceAction = "stopped-incompatible-service";
1407
+ bitcoindServiceAction = "cleared-stale-artifacts";
1837
1408
  }
1838
- else if (initialBitcoindProbe.compatibility === "unreachable") {
1839
- const hasStaleArtifacts = await pathExists(servicePaths.bitcoindStatusPath)
1840
- || await pathExists(servicePaths.bitcoindPidPath)
1841
- || await pathExists(servicePaths.bitcoindReadyPath)
1842
- || await pathExists(servicePaths.bitcoindWalletStatusPath);
1843
- if (hasStaleArtifacts) {
1844
- await clearManagedBitcoindArtifacts(servicePaths);
1845
- bitcoindServiceAction = "cleared-stale-artifacts";
1846
- }
1847
- }
1848
- else if (initialBitcoindProbe.compatibility === "protocol-error") {
1849
- throw new Error(initialBitcoindProbe.error ?? "managed_bitcoind_protocol_error");
1850
- }
1851
- }
1852
- finally {
1853
- await bitcoindLock.release();
1854
1409
  }
1855
- bitcoindHandle = await attachManagedBitcoind({
1856
- dataDir: options.dataDir,
1857
- chain: "main",
1858
- startHeight: 0,
1859
- walletRootId: repairedState.walletRootId,
1860
- });
1861
- const bitcoindRpc = (options.rpcFactory ?? createRpcClient)(bitcoindHandle.rpc);
1862
- const normalizedDescriptorState = await normalizeWalletDescriptorState(repairedState, bitcoindRpc);
1863
- if (normalizedDescriptorState.changed) {
1864
- repairedState = normalizedDescriptorState.state;
1865
- repairStateNeedsPersist = true;
1410
+ else if (initialBitcoindProbe.compatibility === "protocol-error") {
1411
+ throw new Error(initialBitcoindProbe.error ?? "managed_bitcoind_protocol_error");
1866
1412
  }
1867
- const reconciledCoinControl = await persistWalletCoinControlStateIfNeeded({
1868
- state: repairedState,
1869
- access: {
1870
- provider,
1871
- secretReference,
1872
- },
1873
- paths,
1874
- nowUnixMs,
1875
- replacePrimary: recoveredFromBackup && !repairStateNeedsPersist,
1876
- rpc: createRpcClient(bitcoindHandle.rpc),
1413
+ }
1414
+ finally {
1415
+ await bitcoindLock.release();
1416
+ }
1417
+ bitcoindHandle = await attachManagedBitcoind({
1418
+ dataDir: options.dataDir,
1419
+ chain: "main",
1420
+ startHeight: 0,
1421
+ walletRootId: repairedState.walletRootId,
1422
+ });
1423
+ const bitcoindRpc = (options.rpcFactory ?? createRpcClient)(bitcoindHandle.rpc);
1424
+ const normalizedDescriptorState = await normalizeWalletDescriptorState(repairedState, bitcoindRpc);
1425
+ if (normalizedDescriptorState.changed) {
1426
+ repairedState = normalizedDescriptorState.state;
1427
+ repairStateNeedsPersist = true;
1428
+ }
1429
+ const reconciledCoinControl = await persistWalletCoinControlStateIfNeeded({
1430
+ state: repairedState,
1431
+ access: {
1432
+ provider,
1433
+ secretReference,
1434
+ },
1435
+ paths,
1436
+ nowUnixMs,
1437
+ replacePrimary: recoveredFromBackup && !repairStateNeedsPersist,
1438
+ rpc: (options.rpcFactory ?? createRpcClient)(bitcoindHandle.rpc),
1439
+ });
1440
+ repairedState = reconciledCoinControl.state;
1441
+ if (reconciledCoinControl.changed) {
1442
+ repairStateNeedsPersist = false;
1443
+ }
1444
+ let replica = await verifyManagedCoreWalletReplica(repairedState, options.dataDir, {
1445
+ nodeHandle: bitcoindHandle,
1446
+ attachService: options.attachService,
1447
+ rpcFactory: options.rpcFactory,
1448
+ });
1449
+ let recreatedManagedCoreWallet = false;
1450
+ if (replica.proofStatus !== "ready") {
1451
+ repairedState = await recreateManagedCoreWalletReplica(repairedState, provider, paths, options.dataDir, nowUnixMs, {
1452
+ attachService: options.attachService,
1453
+ rpcFactory: options.rpcFactory,
1877
1454
  });
1878
- repairedState = reconciledCoinControl.state;
1879
- if (reconciledCoinControl.changed) {
1880
- repairStateNeedsPersist = false;
1881
- }
1882
- let replica = await verifyManagedCoreWalletReplica(repairedState, options.dataDir, {
1455
+ recreatedManagedCoreWallet = true;
1456
+ managedCoreReplicaAction = "recreated";
1457
+ repairStateNeedsPersist = false;
1458
+ replica = await verifyManagedCoreWalletReplica(repairedState, options.dataDir, {
1883
1459
  nodeHandle: bitcoindHandle,
1884
1460
  attachService: options.attachService,
1885
1461
  rpcFactory: options.rpcFactory,
1886
1462
  });
1887
- let recreatedManagedCoreWallet = false;
1888
- if (replica.proofStatus !== "ready") {
1889
- repairedState = await recreateManagedCoreWalletReplica(repairedState, provider, paths, options.dataDir, nowUnixMs, {
1890
- attachService: options.attachService,
1891
- rpcFactory: options.rpcFactory,
1892
- });
1893
- recreatedManagedCoreWallet = true;
1894
- managedCoreReplicaAction = "recreated";
1895
- repairStateNeedsPersist = false;
1896
- replica = await verifyManagedCoreWalletReplica(repairedState, options.dataDir, {
1897
- nodeHandle: bitcoindHandle,
1898
- attachService: options.attachService,
1899
- rpcFactory: options.rpcFactory,
1900
- });
1901
- }
1902
- const finalBitcoindStatus = await bitcoindHandle.refreshServiceStatus?.() ?? null;
1903
- const chainInfo = await bitcoindRpc.getBlockchainInfo();
1904
- bitcoindPostRepairHealth = mapBitcoindRepairHealth({
1905
- serviceState: finalBitcoindStatus?.state ?? null,
1906
- catchingUp: chainInfo.blocks < chainInfo.headers,
1907
- replica,
1908
- });
1909
- if (bitcoindServiceAction === "none" && initialBitcoindProbe.compatibility === "unreachable") {
1910
- bitcoindServiceAction = "restarted-compatible-service";
1911
- }
1912
- let initialIndexerDaemonInstanceId = null;
1913
- let preAttachIndexerDaemonInstanceId = null;
1914
- const indexerLock = await acquireFileLock(servicePaths.indexerDaemonLockPath, {
1915
- purpose: "indexer-daemon-repair",
1916
- walletRootId: repairedState.walletRootId,
1463
+ }
1464
+ const finalBitcoindStatus = await bitcoindHandle.refreshServiceStatus?.() ?? null;
1465
+ const chainInfo = await bitcoindRpc.getBlockchainInfo();
1466
+ bitcoindPostRepairHealth = mapBitcoindRepairHealth({
1467
+ serviceState: finalBitcoindStatus?.state ?? null,
1468
+ catchingUp: chainInfo.blocks < chainInfo.headers,
1469
+ replica,
1470
+ });
1471
+ if (bitcoindServiceAction === "none" && initialBitcoindProbe.compatibility === "unreachable") {
1472
+ bitcoindServiceAction = "restarted-compatible-service";
1473
+ }
1474
+ let initialIndexerDaemonInstanceId = null;
1475
+ let preAttachIndexerDaemonInstanceId = null;
1476
+ const indexerLock = await acquireFileLock(servicePaths.indexerDaemonLockPath, {
1477
+ purpose: "indexer-daemon-repair",
1478
+ walletRootId: repairedState.walletRootId,
1479
+ dataDir: options.dataDir,
1480
+ databasePath: options.databasePath,
1481
+ });
1482
+ try {
1483
+ const initialProbe = await probeManagedIndexerDaemon({
1917
1484
  dataDir: options.dataDir,
1918
- databasePath: options.databasePath,
1485
+ walletRootId: repairedState.walletRootId,
1919
1486
  });
1920
- try {
1921
- const initialProbe = await probeManagedIndexerDaemon({
1922
- dataDir: options.dataDir,
1923
- walletRootId: repairedState.walletRootId,
1924
- });
1925
- indexerCompatibilityIssue = mapIndexerCompatibilityToRepairIssue(initialProbe.compatibility);
1926
- initialIndexerDaemonInstanceId = initialProbe.status?.daemonInstanceId ?? null;
1927
- if (initialProbe.compatibility === "compatible") {
1928
- await initialProbe.client?.close().catch(() => undefined);
1487
+ indexerCompatibilityIssue = mapIndexerCompatibilityToRepairIssue(initialProbe.compatibility);
1488
+ initialIndexerDaemonInstanceId = initialProbe.status?.daemonInstanceId ?? null;
1489
+ if (initialProbe.compatibility === "compatible") {
1490
+ await initialProbe.client?.close().catch(() => undefined);
1491
+ }
1492
+ else if (initialProbe.compatibility === "service-version-mismatch"
1493
+ || initialProbe.compatibility === "wallet-root-mismatch"
1494
+ || initialProbe.compatibility === "schema-mismatch") {
1495
+ const processId = initialProbe.status?.processId ?? null;
1496
+ if (processId === null) {
1497
+ throw new Error("indexer_daemon_process_id_unavailable");
1929
1498
  }
1930
- else if (initialProbe.compatibility === "service-version-mismatch"
1931
- || initialProbe.compatibility === "wallet-root-mismatch"
1932
- || initialProbe.compatibility === "schema-mismatch") {
1933
- const processId = initialProbe.status?.processId ?? null;
1934
- if (processId === null) {
1935
- throw new Error("indexer_daemon_process_id_unavailable");
1936
- }
1937
- try {
1938
- process.kill(processId, "SIGTERM");
1939
- }
1940
- catch (error) {
1941
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
1942
- throw error;
1943
- }
1944
- }
1945
- await waitForProcessExit(processId);
1946
- await clearIndexerDaemonArtifacts(servicePaths);
1947
- indexerDaemonAction = "stopped-incompatible-daemon";
1499
+ try {
1500
+ process.kill(processId, "SIGTERM");
1948
1501
  }
1949
- else if (initialProbe.compatibility === "unreachable") {
1950
- const hasStaleArtifacts = await pathExists(servicePaths.indexerDaemonSocketPath)
1951
- || await pathExists(servicePaths.indexerDaemonStatusPath);
1952
- if (hasStaleArtifacts) {
1953
- await clearIndexerDaemonArtifacts(servicePaths);
1954
- indexerDaemonAction = "cleared-stale-artifacts";
1502
+ catch (error) {
1503
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
1504
+ throw error;
1955
1505
  }
1956
1506
  }
1957
- else {
1958
- throw new Error(initialProbe.error ?? "indexer_daemon_protocol_error");
1959
- }
1960
- resetIndexerDatabase = await ensureIndexerDatabaseHealthy({
1961
- databasePath: options.databasePath,
1962
- dataDir: options.dataDir,
1963
- walletRootId: repairedState.walletRootId,
1964
- resetIfNeeded: options.assumeYes ?? false,
1965
- });
1507
+ await waitForProcessExit(processId);
1508
+ await clearIndexerDaemonArtifacts(servicePaths);
1509
+ indexerDaemonAction = "stopped-incompatible-daemon";
1966
1510
  }
1967
- finally {
1968
- await indexerLock.release();
1511
+ else if (initialProbe.compatibility === "unreachable") {
1512
+ const hasStaleArtifacts = await pathExists(servicePaths.indexerDaemonSocketPath)
1513
+ || await pathExists(servicePaths.indexerDaemonStatusPath);
1514
+ if (hasStaleArtifacts) {
1515
+ await clearIndexerDaemonArtifacts(servicePaths);
1516
+ indexerDaemonAction = "cleared-stale-artifacts";
1517
+ }
1969
1518
  }
1970
- if (recoveredFromBackup) {
1971
- repairedState = await persistRepairState({
1972
- state: repairedState,
1973
- provider,
1974
- paths,
1975
- nowUnixMs,
1976
- replacePrimary: true,
1977
- });
1978
- repairStateNeedsPersist = false;
1519
+ else {
1520
+ throw new Error(initialProbe.error ?? "indexer_daemon_protocol_error");
1979
1521
  }
1980
- else if (repairStateNeedsPersist) {
1981
- repairedState = await persistRepairState({
1982
- state: repairedState,
1983
- provider,
1984
- paths,
1985
- nowUnixMs,
1986
- });
1987
- repairStateNeedsPersist = false;
1988
- }
1989
- const preAttachProbe = await probeManagedIndexerDaemon({
1522
+ resetIndexerDatabase = await ensureIndexerDatabaseHealthy({
1523
+ databasePath: options.databasePath,
1990
1524
  dataDir: options.dataDir,
1991
1525
  walletRootId: repairedState.walletRootId,
1526
+ resetIfNeeded: options.assumeYes ?? false,
1992
1527
  });
1993
- if (preAttachProbe.compatibility === "compatible") {
1994
- preAttachIndexerDaemonInstanceId = preAttachProbe.status?.daemonInstanceId ?? null;
1995
- await preAttachProbe.client?.close().catch(() => undefined);
1996
- }
1997
- else if (preAttachProbe.compatibility !== "unreachable") {
1998
- throw new Error(preAttachProbe.error ?? "indexer_daemon_protocol_error");
1999
- }
2000
- const daemon = await attachManagedIndexerDaemon({
1528
+ }
1529
+ finally {
1530
+ await indexerLock.release();
1531
+ }
1532
+ if (recoveredFromBackup) {
1533
+ repairedState = await persistRepairState({
1534
+ state: repairedState,
1535
+ provider,
1536
+ paths,
1537
+ nowUnixMs,
1538
+ replacePrimary: true,
1539
+ });
1540
+ repairStateNeedsPersist = false;
1541
+ }
1542
+ else if (repairStateNeedsPersist) {
1543
+ repairedState = await persistRepairState({
1544
+ state: repairedState,
1545
+ provider,
1546
+ paths,
1547
+ nowUnixMs,
1548
+ });
1549
+ repairStateNeedsPersist = false;
1550
+ }
1551
+ const preAttachProbe = await probeManagedIndexerDaemon({
1552
+ dataDir: options.dataDir,
1553
+ walletRootId: repairedState.walletRootId,
1554
+ });
1555
+ if (preAttachProbe.compatibility === "compatible") {
1556
+ preAttachIndexerDaemonInstanceId = preAttachProbe.status?.daemonInstanceId ?? null;
1557
+ await preAttachProbe.client?.close().catch(() => undefined);
1558
+ }
1559
+ else if (preAttachProbe.compatibility !== "unreachable") {
1560
+ throw new Error(preAttachProbe.error ?? "indexer_daemon_protocol_error");
1561
+ }
1562
+ const daemon = await attachManagedIndexerDaemon({
1563
+ dataDir: options.dataDir,
1564
+ databasePath: options.databasePath,
1565
+ walletRootId: repairedState.walletRootId,
1566
+ });
1567
+ try {
1568
+ const { health: indexerPostRepairHealth, daemonInstanceId: postRepairDaemonInstanceId, } = await verifyIndexerPostRepairHealth({
1569
+ daemon,
1570
+ probeIndexerDaemon: probeManagedIndexerDaemon,
2001
1571
  dataDir: options.dataDir,
2002
- databasePath: options.databasePath,
2003
1572
  walletRootId: repairedState.walletRootId,
1573
+ nowUnixMs,
2004
1574
  });
2005
- try {
2006
- const { health: indexerPostRepairHealth, daemonInstanceId: postRepairDaemonInstanceId, } = await verifyIndexerPostRepairHealth({
2007
- daemon,
2008
- probeIndexerDaemon: probeManagedIndexerDaemon,
2009
- dataDir: options.dataDir,
2010
- walletRootId: repairedState.walletRootId,
2011
- nowUnixMs,
1575
+ const restartedIndexerDaemon = indexerDaemonAction !== "none" || preAttachProbe.compatibility === "unreachable";
1576
+ if (restartedIndexerDaemon
1577
+ && initialIndexerDaemonInstanceId !== null
1578
+ && postRepairDaemonInstanceId === initialIndexerDaemonInstanceId) {
1579
+ throw new Error("indexer_daemon_repair_identity_not_rotated");
1580
+ }
1581
+ if (!restartedIndexerDaemon
1582
+ && preAttachProbe.compatibility === "compatible"
1583
+ && preAttachIndexerDaemonInstanceId !== null
1584
+ && postRepairDaemonInstanceId !== preAttachIndexerDaemonInstanceId) {
1585
+ throw new Error("indexer_daemon_repair_identity_changed");
1586
+ }
1587
+ if (indexerDaemonAction === "none" && preAttachProbe.compatibility === "unreachable") {
1588
+ indexerDaemonAction = "restarted-compatible-daemon";
1589
+ }
1590
+ if (miningWasResumable) {
1591
+ const postRepairResumeReady = await canResumeBackgroundMiningAfterRepair({
1592
+ provider,
1593
+ paths,
1594
+ repairedState,
1595
+ bitcoindPostRepairHealth,
1596
+ indexerPostRepairHealth,
2012
1597
  });
2013
- const restartedIndexerDaemon = indexerDaemonAction !== "none" || preAttachProbe.compatibility === "unreachable";
2014
- if (restartedIndexerDaemon
2015
- && initialIndexerDaemonInstanceId !== null
2016
- && postRepairDaemonInstanceId === initialIndexerDaemonInstanceId) {
2017
- throw new Error("indexer_daemon_repair_identity_not_rotated");
2018
- }
2019
- if (!restartedIndexerDaemon
2020
- && preAttachProbe.compatibility === "compatible"
2021
- && preAttachIndexerDaemonInstanceId !== null
2022
- && postRepairDaemonInstanceId !== preAttachIndexerDaemonInstanceId) {
2023
- throw new Error("indexer_daemon_repair_identity_changed");
2024
- }
2025
- if (indexerDaemonAction === "none" && preAttachProbe.compatibility === "unreachable") {
2026
- indexerDaemonAction = "restarted-compatible-daemon";
1598
+ if (!postRepairResumeReady) {
1599
+ miningResumeAction = "skipped-post-repair-blocked";
2027
1600
  }
2028
- let keepUnlockSession = false;
2029
- if (miningWasResumable) {
2030
- const postRepairResumeReady = await canResumeBackgroundMiningAfterRepair({
2031
- provider,
2032
- paths,
2033
- repairedState,
2034
- nowUnixMs,
2035
- bitcoindPostRepairHealth,
2036
- indexerPostRepairHealth,
2037
- unlockUntilUnixMs: savedUnlockUntilUnixMs,
2038
- });
2039
- if (!postRepairResumeReady) {
2040
- miningResumeAction = "skipped-post-repair-blocked";
2041
- }
2042
- else if (savedUnlockUntilUnixMs === null || savedUnlockUntilUnixMs <= nowUnixMs) {
2043
- miningResumeAction = "skipped-post-repair-blocked";
2044
- }
2045
- else {
2046
- await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(repairedState, savedUnlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1601
+ else {
1602
+ try {
1603
+ const startBackgroundMining = options.startBackgroundMining
1604
+ ?? (await import("./mining/runner.js")).startBackgroundMining;
1605
+ const resumed = await startBackgroundMining({
1606
+ dataDir: options.dataDir,
1607
+ databasePath: options.databasePath,
2047
1608
  provider,
2048
- secretReference,
1609
+ paths,
1610
+ prompter: createSilentNonInteractivePrompter(),
2049
1611
  });
2050
- keepUnlockSession = true;
2051
- try {
2052
- const startBackgroundMining = options.startBackgroundMining
2053
- ?? (await import("./mining/runner.js")).startBackgroundMining;
2054
- const resumed = await startBackgroundMining({
2055
- dataDir: options.dataDir,
2056
- databasePath: options.databasePath,
2057
- provider,
2058
- paths,
2059
- prompter: createSilentNonInteractivePrompter(),
2060
- });
2061
- if (resumed.snapshot?.runMode === "background") {
2062
- miningResumeAction = "resumed-background";
2063
- miningPostRepairRunMode = "background";
2064
- }
2065
- else {
2066
- miningResumeAction = "resume-failed";
2067
- miningResumeError = "Background mining did not report a background runtime after repair.";
2068
- }
1612
+ if (resumed.snapshot?.runMode === "background") {
1613
+ miningResumeAction = "resumed-background";
1614
+ miningPostRepairRunMode = "background";
2069
1615
  }
2070
- catch (error) {
1616
+ else {
2071
1617
  miningResumeAction = "resume-failed";
2072
- miningResumeError = error instanceof Error ? error.message : String(error);
1618
+ miningResumeError = "Background mining did not report a background runtime after repair.";
2073
1619
  }
2074
1620
  }
1621
+ catch (error) {
1622
+ miningResumeAction = "resume-failed";
1623
+ miningResumeError = error instanceof Error ? error.message : String(error);
1624
+ }
2075
1625
  }
2076
- if (!keepUnlockSession) {
2077
- await clearUnlockSession(paths.walletUnlockSessionPath);
2078
- }
2079
- return {
2080
- walletRootId: repairedState.walletRootId,
2081
- recoveredFromBackup,
2082
- recreatedManagedCoreWallet,
2083
- resetIndexerDatabase,
2084
- bitcoindServiceAction,
2085
- bitcoindCompatibilityIssue,
2086
- managedCoreReplicaAction,
2087
- bitcoindPostRepairHealth,
2088
- indexerDaemonAction,
2089
- indexerCompatibilityIssue,
2090
- indexerPostRepairHealth,
2091
- miningPreRepairRunMode,
2092
- miningResumeAction,
2093
- miningPostRepairRunMode,
2094
- miningResumeError,
2095
- note: resetIndexerDatabase
2096
- ? "Indexer artifacts were reset and may still be catching up."
2097
- : null,
2098
- };
2099
- }
2100
- finally {
2101
- await daemon.close().catch(() => undefined);
2102
- await bitcoindHandle?.stop?.().catch(() => undefined);
2103
1626
  }
1627
+ await clearLegacyWalletLockArtifacts(paths.walletRuntimeRoot);
1628
+ return {
1629
+ walletRootId: repairedState.walletRootId,
1630
+ recoveredFromBackup,
1631
+ recreatedManagedCoreWallet,
1632
+ resetIndexerDatabase,
1633
+ bitcoindServiceAction,
1634
+ bitcoindCompatibilityIssue,
1635
+ managedCoreReplicaAction,
1636
+ bitcoindPostRepairHealth,
1637
+ indexerDaemonAction,
1638
+ indexerCompatibilityIssue,
1639
+ indexerPostRepairHealth,
1640
+ miningPreRepairRunMode,
1641
+ miningResumeAction,
1642
+ miningPostRepairRunMode,
1643
+ miningResumeError,
1644
+ note: resetIndexerDatabase
1645
+ ? "Indexer artifacts were reset and may still be catching up."
1646
+ : null,
1647
+ };
2104
1648
  }
2105
1649
  finally {
2106
- await miningPreemption?.release().catch(() => undefined);
1650
+ await daemon.close().catch(() => undefined);
1651
+ await bitcoindHandle?.stop?.().catch(() => undefined);
2107
1652
  }
2108
1653
  }
2109
1654
  finally {