@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,7 +7,7 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
7
7
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
8
8
  import { serializeCogClaim, serializeCogLock, serializeCogTransfer, } from "../cogop/index.js";
9
9
  import { openWalletReadContext } from "../read/index.js";
10
- import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
10
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createBuiltWalletMutationFeeSummary, createFundingMutationSender, createWalletMutationFeeMetadata, formatCogAmount, getDecodedInputScriptPubKeyHex, isLocalWalletScript, isAlreadyAcceptedError, isBroadcastUnknownError, mergeFixedWalletInputs, outpointKey, pauseMiningForWalletMutation, resolvePendingMutationReuseDecision, resolveWalletMutationFeeSelection, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
11
  import { confirmTypedAcknowledgement, confirmYesNo } from "./confirm.js";
12
12
  import { getCanonicalIdentitySelector, resolveIdentityBySelector, } from "./identity-selector.js";
13
13
  import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
@@ -68,21 +68,6 @@ function parseHex32(value, errorCode) {
68
68
  function sha256Hex(value) {
69
69
  return createHash("sha256").update(value).digest("hex");
70
70
  }
71
- function resolveAnchorOutpointForSender(state, sender, errorPrefix) {
72
- const anchoredDomains = state.domains.filter((domain) => domain.currentOwnerLocalIndex === sender.index
73
- && domain.canonicalChainStatus === "anchored");
74
- if (anchoredDomains.length === 0) {
75
- return null;
76
- }
77
- const anchoredDomain = anchoredDomains[0];
78
- if (anchoredDomain.currentCanonicalAnchorOutpoint === null) {
79
- throw new Error(`${errorPrefix}_anchor_outpoint_unavailable`);
80
- }
81
- return {
82
- txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
83
- vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
84
- };
85
- }
86
71
  function ensureUsableSender(sender, errorPrefix, amountCogtoshi) {
87
72
  if (sender.address === null) {
88
73
  throw new Error(`${errorPrefix}_sender_address_unavailable`);
@@ -104,31 +89,11 @@ function createResolvedSenderSummary(identity) {
104
89
  }
105
90
  function resolveIdentitySender(context, errorPrefix, amountCogtoshi, selector) {
106
91
  assertWalletMutationContextReady(context, errorPrefix);
107
- const identity = selector == null
108
- ? (() => {
109
- const eligible = context.model.identities.filter((candidate) => candidate.address !== null
110
- && !candidate.readOnly
111
- && candidate.observedCogBalance !== null
112
- && candidate.observedCogBalance >= amountCogtoshi);
113
- if (eligible.length === 0) {
114
- throw new Error(`${errorPrefix}_no_eligible_sender`);
115
- }
116
- if (eligible.length > 1) {
117
- throw new Error(`${errorPrefix}_ambiguous_sender`);
118
- }
119
- return eligible[0];
120
- })()
121
- : resolveIdentityBySelector(context, selector, errorPrefix);
92
+ const identity = resolveIdentityBySelector(context, selector ?? context.model.walletAddress ?? "", errorPrefix);
122
93
  ensureUsableSender(identity, errorPrefix, amountCogtoshi);
123
94
  return {
124
95
  state: context.localState.state,
125
- unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
126
- sender: {
127
- localIndex: identity.index,
128
- scriptPubKeyHex: identity.scriptPubKeyHex,
129
- address: identity.address,
130
- },
131
- anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, identity, errorPrefix),
96
+ sender: createFundingMutationSender(context.localState.state),
132
97
  resolved: {
133
98
  sender: createResolvedSenderSummary(identity),
134
99
  claimPath: null,
@@ -155,20 +120,14 @@ function resolveClaimSender(context, lockId, preimageHex, reclaim) {
155
120
  throw new Error("wallet_reclaim_before_timeout");
156
121
  }
157
122
  const lockerHex = Buffer.from(lock.lockerScriptPubKey).toString("hex");
158
- const senderIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === lockerHex) ?? null;
159
- if (senderIdentity === null) {
123
+ if (lockerHex !== context.localState.state.funding.scriptPubKeyHex || context.model.walletAddress == null) {
160
124
  throw new Error("wallet_reclaim_sender_not_local");
161
125
  }
126
+ const senderIdentity = resolveIdentityBySelector(context, context.model.walletAddress, errorPrefix);
162
127
  ensureUsableSender(senderIdentity, errorPrefix, 0n);
163
128
  return {
164
129
  state: context.localState.state,
165
- unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
166
- sender: {
167
- localIndex: senderIdentity.index,
168
- scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
169
- address: senderIdentity.address,
170
- },
171
- anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, senderIdentity, errorPrefix),
130
+ sender: createFundingMutationSender(context.localState.state),
172
131
  recipientDomainName,
173
132
  amountCogtoshi: lock.amount,
174
133
  lockId: lock.lockId,
@@ -189,20 +148,14 @@ function resolveClaimSender(context, lockId, preimageHex, reclaim) {
189
148
  throw new Error("wallet_claim_recipient_domain_missing");
190
149
  }
191
150
  const recipientOwnerHex = Buffer.from(recipientDomain.ownerScriptPubKey).toString("hex");
192
- const senderIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === recipientOwnerHex) ?? null;
193
- if (senderIdentity === null) {
151
+ if (recipientOwnerHex !== context.localState.state.funding.scriptPubKeyHex || context.model.walletAddress == null) {
194
152
  throw new Error("wallet_claim_sender_not_local");
195
153
  }
154
+ const senderIdentity = resolveIdentityBySelector(context, context.model.walletAddress, errorPrefix);
196
155
  ensureUsableSender(senderIdentity, errorPrefix, 0n);
197
156
  return {
198
157
  state: context.localState.state,
199
- unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
200
- sender: {
201
- localIndex: senderIdentity.index,
202
- scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
203
- address: senderIdentity.address,
204
- },
205
- anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, senderIdentity, errorPrefix),
158
+ sender: createFundingMutationSender(context.localState.state),
206
159
  recipientDomainName,
207
160
  amountCogtoshi: lock.amount,
208
161
  lockId: lock.lockId,
@@ -246,45 +199,13 @@ function buildPlanForCogOperation(options) {
246
199
  && entry.confirmations >= 1
247
200
  && entry.spendable !== false
248
201
  && entry.safe !== false);
249
- const outputs = [{ data: Buffer.from(options.opReturnData).toString("hex") }];
250
- if (options.anchorOutpoint === null) {
251
- return {
252
- sender: options.sender,
253
- changeAddress: options.state.funding.address,
254
- fixedInputs: [],
255
- outputs,
256
- changePosition: 1,
257
- expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
258
- expectedAnchorScriptHex: null,
259
- expectedAnchorValueSats: null,
260
- allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
261
- eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
262
- errorPrefix: options.errorPrefix,
263
- };
264
- }
265
- const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint?.txid
266
- && entry.vout === options.anchorOutpoint.vout
267
- && entry.scriptPubKey === options.sender.scriptPubKeyHex
268
- && entry.confirmations >= 1
269
- && entry.spendable !== false
270
- && entry.safe !== false);
271
- if (anchorUtxo === undefined) {
272
- throw new Error(`${options.errorPrefix}_anchor_utxo_missing`);
273
- }
274
- outputs.push({
275
- [options.sender.address]: satsToBtcNumber(options.anchorValueSats),
276
- });
277
202
  return {
278
203
  sender: options.sender,
279
204
  changeAddress: options.state.funding.address,
280
- fixedInputs: [
281
- { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
282
- ],
283
- outputs,
284
- changePosition: 2,
205
+ fixedInputs: [],
206
+ outputs: [{ data: Buffer.from(options.opReturnData).toString("hex") }],
207
+ changePosition: 1,
285
208
  expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
286
- expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
287
- expectedAnchorValueSats: options.anchorValueSats,
288
209
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
289
210
  eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
290
211
  errorPrefix: options.errorPrefix,
@@ -296,29 +217,10 @@ function validateFundedDraft(decoded, funded, plan) {
296
217
  if (inputs.length === 0) {
297
218
  throw new Error(`${plan.errorPrefix}_missing_sender_input`);
298
219
  }
299
- assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
300
- if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
301
- throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
302
- }
303
- assertFundingInputsAfterFixedPrefix({
304
- inputs,
305
- fixedInputs: plan.fixedInputs,
306
- allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
307
- eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
308
- errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
309
- });
310
220
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
311
221
  throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
312
222
  }
313
- if (plan.expectedAnchorScriptHex !== null) {
314
- if (outputs[1]?.scriptPubKey?.hex !== plan.expectedAnchorScriptHex) {
315
- throw new Error(`${plan.errorPrefix}_anchor_output_mismatch`);
316
- }
317
- if (valueToSats(outputs[1]?.value ?? 0) !== (plan.expectedAnchorValueSats ?? 0n)) {
318
- throw new Error(`${plan.errorPrefix}_anchor_value_mismatch`);
319
- }
320
- }
321
- const expectedWithoutChange = plan.expectedAnchorScriptHex === null ? 1 : 2;
223
+ const expectedWithoutChange = 1;
322
224
  if (funded.changepos === -1) {
323
225
  if (outputs.length !== expectedWithoutChange) {
324
226
  throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
@@ -341,7 +243,7 @@ async function buildTransaction(options) {
341
243
  validateFundedDraft,
342
244
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
343
245
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
344
- reserveCandidates: options.state.proactiveReserveOutpoints,
246
+ feeRate: options.feeRateSatVb,
345
247
  });
346
248
  }
347
249
  function createDraftMutation(options) {
@@ -363,6 +265,7 @@ function createDraftMutation(options) {
363
265
  lastUpdatedAtUnixMs: options.nowUnixMs,
364
266
  attemptedTxid: null,
365
267
  attemptedWtxid: null,
268
+ ...createWalletMutationFeeMetadata(options.feeSelection),
366
269
  temporaryBuilderLockedOutpoints: [],
367
270
  };
368
271
  }
@@ -386,6 +289,7 @@ function createDraftMutation(options) {
386
289
  lastUpdatedAtUnixMs: options.nowUnixMs,
387
290
  attemptedTxid: null,
388
291
  attemptedWtxid: null,
292
+ ...createWalletMutationFeeMetadata(options.feeSelection),
389
293
  temporaryBuilderLockedOutpoints: [],
390
294
  };
391
295
  }
@@ -422,7 +326,6 @@ async function reconcilePendingCogMutation(options) {
422
326
  await saveWalletStatePreservingUnlock({
423
327
  state: nextState,
424
328
  provider: options.provider,
425
- unlockUntilUnixMs: options.unlockUntilUnixMs,
426
329
  nowUnixMs: options.nowUnixMs,
427
330
  paths: options.paths,
428
331
  });
@@ -445,7 +348,6 @@ async function reconcilePendingCogMutation(options) {
445
348
  await saveWalletStatePreservingUnlock({
446
349
  state: nextState,
447
350
  provider: options.provider,
448
- unlockUntilUnixMs: options.unlockUntilUnixMs,
449
351
  nowUnixMs: options.nowUnixMs,
450
352
  paths: options.paths,
451
353
  });
@@ -524,7 +426,6 @@ async function sendBuiltTransaction(options) {
524
426
  await saveWalletStatePreservingUnlock({
525
427
  state: nextState,
526
428
  provider: options.provider,
527
- unlockUntilUnixMs: options.unlockUntilUnixMs,
528
429
  nowUnixMs: options.nowUnixMs,
529
430
  paths: options.paths,
530
431
  });
@@ -551,7 +452,6 @@ async function sendBuiltTransaction(options) {
551
452
  await saveWalletStatePreservingUnlock({
552
453
  state: nextState,
553
454
  provider: options.provider,
554
- unlockUntilUnixMs: options.unlockUntilUnixMs,
555
455
  nowUnixMs: options.nowUnixMs,
556
456
  paths: options.paths,
557
457
  });
@@ -571,7 +471,6 @@ async function sendBuiltTransaction(options) {
571
471
  await saveWalletStatePreservingUnlock({
572
472
  state: nextState,
573
473
  provider: options.provider,
574
- unlockUntilUnixMs: options.unlockUntilUnixMs,
575
474
  nowUnixMs: options.nowUnixMs,
576
475
  paths: options.paths,
577
476
  });
@@ -592,7 +491,6 @@ async function sendBuiltTransaction(options) {
592
491
  await saveWalletStatePreservingUnlock({
593
492
  state: nextState,
594
493
  provider: options.provider,
595
- unlockUntilUnixMs: options.unlockUntilUnixMs,
596
494
  nowUnixMs: options.nowUnixMs,
597
495
  paths: options.paths,
598
496
  });
@@ -640,42 +538,59 @@ export async function sendCog(options) {
640
538
  });
641
539
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
642
540
  const walletName = operation.state.managedCoreWallet.walletName;
541
+ const feeSelection = await resolveWalletMutationFeeSelection({
542
+ rpc,
543
+ feeRateSatVb: options.feeRateSatVb ?? null,
544
+ });
643
545
  const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
546
+ let workingState = operation.state;
547
+ let replacementFixedInputs = null;
644
548
  if (existingMutation !== null) {
645
549
  const reconciled = await reconcilePendingCogMutation({
646
550
  state: operation.state,
647
551
  mutation: existingMutation,
648
552
  provider,
649
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
650
553
  nowUnixMs,
651
554
  paths,
652
555
  rpc,
653
556
  walletName,
654
557
  context: readContext,
655
558
  });
559
+ workingState = reconciled.state;
656
560
  if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
657
- return {
658
- kind: "send",
659
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
660
- status: reconciled.resolution,
661
- reusedExisting: true,
662
- amountCogtoshi,
663
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
664
- resolved: operation.resolved,
665
- };
561
+ const reuse = await resolvePendingMutationReuseDecision({
562
+ rpc,
563
+ walletName,
564
+ mutation: reconciled.mutation,
565
+ nextFeeSelection: feeSelection,
566
+ });
567
+ if (reuse.reuseExisting) {
568
+ return {
569
+ kind: "send",
570
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
571
+ status: reconciled.resolution,
572
+ reusedExisting: true,
573
+ amountCogtoshi,
574
+ recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
575
+ resolved: operation.resolved,
576
+ fees: reuse.fees,
577
+ };
578
+ }
579
+ replacementFixedInputs = reuse.replacementFixedInputs;
666
580
  }
667
581
  if (reconciled.resolution === "repair-required") {
668
582
  throw new Error("wallet_send_repair_required");
669
583
  }
670
584
  }
671
585
  await confirmSend(options.prompter, operation.resolved, options.target, recipient, amountCogtoshi, options.assumeYes);
672
- let nextState = upsertPendingMutation(operation.state, createDraftMutation({
586
+ let nextState = upsertPendingMutation(workingState, createDraftMutation({
673
587
  kind: "send",
674
588
  sender: operation.sender,
675
589
  recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
676
590
  amountCogtoshi,
677
591
  intentFingerprintHex,
678
592
  nowUnixMs,
593
+ feeSelection,
679
594
  existing: existingMutation,
680
595
  }));
681
596
  nextState = {
@@ -686,23 +601,25 @@ export async function sendCog(options) {
686
601
  await saveWalletStatePreservingUnlock({
687
602
  state: nextState,
688
603
  provider,
689
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
690
604
  nowUnixMs,
691
605
  paths,
692
606
  });
607
+ const sendPlan = buildPlanForCogOperation({
608
+ state: nextState,
609
+ allUtxos: await rpc.listUnspent(walletName, 1),
610
+ sender: operation.sender,
611
+ opReturnData: serializeCogTransfer(amountCogtoshi, Buffer.from(recipient.scriptPubKeyHex, "hex")).opReturnData,
612
+ errorPrefix: "wallet_send",
613
+ });
693
614
  const built = await buildTransaction({
694
615
  rpc,
695
616
  walletName,
696
617
  state: nextState,
697
- plan: buildPlanForCogOperation({
698
- state: nextState,
699
- allUtxos: await rpc.listUnspent(walletName, 1),
700
- sender: operation.sender,
701
- anchorOutpoint: operation.anchorOutpoint,
702
- opReturnData: serializeCogTransfer(amountCogtoshi, Buffer.from(recipient.scriptPubKeyHex, "hex")).opReturnData,
703
- anchorValueSats: BigInt(nextState.anchorValueSats),
704
- errorPrefix: "wallet_send",
705
- }),
618
+ plan: {
619
+ ...sendPlan,
620
+ fixedInputs: mergeFixedWalletInputs(sendPlan.fixedInputs, replacementFixedInputs),
621
+ },
622
+ feeRateSatVb: feeSelection.feeRateSatVb,
706
623
  });
707
624
  const final = await sendBuiltTransaction({
708
625
  rpc,
@@ -712,7 +629,6 @@ export async function sendCog(options) {
712
629
  mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
713
630
  state: nextState,
714
631
  provider,
715
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
716
632
  nowUnixMs,
717
633
  paths,
718
634
  errorPrefix: "wallet_send",
@@ -725,6 +641,10 @@ export async function sendCog(options) {
725
641
  amountCogtoshi,
726
642
  recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
727
643
  resolved: operation.resolved,
644
+ fees: createBuiltWalletMutationFeeSummary({
645
+ selection: feeSelection,
646
+ built,
647
+ }),
728
648
  };
729
649
  }
730
650
  finally {
@@ -800,36 +720,52 @@ export async function lockCogToDomain(options) {
800
720
  });
801
721
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
802
722
  const walletName = operation.state.managedCoreWallet.walletName;
723
+ const feeSelection = await resolveWalletMutationFeeSelection({
724
+ rpc,
725
+ feeRateSatVb: options.feeRateSatVb ?? null,
726
+ });
803
727
  const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
728
+ let workingState = operation.state;
729
+ let replacementFixedInputs = null;
804
730
  if (existingMutation !== null) {
805
731
  const reconciled = await reconcilePendingCogMutation({
806
732
  state: operation.state,
807
733
  mutation: existingMutation,
808
734
  provider,
809
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
810
735
  nowUnixMs,
811
736
  paths,
812
737
  rpc,
813
738
  walletName,
814
739
  context: readContext,
815
740
  });
741
+ workingState = reconciled.state;
816
742
  if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
817
- return {
818
- kind: "lock",
819
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
820
- status: reconciled.resolution,
821
- reusedExisting: true,
822
- amountCogtoshi,
823
- recipientDomainName: normalizedRecipientDomainName,
824
- resolved: operation.resolved,
825
- };
743
+ const reuse = await resolvePendingMutationReuseDecision({
744
+ rpc,
745
+ walletName,
746
+ mutation: reconciled.mutation,
747
+ nextFeeSelection: feeSelection,
748
+ });
749
+ if (reuse.reuseExisting) {
750
+ return {
751
+ kind: "lock",
752
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
753
+ status: reconciled.resolution,
754
+ reusedExisting: true,
755
+ amountCogtoshi,
756
+ recipientDomainName: normalizedRecipientDomainName,
757
+ resolved: operation.resolved,
758
+ fees: reuse.fees,
759
+ };
760
+ }
761
+ replacementFixedInputs = reuse.replacementFixedInputs;
826
762
  }
827
763
  if (reconciled.resolution === "repair-required") {
828
764
  throw new Error("wallet_lock_repair_required");
829
765
  }
830
766
  }
831
767
  await confirmLock(options.prompter, operation.resolved, amountCogtoshi, normalizedRecipientDomainName, timeoutHeight, options.assumeYes);
832
- let nextState = upsertPendingMutation(operation.state, createDraftMutation({
768
+ let nextState = upsertPendingMutation(workingState, createDraftMutation({
833
769
  kind: "lock",
834
770
  sender: operation.sender,
835
771
  amountCogtoshi,
@@ -838,6 +774,7 @@ export async function lockCogToDomain(options) {
838
774
  conditionHex: Buffer.from(condition).toString("hex"),
839
775
  intentFingerprintHex,
840
776
  nowUnixMs,
777
+ feeSelection,
841
778
  existing: existingMutation,
842
779
  }));
843
780
  nextState = {
@@ -848,23 +785,25 @@ export async function lockCogToDomain(options) {
848
785
  await saveWalletStatePreservingUnlock({
849
786
  state: nextState,
850
787
  provider,
851
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
852
788
  nowUnixMs,
853
789
  paths,
854
790
  });
791
+ const lockPlan = buildPlanForCogOperation({
792
+ state: nextState,
793
+ allUtxos: await rpc.listUnspent(walletName, 1),
794
+ sender: operation.sender,
795
+ opReturnData: serializeCogLock(amountCogtoshi, timeoutHeight, recipientDomain.domainId, condition).opReturnData,
796
+ errorPrefix: "wallet_lock",
797
+ });
855
798
  const built = await buildTransaction({
856
799
  rpc,
857
800
  walletName,
858
801
  state: nextState,
859
- plan: buildPlanForCogOperation({
860
- state: nextState,
861
- allUtxos: await rpc.listUnspent(walletName, 1),
862
- sender: operation.sender,
863
- anchorOutpoint: operation.anchorOutpoint,
864
- opReturnData: serializeCogLock(amountCogtoshi, timeoutHeight, recipientDomain.domainId, condition).opReturnData,
865
- anchorValueSats: BigInt(nextState.anchorValueSats),
866
- errorPrefix: "wallet_lock",
867
- }),
802
+ plan: {
803
+ ...lockPlan,
804
+ fixedInputs: mergeFixedWalletInputs(lockPlan.fixedInputs, replacementFixedInputs),
805
+ },
806
+ feeRateSatVb: feeSelection.feeRateSatVb,
868
807
  });
869
808
  const final = await sendBuiltTransaction({
870
809
  rpc,
@@ -874,7 +813,6 @@ export async function lockCogToDomain(options) {
874
813
  mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
875
814
  state: nextState,
876
815
  provider,
877
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
878
816
  nowUnixMs,
879
817
  paths,
880
818
  errorPrefix: "wallet_lock",
@@ -887,6 +825,10 @@ export async function lockCogToDomain(options) {
887
825
  amountCogtoshi,
888
826
  recipientDomainName: normalizedRecipientDomainName,
889
827
  resolved: operation.resolved,
828
+ fees: createBuiltWalletMutationFeeSummary({
829
+ selection: feeSelection,
830
+ built,
831
+ }),
890
832
  };
891
833
  }
892
834
  finally {
@@ -937,30 +879,46 @@ async function runClaimLikeMutation(options, reclaim) {
937
879
  });
938
880
  const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
939
881
  const walletName = operation.state.managedCoreWallet.walletName;
882
+ const feeSelection = await resolveWalletMutationFeeSelection({
883
+ rpc,
884
+ feeRateSatVb: options.feeRateSatVb ?? null,
885
+ });
940
886
  const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
887
+ let workingState = operation.state;
888
+ let replacementFixedInputs = null;
941
889
  if (existingMutation !== null) {
942
890
  const reconciled = await reconcilePendingCogMutation({
943
891
  state: operation.state,
944
892
  mutation: existingMutation,
945
893
  provider,
946
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
947
894
  nowUnixMs,
948
895
  paths,
949
896
  rpc,
950
897
  walletName,
951
898
  context: readContext,
952
899
  });
900
+ workingState = reconciled.state;
953
901
  if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
954
- return {
955
- kind: "claim",
956
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
957
- status: reconciled.resolution,
958
- reusedExisting: true,
959
- amountCogtoshi: operation.amountCogtoshi,
960
- recipientDomainName: operation.recipientDomainName,
961
- lockId: options.lockId,
962
- resolved: operation.resolved,
963
- };
902
+ const reuse = await resolvePendingMutationReuseDecision({
903
+ rpc,
904
+ walletName,
905
+ mutation: reconciled.mutation,
906
+ nextFeeSelection: feeSelection,
907
+ });
908
+ if (reuse.reuseExisting) {
909
+ return {
910
+ kind: "claim",
911
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
912
+ status: reconciled.resolution,
913
+ reusedExisting: true,
914
+ amountCogtoshi: operation.amountCogtoshi,
915
+ recipientDomainName: operation.recipientDomainName,
916
+ lockId: options.lockId,
917
+ resolved: operation.resolved,
918
+ fees: reuse.fees,
919
+ };
920
+ }
921
+ replacementFixedInputs = reuse.replacementFixedInputs;
964
922
  }
965
923
  if (reconciled.resolution === "repair-required") {
966
924
  throw new Error(`${errorPrefix}_repair_required`);
@@ -974,7 +932,7 @@ async function runClaimLikeMutation(options, reclaim) {
974
932
  resolved: operation.resolved,
975
933
  assumeYes: options.assumeYes,
976
934
  });
977
- let nextState = upsertPendingMutation(operation.state, createDraftMutation({
935
+ let nextState = upsertPendingMutation(workingState, createDraftMutation({
978
936
  kind: "claim",
979
937
  sender: operation.sender,
980
938
  amountCogtoshi: operation.amountCogtoshi,
@@ -983,6 +941,7 @@ async function runClaimLikeMutation(options, reclaim) {
983
941
  preimageHex,
984
942
  intentFingerprintHex,
985
943
  nowUnixMs,
944
+ feeSelection,
986
945
  existing: existingMutation,
987
946
  }));
988
947
  nextState = {
@@ -993,23 +952,25 @@ async function runClaimLikeMutation(options, reclaim) {
993
952
  await saveWalletStatePreservingUnlock({
994
953
  state: nextState,
995
954
  provider,
996
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
997
955
  nowUnixMs,
998
956
  paths,
999
957
  });
958
+ const claimPlan = buildPlanForCogOperation({
959
+ state: nextState,
960
+ allUtxos: await rpc.listUnspent(walletName, 1),
961
+ sender: operation.sender,
962
+ opReturnData: serializeCogClaim(options.lockId, Buffer.from(preimageHex, "hex")).opReturnData,
963
+ errorPrefix,
964
+ });
1000
965
  const built = await buildTransaction({
1001
966
  rpc,
1002
967
  walletName,
1003
968
  state: nextState,
1004
- plan: buildPlanForCogOperation({
1005
- state: nextState,
1006
- allUtxos: await rpc.listUnspent(walletName, 1),
1007
- sender: operation.sender,
1008
- anchorOutpoint: operation.anchorOutpoint,
1009
- opReturnData: serializeCogClaim(options.lockId, Buffer.from(preimageHex, "hex")).opReturnData,
1010
- anchorValueSats: BigInt(nextState.anchorValueSats),
1011
- errorPrefix,
1012
- }),
969
+ plan: {
970
+ ...claimPlan,
971
+ fixedInputs: mergeFixedWalletInputs(claimPlan.fixedInputs, replacementFixedInputs),
972
+ },
973
+ feeRateSatVb: feeSelection.feeRateSatVb,
1013
974
  });
1014
975
  const final = await sendBuiltTransaction({
1015
976
  rpc,
@@ -1019,7 +980,6 @@ async function runClaimLikeMutation(options, reclaim) {
1019
980
  mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
1020
981
  state: nextState,
1021
982
  provider,
1022
- unlockUntilUnixMs: operation.unlockUntilUnixMs,
1023
983
  nowUnixMs,
1024
984
  paths,
1025
985
  errorPrefix,
@@ -1033,6 +993,10 @@ async function runClaimLikeMutation(options, reclaim) {
1033
993
  recipientDomainName: operation.recipientDomainName,
1034
994
  lockId: options.lockId,
1035
995
  resolved: operation.resolved,
996
+ fees: createBuiltWalletMutationFeeSummary({
997
+ selection: feeSelection,
998
+ built,
999
+ }),
1036
1000
  };
1037
1001
  }
1038
1002
  finally {