@cogcoin/client 0.5.11 → 0.5.13

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 (46) hide show
  1. package/README.md +3 -1
  2. package/dist/app-paths.d.ts +1 -0
  3. package/dist/app-paths.js +3 -0
  4. package/dist/bitcoind/bootstrap/controller.d.ts +3 -0
  5. package/dist/bitcoind/bootstrap/controller.js +7 -5
  6. package/dist/bitcoind/client/factory.d.ts +8 -0
  7. package/dist/bitcoind/client/factory.js +43 -6
  8. package/dist/bitcoind/client/managed-client.js +19 -10
  9. package/dist/bitcoind/client/sync-engine.js +35 -4
  10. package/dist/bitcoind/progress/formatting.js +1 -1
  11. package/dist/bitcoind/testing.d.ts +1 -0
  12. package/dist/bitcoind/testing.js +1 -0
  13. package/dist/cli/commands/follow.js +47 -14
  14. package/dist/cli/commands/sync.js +48 -15
  15. package/dist/cli/context.js +5 -1
  16. package/dist/cli/output.js +11 -0
  17. package/dist/cli/runner.js +2 -0
  18. package/dist/cli/signals.d.ts +1 -1
  19. package/dist/cli/signals.js +17 -4
  20. package/dist/cli/types.d.ts +4 -0
  21. package/dist/cli/update-notifier.d.ts +2 -0
  22. package/dist/cli/update-notifier.js +276 -0
  23. package/dist/client/default-client.d.ts +1 -1
  24. package/dist/client/default-client.js +7 -1
  25. package/dist/client/factory.js +6 -1
  26. package/dist/sqlite/store.js +3 -0
  27. package/dist/types.d.ts +2 -0
  28. package/dist/wallet/archive.js +10 -8
  29. package/dist/wallet/coin-control.d.ts +41 -0
  30. package/dist/wallet/coin-control.js +365 -0
  31. package/dist/wallet/lifecycle.js +39 -2
  32. package/dist/wallet/mining/runner.js +46 -44
  33. package/dist/wallet/read/context.js +15 -6
  34. package/dist/wallet/reset.js +2 -0
  35. package/dist/wallet/state/storage.js +5 -4
  36. package/dist/wallet/tx/anchor.js +36 -51
  37. package/dist/wallet/tx/cog.js +19 -12
  38. package/dist/wallet/tx/common.d.ts +41 -10
  39. package/dist/wallet/tx/common.js +112 -5
  40. package/dist/wallet/tx/domain-admin.js +13 -8
  41. package/dist/wallet/tx/domain-market.js +19 -12
  42. package/dist/wallet/tx/field.js +21 -18
  43. package/dist/wallet/tx/register.js +17 -12
  44. package/dist/wallet/tx/reputation.js +13 -8
  45. package/dist/wallet/types.d.ts +4 -0
  46. package/package.json +1 -1
@@ -9,7 +9,7 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
9
9
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
10
10
  import { serializeSetCanonical, serializeSetDelegate, serializeSetEndpoint, serializeSetMiner, validateDomainName, } from "../cogop/index.js";
11
11
  import { openWalletReadContext } from "../read/index.js";
12
- import { assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
12
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
13
13
  import { confirmYesNo } from "./confirm.js";
14
14
  import { getCanonicalIdentitySelector } from "./identity-selector.js";
15
15
  import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
@@ -133,9 +133,8 @@ function buildPlanForDomainAdminOperation(options) {
133
133
  return {
134
134
  sender: options.sender,
135
135
  changeAddress: options.state.funding.address,
136
- inputs: [
136
+ fixedInputs: [
137
137
  { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
138
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
139
138
  ],
140
139
  outputs: [
141
140
  { data: Buffer.from(options.opReturnData).toString("hex") },
@@ -146,6 +145,7 @@ function buildPlanForDomainAdminOperation(options) {
146
145
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
147
146
  expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
148
147
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
148
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
149
149
  errorPrefix: options.errorPrefix,
150
150
  };
151
151
  }
@@ -155,14 +155,17 @@ function validateFundedDraft(decoded, funded, plan) {
155
155
  if (inputs.length === 0) {
156
156
  throw new Error(`${plan.errorPrefix}_missing_sender_input`);
157
157
  }
158
+ assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
158
159
  if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
159
160
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
160
161
  }
161
- for (let index = 1; index < inputs.length; index += 1) {
162
- if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
163
- throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
164
- }
165
- }
162
+ assertFundingInputsAfterFixedPrefix({
163
+ inputs,
164
+ fixedInputs: plan.fixedInputs,
165
+ allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
166
+ eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
167
+ errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
168
+ });
166
169
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
167
170
  throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
168
171
  }
@@ -189,6 +192,7 @@ async function buildTransaction(options) {
189
192
  return buildWalletMutationTransaction({
190
193
  rpc: options.rpc,
191
194
  walletName: options.walletName,
195
+ state: options.state,
192
196
  plan: options.plan,
193
197
  validateFundedDraft,
194
198
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
@@ -632,6 +636,7 @@ async function submitDomainAdminMutation(options) {
632
636
  const built = await buildTransaction({
633
637
  rpc,
634
638
  walletName,
639
+ state: nextState,
635
640
  plan: buildPlanForDomainAdminOperation({
636
641
  state: nextState,
637
642
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -7,7 +7,7 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
7
7
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
8
8
  import { serializeDomainBuy, serializeDomainSell, serializeDomainTransfer, validateDomainName, } from "../cogop/index.js";
9
9
  import { openWalletReadContext } from "../read/index.js";
10
- import { assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
10
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, 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";
@@ -258,11 +258,8 @@ function buildPlanForDomainOperation(options) {
258
258
  return {
259
259
  sender: options.sender,
260
260
  changeAddress: options.state.funding.address,
261
- inputs: [
261
+ fixedInputs: [
262
262
  { txid: senderUtxo.txid, vout: senderUtxo.vout },
263
- ...fundingUtxos
264
- .filter((entry) => !(entry.txid === senderUtxo.txid && entry.vout === senderUtxo.vout))
265
- .map((entry) => ({ txid: entry.txid, vout: entry.vout })),
266
263
  ],
267
264
  outputs,
268
265
  changePosition: 1,
@@ -270,6 +267,9 @@ function buildPlanForDomainOperation(options) {
270
267
  expectedAnchorScriptHex: null,
271
268
  expectedAnchorValueSats: null,
272
269
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
270
+ eligibleFundingOutpointKeys: new Set(fundingUtxos
271
+ .filter((entry) => !(entry.txid === senderUtxo.txid && entry.vout === senderUtxo.vout))
272
+ .map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
273
273
  errorPrefix: options.errorPrefix,
274
274
  };
275
275
  }
@@ -288,9 +288,8 @@ function buildPlanForDomainOperation(options) {
288
288
  return {
289
289
  sender: options.sender,
290
290
  changeAddress: options.state.funding.address,
291
- inputs: [
291
+ fixedInputs: [
292
292
  { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
293
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
294
293
  ],
295
294
  outputs,
296
295
  changePosition: 2,
@@ -298,6 +297,7 @@ function buildPlanForDomainOperation(options) {
298
297
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
299
298
  expectedAnchorValueSats: options.anchorValueSats,
300
299
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
300
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
301
301
  errorPrefix: options.errorPrefix,
302
302
  };
303
303
  }
@@ -307,14 +307,17 @@ function validateFundedDraft(decoded, funded, plan) {
307
307
  if (inputs.length === 0) {
308
308
  throw new Error(`${plan.errorPrefix}_missing_sender_input`);
309
309
  }
310
+ assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
310
311
  if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
311
312
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
312
313
  }
313
- for (let index = 1; index < inputs.length; index += 1) {
314
- if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
315
- throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
316
- }
317
- }
314
+ assertFundingInputsAfterFixedPrefix({
315
+ inputs,
316
+ fixedInputs: plan.fixedInputs,
317
+ allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
318
+ eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
319
+ errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
320
+ });
318
321
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
319
322
  throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
320
323
  }
@@ -344,6 +347,7 @@ async function buildTransaction(options) {
344
347
  return buildWalletMutationTransaction({
345
348
  rpc: options.rpc,
346
349
  walletName: options.walletName,
350
+ state: options.state,
347
351
  plan: options.plan,
348
352
  validateFundedDraft,
349
353
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
@@ -768,6 +772,7 @@ export async function transferDomain(options) {
768
772
  const built = await buildTransaction({
769
773
  rpc,
770
774
  walletName,
775
+ state: nextState,
771
776
  plan: buildPlanForDomainOperation({
772
777
  state: nextState,
773
778
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -998,6 +1003,7 @@ async function runSellMutation(options) {
998
1003
  const built = await buildTransaction({
999
1004
  rpc,
1000
1005
  walletName,
1006
+ state: nextState,
1001
1007
  plan: buildPlanForDomainOperation({
1002
1008
  state: nextState,
1003
1009
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -1233,6 +1239,7 @@ export async function buyDomain(options) {
1233
1239
  const built = await buildTransaction({
1234
1240
  rpc,
1235
1241
  walletName,
1242
+ state: nextState,
1236
1243
  plan: buildPlanForDomainOperation({
1237
1244
  state: nextState,
1238
1245
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -5,12 +5,13 @@ import { getBalance, lookupDomain, } from "@cogcoin/indexer/queries";
5
5
  import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
6
6
  import { createRpcClient } from "../../bitcoind/node.js";
7
7
  import { acquireFileLock } from "../fs/lock.js";
8
+ import { computeDesignatedProactiveReserveOutpoints } from "../coin-control.js";
8
9
  import { resolveWalletRuntimePathsForTesting, } from "../runtime.js";
9
10
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
10
11
  import { FIELD_FORMAT_BYTES, serializeDataUpdate, serializeFieldReg, } from "../cogop/index.js";
11
12
  import { validateFieldName } from "../cogop/validate-name.js";
12
13
  import { findDomainField, openWalletReadContext, } from "../read/index.js";
13
- import { assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
14
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
14
15
  import { confirmTypedAcknowledgement as confirmSharedTypedAcknowledgement, confirmYesNo as confirmSharedYesNo, } from "./confirm.js";
15
16
  import { getCanonicalIdentitySelector } from "./identity-selector.js";
16
17
  import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
@@ -253,9 +254,8 @@ function buildAnchoredFieldPlan(options) {
253
254
  return {
254
255
  sender: options.sender,
255
256
  changeAddress: options.state.funding.address,
256
- inputs: [
257
+ fixedInputs: [
257
258
  { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
258
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
259
259
  ],
260
260
  outputs: [
261
261
  { data: Buffer.from(options.opReturnData).toString("hex") },
@@ -266,6 +266,7 @@ function buildAnchoredFieldPlan(options) {
266
266
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
267
267
  expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
268
268
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
269
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
269
270
  errorPrefix: options.errorPrefix,
270
271
  };
271
272
  }
@@ -277,10 +278,7 @@ function buildFieldFamilyTx2Plan(options) {
277
278
  return {
278
279
  sender: options.sender,
279
280
  changeAddress: options.state.funding.address,
280
- inputs: [
281
- { txid: options.tx1Txid, vout: 1 },
282
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
283
- ],
281
+ fixedInputs: [{ txid: options.tx1Txid, vout: 1 }],
284
282
  outputs: [
285
283
  { data: Buffer.from(options.opReturnData).toString("hex") },
286
284
  { [options.sender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
@@ -290,6 +288,7 @@ function buildFieldFamilyTx2Plan(options) {
290
288
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
291
289
  expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
292
290
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
291
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
293
292
  errorPrefix: "wallet_field_create_tx2",
294
293
  };
295
294
  }
@@ -299,14 +298,17 @@ function validateFieldDraft(decoded, funded, plan) {
299
298
  if (inputs.length === 0) {
300
299
  throw new Error(`${plan.errorPrefix}_missing_sender_input`);
301
300
  }
301
+ assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
302
302
  if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
303
303
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
304
304
  }
305
- for (let index = 1; index < inputs.length; index += 1) {
306
- if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
307
- throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
308
- }
309
- }
305
+ assertFundingInputsAfterFixedPrefix({
306
+ inputs,
307
+ fixedInputs: plan.fixedInputs,
308
+ allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
309
+ eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
310
+ errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
311
+ });
310
312
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
311
313
  throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
312
314
  }
@@ -330,14 +332,16 @@ function validateFieldDraft(decoded, funded, plan) {
330
332
  }
331
333
  }
332
334
  async function buildFieldTransaction(options) {
333
- return buildWalletMutationTransaction({
335
+ const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
336
+ return buildWalletMutationTransactionWithReserveFallback({
334
337
  rpc: options.rpc,
335
338
  walletName: options.walletName,
339
+ state: options.state,
336
340
  plan: options.plan,
337
341
  validateFundedDraft: validateFieldDraft,
338
342
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
339
343
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
340
- builderOptions: options.builderOptions,
344
+ reserveCandidates,
341
345
  });
342
346
  }
343
347
  async function saveUpdatedState(options) {
@@ -1348,6 +1352,7 @@ async function submitStandaloneFieldMutation(options) {
1348
1352
  const built = await buildFieldTransaction({
1349
1353
  rpc,
1350
1354
  walletName,
1355
+ state: nextState,
1351
1356
  plan: buildAnchoredFieldPlan({
1352
1357
  state: nextState,
1353
1358
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -1544,6 +1549,7 @@ async function submitFieldCreateFamily(options) {
1544
1549
  const tx1 = await buildFieldTransaction({
1545
1550
  rpc,
1546
1551
  walletName,
1552
+ state: nextState,
1547
1553
  plan: buildAnchoredFieldPlan({
1548
1554
  state: nextState,
1549
1555
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -1576,6 +1582,7 @@ async function submitFieldCreateFamily(options) {
1576
1582
  const tx2 = await buildFieldTransaction({
1577
1583
  rpc,
1578
1584
  walletName,
1585
+ state: workingState,
1579
1586
  plan: buildFieldFamilyTx2Plan({
1580
1587
  state: workingState,
1581
1588
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -1583,10 +1590,6 @@ async function submitFieldCreateFamily(options) {
1583
1590
  tx1Txid,
1584
1591
  opReturnData: serializeDataUpdate(operation.chainDomain.domainId, resumedFamily.expectedFieldId ?? operation.chainDomain.nextFieldId, options.value.format, options.value.value).opReturnData,
1585
1592
  }),
1586
- builderOptions: {
1587
- includeUnsafe: true,
1588
- minConf: 0,
1589
- },
1590
1593
  });
1591
1594
  const final = await sendFamilyTx2({
1592
1595
  rpc,
@@ -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 { assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, 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";
@@ -401,14 +401,17 @@ function validateFundedDraft(decoded, funded, plan) {
401
401
  if (inputs.length === 0) {
402
402
  throw new Error("wallet_register_missing_sender_input");
403
403
  }
404
+ assertFixedInputPrefixMatches(inputs, plan.fixedInputs, "wallet_register_sender_input_mismatch");
404
405
  if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
405
406
  throw new Error("wallet_register_sender_input_mismatch");
406
407
  }
407
- for (let index = 1; index < inputs.length; index += 1) {
408
- if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
409
- throw new Error("wallet_register_unexpected_funding_input");
410
- }
411
- }
408
+ assertFundingInputsAfterFixedPrefix({
409
+ inputs,
410
+ fixedInputs: plan.fixedInputs,
411
+ allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
412
+ eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
413
+ errorCode: "wallet_register_unexpected_funding_input",
414
+ });
412
415
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
413
416
  throw new Error("wallet_register_opreturn_mismatch");
414
417
  }
@@ -467,9 +470,8 @@ function buildRegisterPlan(options) {
467
470
  registerKind: "root",
468
471
  sender: options.sender,
469
472
  changeAddress: options.state.funding.address,
470
- inputs: [
473
+ fixedInputs: [
471
474
  { txid: senderInput.txid, vout: senderInput.vout },
472
- ...additionalFunding,
473
475
  ],
474
476
  outputs: rootOutputs.outputs,
475
477
  changePosition: rootOutputs.changePosition,
@@ -481,6 +483,7 @@ function buildRegisterPlan(options) {
481
483
  expectedAnchorScriptHex: null,
482
484
  expectedAnchorValueSats: null,
483
485
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
486
+ eligibleFundingOutpointKeys: new Set(additionalFunding.map((entry) => outpointKey(entry))),
484
487
  };
485
488
  }
486
489
  const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint?.txid
@@ -494,9 +497,8 @@ function buildRegisterPlan(options) {
494
497
  registerKind: "root",
495
498
  sender: options.sender,
496
499
  changeAddress: options.state.funding.address,
497
- inputs: [
500
+ fixedInputs: [
498
501
  { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
499
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
500
502
  ],
501
503
  outputs: rootOutputs.outputs,
502
504
  changePosition: rootOutputs.changePosition,
@@ -508,6 +510,7 @@ function buildRegisterPlan(options) {
508
510
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
509
511
  expectedAnchorValueSats: options.anchorValueSats,
510
512
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
513
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey(entry))),
511
514
  };
512
515
  }
513
516
  const anchor = options.anchorOutpoint;
@@ -530,9 +533,8 @@ function buildRegisterPlan(options) {
530
533
  registerKind: "subdomain",
531
534
  sender: options.sender,
532
535
  changeAddress: options.state.funding.address,
533
- inputs: [
536
+ fixedInputs: [
534
537
  { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
535
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
536
538
  ],
537
539
  outputs: subdomainOutputs.outputs,
538
540
  changePosition: subdomainOutputs.changePosition,
@@ -544,12 +546,14 @@ function buildRegisterPlan(options) {
544
546
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
545
547
  expectedAnchorValueSats: options.anchorValueSats,
546
548
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
549
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey(entry))),
547
550
  };
548
551
  }
549
552
  async function buildRegisterTransaction(options) {
550
553
  return buildWalletMutationTransaction({
551
554
  rpc: options.rpc,
552
555
  walletName: options.walletName,
556
+ state: options.state,
553
557
  plan: options.plan,
554
558
  validateFundedDraft,
555
559
  finalizeErrorCode: "wallet_register_finalize_failed",
@@ -810,6 +814,7 @@ export async function registerDomain(options) {
810
814
  const built = await buildRegisterTransaction({
811
815
  rpc,
812
816
  walletName,
817
+ state: nextState,
813
818
  plan,
814
819
  });
815
820
  const currentMutation = nextState.pendingMutations?.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex)
@@ -8,7 +8,7 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
8
8
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
9
9
  import { serializeRepCommit, serializeRepRevoke, validateDomainName, } from "../cogop/index.js";
10
10
  import { openWalletReadContext } from "../read/index.js";
11
- import { assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
12
12
  import { confirmTypedAcknowledgement as confirmSharedTypedAcknowledgement, confirmYesNo as confirmSharedYesNo, } from "./confirm.js";
13
13
  import { getCanonicalIdentitySelector } from "./identity-selector.js";
14
14
  import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
@@ -162,9 +162,8 @@ function buildPlanForReputationOperation(options) {
162
162
  return {
163
163
  sender: options.sender,
164
164
  changeAddress: options.state.funding.address,
165
- inputs: [
165
+ fixedInputs: [
166
166
  { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
167
- ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
168
167
  ],
169
168
  outputs: [
170
169
  { data: Buffer.from(options.opReturnData).toString("hex") },
@@ -175,6 +174,7 @@ function buildPlanForReputationOperation(options) {
175
174
  expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
176
175
  expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
177
176
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
177
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
178
178
  errorPrefix: options.errorPrefix,
179
179
  };
180
180
  }
@@ -184,14 +184,17 @@ function validateFundedDraft(decoded, funded, plan) {
184
184
  if (inputs.length === 0) {
185
185
  throw new Error(`${plan.errorPrefix}_missing_sender_input`);
186
186
  }
187
+ assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
187
188
  if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
188
189
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
189
190
  }
190
- for (let index = 1; index < inputs.length; index += 1) {
191
- if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
192
- throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
193
- }
194
- }
191
+ assertFundingInputsAfterFixedPrefix({
192
+ inputs,
193
+ fixedInputs: plan.fixedInputs,
194
+ allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
195
+ eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
196
+ errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
197
+ });
195
198
  if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
196
199
  throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
197
200
  }
@@ -218,6 +221,7 @@ async function buildTransaction(options) {
218
221
  return buildWalletMutationTransaction({
219
222
  rpc: options.rpc,
220
223
  walletName: options.walletName,
224
+ state: options.state,
221
225
  plan: options.plan,
222
226
  validateFundedDraft,
223
227
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
@@ -635,6 +639,7 @@ async function submitReputationMutation(options) {
635
639
  const built = await buildTransaction({
636
640
  rpc,
637
641
  walletName,
642
+ state: nextState,
638
643
  plan: buildPlanForReputationOperation({
639
644
  state: nextState,
640
645
  allUtxos: await rpc.listUnspent(walletName, 1),
@@ -142,6 +142,8 @@ export interface WalletStateV1 {
142
142
  walletRootId: string;
143
143
  network: WalletNetwork;
144
144
  anchorValueSats: number;
145
+ proactiveReserveSats: number;
146
+ proactiveReserveOutpoints: OutpointRecord[];
145
147
  nextDedicatedIndex: number;
146
148
  fundingIndex: 0;
147
149
  mnemonic: {
@@ -191,6 +193,8 @@ export interface PortableWalletArchivePayloadV1 {
191
193
  walletRootId: string;
192
194
  network: WalletNetwork;
193
195
  anchorValueSats: number;
196
+ proactiveReserveSats: number;
197
+ proactiveReserveOutpoints: OutpointRecord[];
194
198
  nextDedicatedIndex: number;
195
199
  fundingIndex: 0;
196
200
  mnemonic: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",