@cogcoin/client 0.5.15 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +80 -25
  2. package/dist/app-paths.d.ts +5 -6
  3. package/dist/app-paths.js +8 -16
  4. package/dist/art/balance.txt +10 -0
  5. package/dist/art/welcome.txt +16 -0
  6. package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
  7. package/dist/bitcoind/bootstrap/controller.js +53 -1
  8. package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
  9. package/dist/bitcoind/client/follow-block-times.js +1 -1
  10. package/dist/bitcoind/client/internal-types.d.ts +7 -3
  11. package/dist/bitcoind/client/managed-client.d.ts +4 -2
  12. package/dist/bitcoind/client/managed-client.js +14 -0
  13. package/dist/bitcoind/client/sync-engine.js +72 -11
  14. package/dist/bitcoind/hash-order.d.ts +4 -0
  15. package/dist/bitcoind/hash-order.js +13 -0
  16. package/dist/bitcoind/indexer-daemon-main.js +11 -3
  17. package/dist/bitcoind/normalize.js +3 -2
  18. package/dist/bitcoind/processing-start-height.d.ts +5 -0
  19. package/dist/bitcoind/processing-start-height.js +7 -0
  20. package/dist/bitcoind/progress/constants.d.ts +4 -0
  21. package/dist/bitcoind/progress/constants.js +4 -0
  22. package/dist/bitcoind/progress/controller.d.ts +2 -1
  23. package/dist/bitcoind/progress/controller.js +3 -3
  24. package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
  25. package/dist/bitcoind/progress/follow-scene.js +29 -6
  26. package/dist/bitcoind/progress/formatting.d.ts +1 -0
  27. package/dist/bitcoind/progress/formatting.js +6 -0
  28. package/dist/bitcoind/progress/train-scene.js +37 -18
  29. package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
  30. package/dist/bitcoind/progress/tty-renderer.js +8 -4
  31. package/dist/bitcoind/rpc.d.ts +2 -1
  32. package/dist/bitcoind/rpc.js +3 -0
  33. package/dist/bitcoind/types.d.ts +6 -0
  34. package/dist/bytes.d.ts +1 -0
  35. package/dist/bytes.js +3 -0
  36. package/dist/cli/art.d.ts +2 -0
  37. package/dist/cli/art.js +37 -0
  38. package/dist/cli/commands/client-admin.d.ts +2 -0
  39. package/dist/cli/commands/client-admin.js +91 -0
  40. package/dist/cli/commands/follow.js +0 -2
  41. package/dist/cli/commands/mining-admin.js +6 -47
  42. package/dist/cli/commands/mining-read.js +11 -50
  43. package/dist/cli/commands/mining-runtime.js +142 -5
  44. package/dist/cli/commands/service-runtime.js +0 -2
  45. package/dist/cli/commands/status.js +8 -2
  46. package/dist/cli/commands/sync.js +49 -92
  47. package/dist/cli/commands/wallet-admin.js +142 -136
  48. package/dist/cli/commands/wallet-mutation.js +91 -79
  49. package/dist/cli/commands/wallet-read.js +15 -18
  50. package/dist/cli/context.js +5 -14
  51. package/dist/cli/mining-format.d.ts +0 -1
  52. package/dist/cli/mining-format.js +5 -37
  53. package/dist/cli/mining-json.d.ts +0 -18
  54. package/dist/cli/mining-json.js +0 -35
  55. package/dist/cli/mutation-command-groups.d.ts +1 -2
  56. package/dist/cli/mutation-command-groups.js +0 -5
  57. package/dist/cli/mutation-json.d.ts +24 -145
  58. package/dist/cli/mutation-json.js +30 -136
  59. package/dist/cli/mutation-resolved-json.d.ts +0 -7
  60. package/dist/cli/mutation-resolved-json.js +4 -10
  61. package/dist/cli/mutation-success.d.ts +2 -0
  62. package/dist/cli/mutation-success.js +11 -1
  63. package/dist/cli/mutation-text-format.js +1 -3
  64. package/dist/cli/output.d.ts +1 -1
  65. package/dist/cli/output.js +254 -231
  66. package/dist/cli/parse.d.ts +1 -1
  67. package/dist/cli/parse.js +93 -122
  68. package/dist/cli/preview-json.d.ts +17 -120
  69. package/dist/cli/preview-json.js +14 -97
  70. package/dist/cli/prompt.js +8 -13
  71. package/dist/cli/read-json.d.ts +15 -37
  72. package/dist/cli/read-json.js +44 -140
  73. package/dist/cli/runner.js +10 -13
  74. package/dist/cli/sync-progress.d.ts +6 -0
  75. package/dist/cli/sync-progress.js +91 -0
  76. package/dist/cli/types.d.ts +9 -17
  77. package/dist/cli/types.js +0 -2
  78. package/dist/cli/wallet-format.d.ts +1 -0
  79. package/dist/cli/wallet-format.js +208 -144
  80. package/dist/cli/workflow-hints.d.ts +3 -3
  81. package/dist/cli/workflow-hints.js +11 -8
  82. package/dist/client/default-client.d.ts +3 -1
  83. package/dist/client/default-client.js +45 -2
  84. package/dist/client/factory.js +1 -1
  85. package/dist/client/initialization.js +23 -0
  86. package/dist/client/persistence.js +5 -5
  87. package/dist/client/store-adapter.js +1 -0
  88. package/dist/sqlite/checkpoints.d.ts +1 -0
  89. package/dist/sqlite/checkpoints.js +7 -0
  90. package/dist/sqlite/store.js +14 -1
  91. package/dist/types.d.ts +1 -0
  92. package/dist/wallet/coin-control.d.ts +41 -12
  93. package/dist/wallet/coin-control.js +100 -428
  94. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  95. package/dist/wallet/descriptor-normalization.js +0 -16
  96. package/dist/wallet/lifecycle.d.ts +7 -99
  97. package/dist/wallet/lifecycle.js +513 -968
  98. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  99. package/dist/wallet/managed-core-wallet.js +20 -0
  100. package/dist/wallet/mining/constants.d.ts +5 -12
  101. package/dist/wallet/mining/constants.js +5 -12
  102. package/dist/wallet/mining/control.d.ts +1 -13
  103. package/dist/wallet/mining/control.js +45 -349
  104. package/dist/wallet/mining/index.d.ts +4 -5
  105. package/dist/wallet/mining/index.js +2 -3
  106. package/dist/wallet/mining/runner.d.ts +123 -13
  107. package/dist/wallet/mining/runner.js +899 -511
  108. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  109. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  110. package/dist/wallet/mining/sentence-protocol.js +123 -0
  111. package/dist/wallet/mining/sentences.d.ts +4 -8
  112. package/dist/wallet/mining/sentences.js +3 -52
  113. package/dist/wallet/mining/state.d.ts +11 -6
  114. package/dist/wallet/mining/state.js +7 -6
  115. package/dist/wallet/mining/types.d.ts +2 -30
  116. package/dist/wallet/mining/visualizer.d.ts +31 -3
  117. package/dist/wallet/mining/visualizer.js +135 -13
  118. package/dist/wallet/read/context.d.ts +0 -2
  119. package/dist/wallet/read/context.js +119 -140
  120. package/dist/wallet/read/filter.js +2 -11
  121. package/dist/wallet/read/index.d.ts +1 -1
  122. package/dist/wallet/read/project.js +24 -77
  123. package/dist/wallet/read/types.d.ts +10 -25
  124. package/dist/wallet/reset.d.ts +0 -1
  125. package/dist/wallet/reset.js +60 -138
  126. package/dist/wallet/root-resolution.d.ts +1 -5
  127. package/dist/wallet/root-resolution.js +0 -18
  128. package/dist/wallet/runtime.d.ts +0 -6
  129. package/dist/wallet/runtime.js +0 -8
  130. package/dist/wallet/state/client-password-agent.js +208 -0
  131. package/dist/wallet/state/client-password.d.ts +65 -0
  132. package/dist/wallet/state/client-password.js +952 -0
  133. package/dist/wallet/state/crypto.d.ts +1 -20
  134. package/dist/wallet/state/crypto.js +0 -63
  135. package/dist/wallet/state/provider.d.ts +23 -11
  136. package/dist/wallet/state/provider.js +248 -290
  137. package/dist/wallet/state/storage.d.ts +2 -2
  138. package/dist/wallet/state/storage.js +48 -16
  139. package/dist/wallet/tx/anchor.d.ts +3 -28
  140. package/dist/wallet/tx/anchor.js +349 -1250
  141. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  142. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  143. package/dist/wallet/tx/cog.d.ts +5 -1
  144. package/dist/wallet/tx/cog.js +149 -185
  145. package/dist/wallet/tx/common.d.ts +61 -8
  146. package/dist/wallet/tx/common.js +266 -146
  147. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  148. package/dist/wallet/tx/domain-admin.js +61 -99
  149. package/dist/wallet/tx/domain-market.d.ts +5 -1
  150. package/dist/wallet/tx/domain-market.js +221 -228
  151. package/dist/wallet/tx/field.d.ts +4 -10
  152. package/dist/wallet/tx/field.js +83 -924
  153. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  154. package/dist/wallet/tx/identity-selector.js +17 -35
  155. package/dist/wallet/tx/index.d.ts +3 -1
  156. package/dist/wallet/tx/index.js +2 -1
  157. package/dist/wallet/tx/register.d.ts +3 -1
  158. package/dist/wallet/tx/register.js +62 -220
  159. package/dist/wallet/tx/reputation.d.ts +3 -1
  160. package/dist/wallet/tx/reputation.js +58 -95
  161. package/dist/wallet/types.d.ts +8 -122
  162. package/package.json +5 -5
  163. package/dist/wallet/archive.d.ts +0 -4
  164. package/dist/wallet/archive.js +0 -41
  165. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  166. package/dist/wallet/mining/hook-protocol.js +0 -161
  167. package/dist/wallet/mining/hook-runner.js +0 -52
  168. package/dist/wallet/mining/hooks.d.ts +0 -38
  169. package/dist/wallet/mining/hooks.js +0 -520
  170. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  171. package/dist/wallet/state/explicit-lock.js +0 -19
  172. package/dist/wallet/state/session.d.ts +0 -12
  173. package/dist/wallet/state/session.js +0 -23
  174. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
@@ -8,7 +8,7 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
8
8
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
9
9
  import { computeRootRegistrationPriceSats, serializeDomainReg } from "../cogop/index.js";
10
10
  import { openWalletReadContext } from "../read/index.js";
11
- import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, getDecodedInputScriptPubKeyHex, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createBuiltWalletMutationFeeSummary, createFundingMutationSender, createWalletMutationFeeMetadata, formatCogAmount, getDecodedInputScriptPubKeyHex, isLocalWalletScript, isAlreadyAcceptedError, isBroadcastUnknownError, mergeFixedWalletInputs, outpointKey, pauseMiningForWalletMutation, resolvePendingMutationReuseDecision, resolveWalletMutationFeeSelection, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
12
12
  import { confirmTypedAcknowledgement, confirmYesNo } from "./confirm.js";
13
13
  import { getCanonicalIdentitySelector, resolveIdentityBySelector, } from "./identity-selector.js";
14
14
  import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
@@ -96,9 +96,6 @@ function buildRootRegisterOutputs(options) {
96
96
  { data: Buffer.from(payload).toString("hex") },
97
97
  { [options.treasuryAddress]: satsToBtcNumber(options.priceSats) },
98
98
  ];
99
- if (options.senderAddress !== null && options.anchorValueSats !== null) {
100
- outputs.push({ [options.senderAddress]: satsToBtcNumber(options.anchorValueSats) });
101
- }
102
99
  return {
103
100
  outputs,
104
101
  changePosition: outputs.length,
@@ -108,11 +105,8 @@ function buildRootRegisterOutputs(options) {
108
105
  function buildSubdomainRegisterOutputs(options) {
109
106
  const payload = serializeDomainReg(options.domainName).opReturnData;
110
107
  return {
111
- outputs: [
112
- { data: Buffer.from(payload).toString("hex") },
113
- { [options.senderAddress]: satsToBtcNumber(options.anchorValueSats) },
114
- ],
115
- changePosition: 2,
108
+ outputs: [{ data: Buffer.from(payload).toString("hex") }],
109
+ changePosition: 1,
116
110
  expectedOpReturnScriptHex: encodeOpReturnScript(payload),
117
111
  };
118
112
  }
@@ -184,7 +178,7 @@ function describeRegisterEconomicEffect(summary) {
184
178
  if (summary.economicEffect.kind === "treasury-payment") {
185
179
  return `send ${summary.economicEffect.amount.toString()} sats to the Cogcoin treasury.`;
186
180
  }
187
- return `burn ${formatCogAmount(summary.economicEffect.amount)} from the parent-owner identity.`;
181
+ return `burn ${formatCogAmount(summary.economicEffect.amount)} from the parent owner.`;
188
182
  }
189
183
  function writeRegisterResolvedSummary(prompter, summary) {
190
184
  prompter.writeLine(`Resolved path: ${summary.path} registration.`);
@@ -194,15 +188,6 @@ function writeRegisterResolvedSummary(prompter, summary) {
194
188
  prompter.writeLine(`Resolved sender: ${summary.sender.selector} (${summary.sender.address})`);
195
189
  prompter.writeLine(`Economic effect: ${describeRegisterEconomicEffect(summary)}`);
196
190
  }
197
- function replaceAssignedDomainNames(identity, domainName) {
198
- if (identity.assignedDomainNames.includes(domainName)) {
199
- return identity;
200
- }
201
- return {
202
- ...identity,
203
- assignedDomainNames: [...identity.assignedDomainNames, domainName].sort((left, right) => left.localeCompare(right)),
204
- };
205
- }
206
191
  function reserveLocalDomainRecord(options) {
207
192
  const existing = options.state.domains.find((domain) => domain.name === options.domainName) ?? null;
208
193
  const domains = options.state.domains.some((domain) => domain.name === options.domainName)
@@ -213,7 +198,6 @@ function reserveLocalDomainRecord(options) {
213
198
  return {
214
199
  ...domain,
215
200
  currentOwnerScriptPubKeyHex: options.sender.scriptPubKeyHex,
216
- currentOwnerLocalIndex: options.sender.localIndex,
217
201
  birthTime: domain.birthTime ?? Math.floor(options.nowUnixMs / 1000),
218
202
  };
219
203
  })
@@ -222,23 +206,15 @@ function reserveLocalDomainRecord(options) {
222
206
  {
223
207
  name: options.domainName,
224
208
  domainId: null,
225
- dedicatedIndex: null,
226
209
  currentOwnerScriptPubKeyHex: options.sender.scriptPubKeyHex,
227
- currentOwnerLocalIndex: options.sender.localIndex,
228
210
  canonicalChainStatus: "unknown",
229
- localAnchorIntent: "none",
230
- currentCanonicalAnchorOutpoint: null,
231
211
  foundingMessageText: existing?.foundingMessageText ?? null,
232
212
  birthTime: Math.floor(options.nowUnixMs / 1000),
233
213
  },
234
214
  ];
235
- const identities = options.state.identities.map((identity) => identity.index === options.sender.localIndex
236
- ? replaceAssignedDomainNames(identity, options.domainName)
237
- : identity);
238
215
  return {
239
216
  ...options.state,
240
217
  domains,
241
- identities,
242
218
  };
243
219
  }
244
220
  function getMutationStatusAfterAcceptance(options) {
@@ -250,78 +226,20 @@ function getMutationStatusAfterAcceptance(options) {
250
226
  ? "confirmed"
251
227
  : "live";
252
228
  }
253
- function resolveRootRegisterAnchorOutpoint(state, senderIndex) {
254
- const anchoredDomain = state.domains.find((domain) => domain.currentOwnerLocalIndex === senderIndex
255
- && domain.canonicalChainStatus === "anchored"
256
- && domain.currentCanonicalAnchorOutpoint !== null) ?? null;
257
- if (anchoredDomain?.currentCanonicalAnchorOutpoint === undefined
258
- || anchoredDomain?.currentCanonicalAnchorOutpoint === null) {
259
- return null;
260
- }
261
- return {
262
- txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
263
- vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
264
- };
265
- }
266
229
  function resolveRegisterSender(context, domainName, fromIdentity) {
267
230
  const state = context.localState.state;
268
- const fundingIdentity = context.model.fundingIdentity;
269
- if (fundingIdentity == null || fundingIdentity.address === null) {
231
+ if (context.model.walletAddress === null) {
270
232
  throw new Error("wallet_register_funding_identity_unavailable");
271
233
  }
234
+ void fromIdentity;
272
235
  if (!domainName.includes("-")) {
273
- if (fromIdentity !== null && fromIdentity !== undefined) {
274
- const selectedIdentity = resolveIdentityBySelector(context, fromIdentity, "wallet_register");
275
- if (selectedIdentity.address === null) {
276
- throw new Error("wallet_register_sender_address_unavailable");
277
- }
278
- if (selectedIdentity.readOnly) {
279
- throw new Error("wallet_register_sender_read_only");
280
- }
281
- if (selectedIdentity.index === fundingIdentity.index) {
282
- return {
283
- registerKind: "root",
284
- parentDomainName: null,
285
- senderSelector: getCanonicalIdentitySelector(selectedIdentity),
286
- sender: {
287
- localIndex: selectedIdentity.index,
288
- scriptPubKeyHex: selectedIdentity.scriptPubKeyHex,
289
- address: selectedIdentity.address,
290
- },
291
- anchorOutpoint: null,
292
- };
293
- }
294
- const anchorOutpoint = resolveRootRegisterAnchorOutpoint(state, selectedIdentity.index);
295
- if (anchorOutpoint === null) {
296
- throw new Error("wallet_register_sender_not_root_eligible");
297
- }
298
- return {
299
- registerKind: "root",
300
- parentDomainName: null,
301
- senderSelector: getCanonicalIdentitySelector(selectedIdentity),
302
- sender: {
303
- localIndex: selectedIdentity.index,
304
- scriptPubKeyHex: selectedIdentity.scriptPubKeyHex,
305
- address: selectedIdentity.address,
306
- },
307
- anchorOutpoint,
308
- };
309
- }
310
236
  return {
311
237
  registerKind: "root",
312
238
  parentDomainName: null,
313
- senderSelector: getCanonicalIdentitySelector(fundingIdentity),
314
- sender: {
315
- localIndex: fundingIdentity.index,
316
- scriptPubKeyHex: fundingIdentity.scriptPubKeyHex,
317
- address: fundingIdentity.address,
318
- },
319
- anchorOutpoint: null,
239
+ senderSelector: context.model.walletAddress,
240
+ sender: createFundingMutationSender(state),
320
241
  };
321
242
  }
322
- if (fromIdentity !== null && fromIdentity !== undefined) {
323
- throw new Error("wallet_register_from_not_supported_for_subdomain");
324
- }
325
243
  const parent = getParent(context.snapshot.state, domainName);
326
244
  if (parent === null) {
327
245
  throw new Error("wallet_register_parent_not_found");
@@ -330,37 +248,17 @@ function resolveRegisterSender(context, domainName, fromIdentity) {
330
248
  throw new Error("wallet_register_parent_not_anchored");
331
249
  }
332
250
  const parentDomain = context.model.domains.find((domain) => domain.name === parent.parentName) ?? null;
333
- if (parentDomain?.ownerLocalIndex === null || parentDomain?.ownerLocalIndex === undefined) {
251
+ if (!isLocalWalletScript(state, parentDomain?.ownerScriptPubKeyHex)) {
334
252
  throw new Error("wallet_register_parent_not_locally_controlled");
335
253
  }
336
- if (parentDomain.readOnly) {
337
- throw new Error("wallet_register_parent_read_only");
338
- }
339
- const senderIdentity = context.model.identities.find((identity) => identity.index === parentDomain.ownerLocalIndex) ?? null;
340
- if (senderIdentity === null || senderIdentity.address === null) {
341
- throw new Error("wallet_register_sender_identity_unavailable");
342
- }
343
- if (getBalance(context.snapshot.state, senderIdentity.scriptPubKeyHex) < SUBDOMAIN_REGISTRATION_FEE_COGTOSHI) {
254
+ if (getBalance(context.snapshot.state, state.funding.scriptPubKeyHex) < SUBDOMAIN_REGISTRATION_FEE_COGTOSHI) {
344
255
  throw new Error("wallet_register_insufficient_cog_balance");
345
256
  }
346
- const localParentRecord = state.domains.find((domain) => domain.name === parent.parentName) ?? null;
347
- const anchorOutpoint = localParentRecord?.currentCanonicalAnchorOutpoint ?? null;
348
- if (anchorOutpoint === null) {
349
- throw new Error("wallet_register_anchor_outpoint_unavailable");
350
- }
351
257
  return {
352
258
  registerKind: "subdomain",
353
259
  parentDomainName: parent.parentName,
354
- senderSelector: getCanonicalIdentitySelector(senderIdentity),
355
- sender: {
356
- localIndex: senderIdentity.index,
357
- scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
358
- address: senderIdentity.address,
359
- },
360
- anchorOutpoint: {
361
- txid: anchorOutpoint.txid,
362
- vout: anchorOutpoint.vout,
363
- },
260
+ senderSelector: context.model.walletAddress,
261
+ sender: createFundingMutationSender(state),
364
262
  };
365
263
  }
366
264
  function createDraftMutation(options) {
@@ -375,6 +273,7 @@ function createDraftMutation(options) {
375
273
  lastUpdatedAtUnixMs: options.nowUnixMs,
376
274
  attemptedTxid: null,
377
275
  attemptedWtxid: null,
276
+ ...createWalletMutationFeeMetadata(options.feeSelection),
378
277
  temporaryBuilderLockedOutpoints: [],
379
278
  };
380
279
  }
@@ -392,6 +291,7 @@ function createDraftMutation(options) {
392
291
  lastUpdatedAtUnixMs: options.nowUnixMs,
393
292
  attemptedTxid: null,
394
293
  attemptedWtxid: null,
294
+ ...createWalletMutationFeeMetadata(options.feeSelection),
395
295
  temporaryBuilderLockedOutpoints: [],
396
296
  };
397
297
  }
@@ -401,17 +301,6 @@ function validateFundedDraft(decoded, funded, plan) {
401
301
  if (inputs.length === 0) {
402
302
  throw new Error("wallet_register_missing_sender_input");
403
303
  }
404
- assertFixedInputPrefixMatches(inputs, plan.fixedInputs, "wallet_register_sender_input_mismatch");
405
- if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
406
- throw new Error("wallet_register_sender_input_mismatch");
407
- }
408
- assertFundingInputsAfterFixedPrefix({
409
- decoded,
410
- fixedInputs: plan.fixedInputs,
411
- allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
412
- eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
413
- errorCode: "wallet_register_unexpected_funding_input",
414
- });
415
304
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
416
305
  throw new Error("wallet_register_opreturn_mismatch");
417
306
  }
@@ -423,17 +312,7 @@ function validateFundedDraft(decoded, funded, plan) {
423
312
  throw new Error("wallet_register_treasury_value_too_small");
424
313
  }
425
314
  }
426
- if (plan.expectedAnchorScriptHex !== null && plan.expectedAnchorOutputIndex !== null) {
427
- if (outputs[plan.expectedAnchorOutputIndex]?.scriptPubKey?.hex !== plan.expectedAnchorScriptHex) {
428
- throw new Error("wallet_register_anchor_output_mismatch");
429
- }
430
- if (valueToSats(outputs[plan.expectedAnchorOutputIndex]?.value ?? 0) !== (plan.expectedAnchorValueSats ?? 0n)) {
431
- throw new Error("wallet_register_anchor_value_mismatch");
432
- }
433
- }
434
- const expectedWithoutChange = 1
435
- + Number(plan.expectedTreasuryOutputIndex !== null)
436
- + Number(plan.expectedAnchorOutputIndex !== null);
315
+ const expectedWithoutChange = 1 + Number(plan.expectedTreasuryOutputIndex !== null);
437
316
  if (funded.changepos === -1) {
438
317
  if (outputs.length !== expectedWithoutChange) {
439
318
  throw new Error("wallet_register_unexpected_output_count");
@@ -455,87 +334,36 @@ function buildRegisterPlan(options) {
455
334
  treasuryAddress: options.treasuryAddress,
456
335
  treasuryScriptPubKeyHex: options.treasuryScriptPubKeyHex,
457
336
  priceSats: options.rootPriceSats,
458
- senderAddress: options.anchorOutpoint === null ? null : options.sender.address,
459
- anchorValueSats: options.anchorOutpoint === null ? null : options.anchorValueSats,
460
337
  });
461
- if (options.anchorOutpoint === null) {
462
- return {
463
- registerKind: "root",
464
- sender: options.sender,
465
- changeAddress: options.state.funding.address,
466
- fixedInputs: [],
467
- outputs: rootOutputs.outputs,
468
- changePosition: rootOutputs.changePosition,
469
- expectedOpReturnScriptHex: rootOutputs.expectedOpReturnScriptHex,
470
- expectedTreasuryOutputIndex: 1,
471
- expectedTreasuryScriptHex: options.treasuryScriptPubKeyHex,
472
- expectedTreasuryValueSats: options.rootPriceSats,
473
- expectedAnchorOutputIndex: null,
474
- expectedAnchorScriptHex: null,
475
- expectedAnchorValueSats: null,
476
- allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
477
- eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey(entry))),
478
- };
479
- }
480
- const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint?.txid
481
- && entry.vout === options.anchorOutpoint.vout
482
- && entry.scriptPubKey === options.sender.scriptPubKeyHex
483
- && isSpendableConfirmedUtxo(entry));
484
- if (anchorUtxo === undefined) {
485
- throw new Error("wallet_register_anchor_utxo_missing");
486
- }
487
338
  return {
488
339
  registerKind: "root",
489
340
  sender: options.sender,
490
341
  changeAddress: options.state.funding.address,
491
- fixedInputs: [
492
- { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
493
- ],
342
+ fixedInputs: [],
494
343
  outputs: rootOutputs.outputs,
495
344
  changePosition: rootOutputs.changePosition,
496
345
  expectedOpReturnScriptHex: rootOutputs.expectedOpReturnScriptHex,
497
346
  expectedTreasuryOutputIndex: 1,
498
347
  expectedTreasuryScriptHex: options.treasuryScriptPubKeyHex,
499
348
  expectedTreasuryValueSats: options.rootPriceSats,
500
- expectedAnchorOutputIndex: 2,
501
- expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
502
- expectedAnchorValueSats: options.anchorValueSats,
503
349
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
504
350
  eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey(entry))),
505
351
  };
506
352
  }
507
- const anchor = options.anchorOutpoint;
508
- if (anchor === null) {
509
- throw new Error("wallet_register_anchor_outpoint_unavailable");
510
- }
511
- const anchorUtxo = options.allUtxos.find((entry) => entry.txid === anchor.txid
512
- && entry.vout === anchor.vout
513
- && entry.scriptPubKey === options.sender.scriptPubKeyHex
514
- && isSpendableConfirmedUtxo(entry));
515
- if (anchorUtxo === undefined) {
516
- throw new Error("wallet_register_anchor_utxo_missing");
517
- }
518
353
  const subdomainOutputs = buildSubdomainRegisterOutputs({
519
354
  domainName: options.domainName,
520
- senderAddress: options.sender.address,
521
- anchorValueSats: options.anchorValueSats,
522
355
  });
523
356
  return {
524
357
  registerKind: "subdomain",
525
358
  sender: options.sender,
526
359
  changeAddress: options.state.funding.address,
527
- fixedInputs: [
528
- { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
529
- ],
360
+ fixedInputs: [],
530
361
  outputs: subdomainOutputs.outputs,
531
362
  changePosition: subdomainOutputs.changePosition,
532
363
  expectedOpReturnScriptHex: subdomainOutputs.expectedOpReturnScriptHex,
533
364
  expectedTreasuryOutputIndex: null,
534
365
  expectedTreasuryScriptHex: null,
535
366
  expectedTreasuryValueSats: null,
536
- expectedAnchorOutputIndex: 1,
537
- expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
538
- expectedAnchorValueSats: options.anchorValueSats,
539
367
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
540
368
  eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey(entry))),
541
369
  };
@@ -549,7 +377,7 @@ async function buildRegisterTransaction(options) {
549
377
  validateFundedDraft,
550
378
  finalizeErrorCode: "wallet_register_finalize_failed",
551
379
  mempoolRejectPrefix: "wallet_register_mempool_rejected",
552
- reserveCandidates: options.state.proactiveReserveOutpoints,
380
+ feeRate: options.feeRateSatVb,
553
381
  });
554
382
  }
555
383
  async function reconcilePendingRegisterMutation(options) {
@@ -573,7 +401,6 @@ async function reconcilePendingRegisterMutation(options) {
573
401
  await saveWalletStatePreservingUnlock({
574
402
  state: nextState,
575
403
  provider: options.provider,
576
- unlockUntilUnixMs: options.unlockUntilUnixMs,
577
404
  nowUnixMs: options.nowUnixMs,
578
405
  paths: options.paths,
579
406
  });
@@ -596,7 +423,6 @@ async function reconcilePendingRegisterMutation(options) {
596
423
  await saveWalletStatePreservingUnlock({
597
424
  state: nextState,
598
425
  provider: options.provider,
599
- unlockUntilUnixMs: options.unlockUntilUnixMs,
600
426
  nowUnixMs: options.nowUnixMs,
601
427
  paths: options.paths,
602
428
  });
@@ -622,7 +448,6 @@ async function reconcilePendingRegisterMutation(options) {
622
448
  await saveWalletStatePreservingUnlock({
623
449
  state: nextState,
624
450
  provider: options.provider,
625
- unlockUntilUnixMs: options.unlockUntilUnixMs,
626
451
  nowUnixMs: options.nowUnixMs,
627
452
  paths: options.paths,
628
453
  });
@@ -645,7 +470,6 @@ async function reconcilePendingRegisterMutation(options) {
645
470
  await saveWalletStatePreservingUnlock({
646
471
  state: nextState,
647
472
  provider: options.provider,
648
- unlockUntilUnixMs: options.unlockUntilUnixMs,
649
473
  nowUnixMs: options.nowUnixMs,
650
474
  paths: options.paths,
651
475
  });
@@ -688,7 +512,6 @@ export async function registerDomain(options) {
688
512
  try {
689
513
  assertWalletMutationContextReady(readContext, "wallet_register");
690
514
  const state = readContext.localState.state;
691
- const unlockUntilUnixMs = readContext.localState.unlockUntilUnixMs;
692
515
  const senderResolution = resolveRegisterSender(readContext, normalizedDomainName, options.fromIdentity);
693
516
  const intentFingerprintHex = createRegisterIntentFingerprint({
694
517
  walletRootId: state.walletRootId,
@@ -713,13 +536,18 @@ export async function registerDomain(options) {
713
536
  });
714
537
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
715
538
  const walletName = state.managedCoreWallet.walletName;
539
+ const feeSelection = await resolveWalletMutationFeeSelection({
540
+ rpc,
541
+ feeRateSatVb: options.feeRateSatVb ?? null,
542
+ });
716
543
  const existingMutation = findPendingMutationByIntent(state, intentFingerprintHex);
544
+ let workingState = state;
545
+ let replacementFixedInputs = null;
717
546
  if (existingMutation !== null) {
718
547
  const reconciled = await reconcilePendingRegisterMutation({
719
548
  state,
720
549
  mutation: existingMutation,
721
550
  provider,
722
- unlockUntilUnixMs,
723
551
  nowUnixMs,
724
552
  paths,
725
553
  rpc,
@@ -727,22 +555,33 @@ export async function registerDomain(options) {
727
555
  context: readContext,
728
556
  sender: senderResolution.sender,
729
557
  });
558
+ workingState = reconciled.state;
730
559
  if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
731
- return {
732
- domainName: normalizedDomainName,
733
- registerKind: senderResolution.registerKind,
734
- parentDomainName: senderResolution.parentDomainName,
735
- senderSelector: senderResolution.senderSelector,
736
- senderLocalIndex: senderResolution.sender.localIndex,
737
- senderScriptPubKeyHex: senderResolution.sender.scriptPubKeyHex,
738
- senderAddress: senderResolution.sender.address,
739
- economicEffectKind: senderResolution.registerKind === "root" ? "treasury-payment" : "cog-burn",
740
- economicEffectAmount: senderResolution.registerKind === "root" ? rootPriceSats : SUBDOMAIN_REGISTRATION_FEE_COGTOSHI,
741
- resolved: resolvedSummary,
742
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
743
- status: reconciled.resolution,
744
- reusedExisting: true,
745
- };
560
+ const reuse = await resolvePendingMutationReuseDecision({
561
+ rpc,
562
+ walletName,
563
+ mutation: reconciled.mutation,
564
+ nextFeeSelection: feeSelection,
565
+ });
566
+ if (reuse.reuseExisting) {
567
+ return {
568
+ domainName: normalizedDomainName,
569
+ registerKind: senderResolution.registerKind,
570
+ parentDomainName: senderResolution.parentDomainName,
571
+ senderSelector: senderResolution.senderSelector,
572
+ senderLocalIndex: senderResolution.sender.localIndex,
573
+ senderScriptPubKeyHex: senderResolution.sender.scriptPubKeyHex,
574
+ senderAddress: senderResolution.sender.address,
575
+ economicEffectKind: senderResolution.registerKind === "root" ? "treasury-payment" : "cog-burn",
576
+ economicEffectAmount: senderResolution.registerKind === "root" ? rootPriceSats : SUBDOMAIN_REGISTRATION_FEE_COGTOSHI,
577
+ resolved: resolvedSummary,
578
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
579
+ status: reconciled.resolution,
580
+ reusedExisting: true,
581
+ fees: reuse.fees,
582
+ };
583
+ }
584
+ replacementFixedInputs = reuse.replacementFixedInputs;
746
585
  }
747
586
  if (reconciled.resolution === "repair-required") {
748
587
  throw new Error("wallet_register_repair_required");
@@ -767,13 +606,14 @@ export async function registerDomain(options) {
767
606
  else {
768
607
  await confirmSubdomainRegistration(options.prompter, normalizedDomainName, resolvedSummary, options.assumeYes);
769
608
  }
770
- let nextState = upsertPendingMutation(state, createDraftMutation({
609
+ let nextState = upsertPendingMutation(workingState, createDraftMutation({
771
610
  domainName: normalizedDomainName,
772
611
  parentDomainName: senderResolution.parentDomainName,
773
612
  sender: senderResolution.sender,
774
613
  registerKind: senderResolution.registerKind,
775
614
  intentFingerprintHex,
776
615
  nowUnixMs,
616
+ feeSelection,
777
617
  existing: existingMutation,
778
618
  }));
779
619
  nextState = {
@@ -784,7 +624,6 @@ export async function registerDomain(options) {
784
624
  await saveWalletStatePreservingUnlock({
785
625
  state: nextState,
786
626
  provider,
787
- unlockUntilUnixMs,
788
627
  nowUnixMs,
789
628
  paths,
790
629
  });
@@ -794,20 +633,22 @@ export async function registerDomain(options) {
794
633
  state: nextState,
795
634
  allUtxos,
796
635
  sender: senderResolution.sender,
797
- anchorOutpoint: senderResolution.anchorOutpoint,
798
636
  registerKind: senderResolution.registerKind,
799
637
  domainName: normalizedDomainName,
800
638
  parentDomainName: senderResolution.parentDomainName,
801
639
  treasuryAddress: genesis.treasuryAddress,
802
640
  treasuryScriptPubKeyHex: Buffer.from(genesis.treasuryScriptPubKey).toString("hex"),
803
- anchorValueSats: BigInt(nextState.anchorValueSats),
804
641
  rootPriceSats,
805
642
  });
806
643
  const built = await buildRegisterTransaction({
807
644
  rpc,
808
645
  walletName,
809
646
  state: nextState,
810
- plan,
647
+ plan: {
648
+ ...plan,
649
+ fixedInputs: mergeFixedWalletInputs(plan.fixedInputs, replacementFixedInputs),
650
+ },
651
+ feeRateSatVb: feeSelection.feeRateSatVb,
811
652
  });
812
653
  const currentMutation = nextState.pendingMutations?.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex)
813
654
  ?? createDraftMutation({
@@ -817,6 +658,7 @@ export async function registerDomain(options) {
817
658
  registerKind: senderResolution.registerKind,
818
659
  intentFingerprintHex,
819
660
  nowUnixMs,
661
+ feeSelection,
820
662
  });
821
663
  const broadcastingMutation = updateMutationRecord(currentMutation, "broadcasting", nowUnixMs, {
822
664
  attemptedTxid: built.txid,
@@ -831,7 +673,6 @@ export async function registerDomain(options) {
831
673
  await saveWalletStatePreservingUnlock({
832
674
  state: nextState,
833
675
  provider,
834
- unlockUntilUnixMs,
835
676
  nowUnixMs,
836
677
  paths,
837
678
  });
@@ -863,7 +704,6 @@ export async function registerDomain(options) {
863
704
  await saveWalletStatePreservingUnlock({
864
705
  state: nextState,
865
706
  provider,
866
- unlockUntilUnixMs,
867
707
  nowUnixMs,
868
708
  paths,
869
709
  });
@@ -884,7 +724,6 @@ export async function registerDomain(options) {
884
724
  await saveWalletStatePreservingUnlock({
885
725
  state: nextState,
886
726
  provider,
887
- unlockUntilUnixMs,
888
727
  nowUnixMs,
889
728
  paths,
890
729
  });
@@ -919,7 +758,6 @@ export async function registerDomain(options) {
919
758
  await saveWalletStatePreservingUnlock({
920
759
  state: nextState,
921
760
  provider,
922
- unlockUntilUnixMs,
923
761
  nowUnixMs,
924
762
  paths,
925
763
  });
@@ -937,6 +775,10 @@ export async function registerDomain(options) {
937
775
  txid: built.txid,
938
776
  status: finalStatus,
939
777
  reusedExisting: false,
778
+ fees: createBuiltWalletMutationFeeSummary({
779
+ selection: feeSelection,
780
+ built,
781
+ }),
940
782
  };
941
783
  }
942
784
  finally {
@@ -5,7 +5,7 @@ import type { WalletPrompter } from "../lifecycle.js";
5
5
  import { type WalletRuntimePaths } from "../runtime.js";
6
6
  import { type WalletSecretProvider } from "../state/provider.js";
7
7
  import { openWalletReadContext } from "../read/index.js";
8
- import { type WalletMutationRpcClient } from "./common.js";
8
+ import { type WalletMutationFeeSummary, type WalletMutationRpcClient } from "./common.js";
9
9
  interface ReputationRpcClient extends WalletMutationRpcClient {
10
10
  getBlockchainInfo(): Promise<{
11
11
  blocks: number;
@@ -23,6 +23,7 @@ export interface ReputationMutationResult {
23
23
  reusedExisting: boolean;
24
24
  reviewIncluded: boolean;
25
25
  resolved?: ReputationResolvedSummary | null;
26
+ fees: WalletMutationFeeSummary;
26
27
  }
27
28
  export interface ReputationResolvedSenderSummary {
28
29
  selector: string;
@@ -52,6 +53,7 @@ interface ReputationBaseOptions {
52
53
  targetDomainName: string;
53
54
  amountCogtoshi: bigint;
54
55
  reviewText?: string | null;
56
+ feeRateSatVb?: number | null;
55
57
  dataDir: string;
56
58
  databasePath: string;
57
59
  provider?: WalletSecretProvider;