@cogcoin/client 0.5.13 → 0.5.14

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.
@@ -5,7 +5,6 @@ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
5
5
  import { createRpcClient } from "../../bitcoind/node.js";
6
6
  import { acquireFileLock } from "../fs/lock.js";
7
7
  import { deriveWalletIdentityMaterial, } from "../material.js";
8
- import { computeDesignatedProactiveReserveOutpoints } from "../coin-control.js";
9
8
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
10
9
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
11
10
  import { serializeDomainAnchor, serializeDomainTransfer, validateDomainName, } from "../cogop/index.js";
@@ -116,6 +115,11 @@ function findActiveAnchorFamilyByDomain(state, domainName) {
116
115
  && family.domainName === domainName
117
116
  && ACTIVE_FAMILY_STATUSES.has(family.status)) ?? null;
118
117
  }
118
+ function isClearableReservedAnchorFamily(family) {
119
+ return family?.type === "anchor"
120
+ && family.status === "draft"
121
+ && family.currentStep === "reserved";
122
+ }
119
123
  function findAnchorFamilyById(state, familyId) {
120
124
  return state.proactiveFamilies.find((family) => family.familyId === familyId) ?? null;
121
125
  }
@@ -216,6 +220,35 @@ function encodeFoundingMessage(foundingMessageText) {
216
220
  throw new Error(error instanceof Error ? `wallet_anchor_invalid_message_${error.message}` : "wallet_anchor_invalid_message");
217
221
  });
218
222
  }
223
+ function extractAnchorInvalidMessageReason(error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ if (message === "wallet_anchor_invalid_message") {
226
+ return null;
227
+ }
228
+ if (!message.startsWith("wallet_anchor_invalid_message_")) {
229
+ return null;
230
+ }
231
+ const reason = message.slice("wallet_anchor_invalid_message_".length).trim();
232
+ return reason === "" ? null : reason;
233
+ }
234
+ async function resolveFoundingMessage(options) {
235
+ if (!options.promptForFoundingMessageWhenMissing || options.foundingMessageText != null) {
236
+ return encodeFoundingMessage(options.foundingMessageText ?? null);
237
+ }
238
+ for (;;) {
239
+ const answer = await options.prompter.prompt("Founding message (optional, press Enter to skip): ");
240
+ try {
241
+ return await encodeFoundingMessage(answer);
242
+ }
243
+ catch (error) {
244
+ const reason = extractAnchorInvalidMessageReason(error);
245
+ options.prompter.writeLine("Founding message cannot be encoded in canonical Coglex.");
246
+ if (reason !== null) {
247
+ options.prompter.writeLine(`Reason: ${reason}`);
248
+ }
249
+ }
250
+ }
251
+ }
219
252
  function resolveAnchorOutpointForSender(state, senderIndex) {
220
253
  const anchoredDomain = state.domains.find((domain) => domain.currentOwnerLocalIndex === senderIndex
221
254
  && domain.canonicalChainStatus === "anchored"
@@ -457,14 +490,10 @@ function buildTx1Plan(options) {
457
490
  { [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
458
491
  ];
459
492
  if (options.operation.sourceAnchorOutpoint === null) {
460
- if (fundingUtxos.length === 0) {
461
- throw new Error("wallet_anchor_sender_utxo_unavailable");
462
- }
463
- const senderInput = fundingUtxos[0];
464
493
  return {
465
494
  sender: options.operation.sourceSender,
466
495
  changeAddress: options.state.funding.address,
467
- fixedInputs: [{ txid: senderInput.txid, vout: senderInput.vout }],
496
+ fixedInputs: [],
468
497
  outputs,
469
498
  changePosition: 2,
470
499
  expectedOpReturnScriptHex: encodeOpReturnScript(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData),
@@ -473,9 +502,7 @@ function buildTx1Plan(options) {
473
502
  expectedReplacementAnchorScriptHex: null,
474
503
  expectedReplacementAnchorValueSats: null,
475
504
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
476
- eligibleFundingOutpointKeys: new Set(fundingUtxos
477
- .filter((entry) => !(entry.txid === senderInput.txid && entry.vout === senderInput.vout))
478
- .map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
505
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
479
506
  requiredSenderOutpoint: null,
480
507
  requiredProvisionalOutpoint: null,
481
508
  errorPrefix: "wallet_anchor_tx1",
@@ -653,7 +680,6 @@ function validateTx2Draft(decoded, funded, plan) {
653
680
  }
654
681
  }
655
682
  async function buildTx1(options) {
656
- const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
657
683
  return buildWalletMutationTransactionWithReserveFallback({
658
684
  rpc: options.rpc,
659
685
  walletName: options.walletName,
@@ -662,11 +688,10 @@ async function buildTx1(options) {
662
688
  validateFundedDraft: validateTx1Draft,
663
689
  finalizeErrorCode: "wallet_anchor_tx1_finalize_failed",
664
690
  mempoolRejectPrefix: "wallet_anchor_tx1_mempool_rejected",
665
- reserveCandidates,
691
+ reserveCandidates: options.state.proactiveReserveOutpoints,
666
692
  });
667
693
  }
668
694
  async function buildTx2(options) {
669
- const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
670
695
  return buildWalletMutationTransactionWithReserveFallback({
671
696
  rpc: options.rpc,
672
697
  walletName: options.walletName,
@@ -675,7 +700,7 @@ async function buildTx2(options) {
675
700
  validateFundedDraft: validateTx2Draft,
676
701
  finalizeErrorCode: "wallet_anchor_tx2_finalize_failed",
677
702
  mempoolRejectPrefix: "wallet_anchor_tx2_mempool_rejected",
678
- reserveCandidates,
703
+ reserveCandidates: options.state.proactiveReserveOutpoints,
679
704
  });
680
705
  }
681
706
  async function relockAnchorOutpoint(rpc, walletName, outpoint) {
@@ -1107,6 +1132,7 @@ async function submitTx2(options) {
1107
1132
  dedicatedIndex: options.operation.targetIdentity.localIndex,
1108
1133
  status: finalStatus,
1109
1134
  reusedExisting: false,
1135
+ foundingMessageText: options.operation.foundingMessageText,
1110
1136
  };
1111
1137
  }
1112
1138
  function ensureSameTipHeight(context, bestHeight, errorCode) {
@@ -1131,7 +1157,11 @@ export async function anchorDomain(options) {
1131
1157
  paths,
1132
1158
  reason: "wallet-anchor",
1133
1159
  });
1134
- const message = await encodeFoundingMessage(options.foundingMessageText ?? null);
1160
+ const message = await resolveFoundingMessage({
1161
+ foundingMessageText: options.foundingMessageText,
1162
+ promptForFoundingMessageWhenMissing: options.promptForFoundingMessageWhenMissing,
1163
+ prompter: options.prompter,
1164
+ });
1135
1165
  const readContext = await (options.openReadContext ?? openWalletReadContext)({
1136
1166
  dataDir: options.dataDir,
1137
1167
  databasePath: options.databasePath,
@@ -1144,6 +1174,9 @@ export async function anchorDomain(options) {
1144
1174
  const initialFamily = createDraftAnchorFamily(operation, nowUnixMs);
1145
1175
  const existingFamily = findAnchorFamilyByIntent(operation.state, initialFamily.intentFingerprintHex);
1146
1176
  const conflictingFamily = findActiveAnchorFamilyByDomain(operation.state, normalizedDomainName);
1177
+ if (existingFamily === null && isClearableReservedAnchorFamily(conflictingFamily)) {
1178
+ throw new Error(`wallet_anchor_clear_pending_first_${conflictingFamily.domainName}`);
1179
+ }
1147
1180
  if (existingFamily === null && conflictingFamily !== null) {
1148
1181
  throw new Error("wallet_anchor_prior_family_unresolved");
1149
1182
  }
@@ -1185,6 +1218,7 @@ export async function anchorDomain(options) {
1185
1218
  dedicatedIndex: reconciled.family.reservedDedicatedIndex ?? existingTargetIdentity.localIndex,
1186
1219
  status: reconciled.resolution,
1187
1220
  reusedExisting: true,
1221
+ foundingMessageText: reconciled.family.foundingMessageText,
1188
1222
  };
1189
1223
  }
1190
1224
  if (reconciled.resolution === "repair-required") {
@@ -1336,6 +1370,7 @@ export async function anchorDomain(options) {
1336
1370
  return {
1337
1371
  ...result,
1338
1372
  reusedExisting: resumedExisting,
1373
+ foundingMessageText: result.foundingMessageText ?? operation.foundingMessageText,
1339
1374
  };
1340
1375
  }
1341
1376
  finally {
@@ -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, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
10
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, 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";
@@ -248,28 +248,17 @@ function buildPlanForCogOperation(options) {
248
248
  && entry.safe !== false);
249
249
  const outputs = [{ data: Buffer.from(options.opReturnData).toString("hex") }];
250
250
  if (options.anchorOutpoint === null) {
251
- const senderUtxo = options.allUtxos.find((entry) => entry.scriptPubKey === options.sender.scriptPubKeyHex
252
- && entry.confirmations >= 1
253
- && entry.spendable !== false
254
- && entry.safe !== false);
255
- if (senderUtxo === undefined) {
256
- throw new Error(`${options.errorPrefix}_sender_utxo_unavailable`);
257
- }
258
251
  return {
259
252
  sender: options.sender,
260
253
  changeAddress: options.state.funding.address,
261
- fixedInputs: [
262
- { txid: senderUtxo.txid, vout: senderUtxo.vout },
263
- ],
254
+ fixedInputs: [],
264
255
  outputs,
265
256
  changePosition: 1,
266
257
  expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
267
258
  expectedAnchorScriptHex: null,
268
259
  expectedAnchorValueSats: null,
269
260
  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 }))),
261
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
273
262
  errorPrefix: options.errorPrefix,
274
263
  };
275
264
  }
@@ -344,7 +333,7 @@ function validateFundedDraft(decoded, funded, plan) {
344
333
  }
345
334
  }
346
335
  async function buildTransaction(options) {
347
- return buildWalletMutationTransaction({
336
+ return buildWalletMutationTransactionWithReserveFallback({
348
337
  rpc: options.rpc,
349
338
  walletName: options.walletName,
350
339
  state: options.state,
@@ -352,6 +341,7 @@ async function buildTransaction(options) {
352
341
  validateFundedDraft,
353
342
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
354
343
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
344
+ reserveCandidates: options.state.proactiveReserveOutpoints,
355
345
  });
356
346
  }
357
347
  function createDraftMutation(options) {
@@ -95,6 +95,8 @@ export declare function buildWalletMutationTransaction<TPlan>(options: {
95
95
  outputs: unknown[];
96
96
  changeAddress: string;
97
97
  changePosition: number;
98
+ allowedFundingScriptPubKeyHex: string;
99
+ eligibleFundingOutpointKeys: Set<string>;
98
100
  };
99
101
  validateFundedDraft(decoded: RpcDecodedPsbt, funded: RpcWalletCreateFundedPsbtResult, plan: TPlan): void;
100
102
  finalizeErrorCode: string;
@@ -111,6 +113,8 @@ export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>
111
113
  outputs: unknown[];
112
114
  changeAddress: string;
113
115
  changePosition: number;
116
+ allowedFundingScriptPubKeyHex: string;
117
+ eligibleFundingOutpointKeys: Set<string>;
114
118
  };
115
119
  validateFundedDraft(decoded: RpcDecodedPsbt, funded: RpcWalletCreateFundedPsbtResult, plan: TPlan): void;
116
120
  finalizeErrorCode: string;
@@ -5,6 +5,14 @@ import { createWalletSecretReference, } from "../state/provider.js";
5
5
  import { reconcilePersistentPolicyLocks as reconcileWalletCoinControlLocks } from "../coin-control.js";
6
6
  import { requestMiningGenerationPreemption } from "../mining/coordination.js";
7
7
  export const DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB = 10;
8
+ function btcNumberToSats(value) {
9
+ return BigInt(Math.round(value * 100_000_000));
10
+ }
11
+ function valueToSats(value) {
12
+ return typeof value === "string"
13
+ ? BigInt(Math.round(Number(value) * 100_000_000))
14
+ : btcNumberToSats(value);
15
+ }
8
16
  function createUnlockSessionState(state, unlockUntilUnixMs, nowUnixMs) {
9
17
  return {
10
18
  schemaVersion: 1,
@@ -40,6 +48,12 @@ export function formatCogAmount(value) {
40
48
  export function outpointKey(outpoint) {
41
49
  return `${outpoint.txid}:${outpoint.vout}`;
42
50
  }
51
+ function isSpendableConfirmedFundingUtxo(entry, fundingScriptPubKeyHex) {
52
+ return entry.scriptPubKey === fundingScriptPubKeyHex
53
+ && entry.confirmations >= 1
54
+ && entry.spendable !== false
55
+ && entry.safe !== false;
56
+ }
43
57
  export function updateMutationRecord(mutation, status, nowUnixMs, options = {}) {
44
58
  return {
45
59
  ...mutation,
@@ -132,6 +146,34 @@ export function isInsufficientFundsError(error) {
132
146
  const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
133
147
  return message.includes("insufficient funds");
134
148
  }
149
+ function isReserveFloorFundingError(error) {
150
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
151
+ return message.includes("insufficient_funding_after_reserve");
152
+ }
153
+ function computeRemainingFundingValueSats(options) {
154
+ let remaining = 0n;
155
+ for (const value of options.availableFundingValueByKey.values()) {
156
+ remaining += value;
157
+ }
158
+ for (const input of options.transaction.vin) {
159
+ const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(input);
160
+ const vout = getDecodedInputVout(input);
161
+ if (scriptPubKeyHex !== options.fundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
162
+ continue;
163
+ }
164
+ remaining -= options.availableFundingValueByKey.get(outpointKey({
165
+ txid: input.txid,
166
+ vout,
167
+ })) ?? 0n;
168
+ }
169
+ for (const output of options.transaction.vout) {
170
+ if (output.scriptPubKey?.hex !== options.fundingScriptPubKeyHex) {
171
+ continue;
172
+ }
173
+ remaining += valueToSats(output.value);
174
+ }
175
+ return remaining;
176
+ }
135
177
  export function assertWalletMutationContextReady(context, errorPrefix) {
136
178
  if (context.localState.availability === "uninitialized") {
137
179
  throw new Error("wallet_uninitialized");
@@ -169,6 +211,19 @@ export async function buildWalletMutationTransaction(options) {
169
211
  fixedInputs: options.plan.fixedInputs,
170
212
  temporarilyUnlockedOutpoints: options.temporarilyUnlockedPolicyOutpoints,
171
213
  });
214
+ const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, 1))
215
+ .filter((entry) => isSpendableConfirmedFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex));
216
+ const availableFundingValueByKey = new Map(availableFundingUtxos.map((entry) => [
217
+ outpointKey({ txid: entry.txid, vout: entry.vout }),
218
+ btcNumberToSats(entry.amount),
219
+ ]));
220
+ const validationPlan = {
221
+ ...options.plan,
222
+ eligibleFundingOutpointKeys: new Set([
223
+ ...options.plan.eligibleFundingOutpointKeys,
224
+ ...availableFundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout })),
225
+ ]),
226
+ };
172
227
  const lockedBefore = await options.rpc.listLockUnspent(options.walletName);
173
228
  let temporaryBuilderLockedOutpoints = [];
174
229
  try {
@@ -186,7 +241,17 @@ export async function buildWalletMutationTransaction(options) {
186
241
  const lockedAfter = await options.rpc.listLockUnspent(options.walletName);
187
242
  temporaryBuilderLockedOutpoints = diffTemporaryLockedOutpoints(lockedBefore, lockedAfter);
188
243
  const decoded = await options.rpc.decodePsbt(funded.psbt);
189
- options.validateFundedDraft(decoded, funded, options.plan);
244
+ options.validateFundedDraft(decoded, funded, validationPlan);
245
+ if (options.state.proactiveReserveSats > 0) {
246
+ const remainingFundingValueSats = computeRemainingFundingValueSats({
247
+ transaction: decoded.tx,
248
+ fundingScriptPubKeyHex: options.plan.allowedFundingScriptPubKeyHex,
249
+ availableFundingValueByKey,
250
+ });
251
+ if (remainingFundingValueSats < BigInt(options.state.proactiveReserveSats)) {
252
+ throw new Error("wallet_mutation_insufficient_funding_after_reserve");
253
+ }
254
+ }
190
255
  const signed = await options.rpc.walletProcessPsbt(options.walletName, funded.psbt, true, "DEFAULT");
191
256
  const finalized = await options.rpc.finalizePsbt(signed.psbt, true);
192
257
  if (!finalized.complete || finalized.hex == null) {
@@ -254,7 +319,7 @@ export async function buildWalletMutationTransactionWithReserveFallback(options)
254
319
  }
255
320
  catch (error) {
256
321
  lastError = error;
257
- if (!isInsufficientFundsError(error) || attempt === options.reserveCandidates.length) {
322
+ if ((!isInsufficientFundsError(error) && !isReserveFloorFundingError(error)) || attempt === options.reserveCandidates.length) {
258
323
  throw error;
259
324
  }
260
325
  }
@@ -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 { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
12
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, 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";
@@ -189,7 +189,7 @@ function validateFundedDraft(decoded, funded, plan) {
189
189
  }
190
190
  }
191
191
  async function buildTransaction(options) {
192
- return buildWalletMutationTransaction({
192
+ return buildWalletMutationTransactionWithReserveFallback({
193
193
  rpc: options.rpc,
194
194
  walletName: options.walletName,
195
195
  state: options.state,
@@ -197,6 +197,7 @@ async function buildTransaction(options) {
197
197
  validateFundedDraft,
198
198
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
199
199
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
200
+ reserveCandidates: options.state.proactiveReserveOutpoints,
200
201
  });
201
202
  }
202
203
  function createDraftMutation(options) {
@@ -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 { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
10
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, 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";
@@ -248,28 +248,17 @@ function buildPlanForDomainOperation(options) {
248
248
  && entry.safe !== false);
249
249
  const outputs = [{ data: Buffer.from(options.opReturnData).toString("hex") }];
250
250
  if (options.anchorOutpoint === null) {
251
- const senderUtxo = options.allUtxos.find((entry) => entry.scriptPubKey === options.sender.scriptPubKeyHex
252
- && entry.confirmations >= 1
253
- && entry.spendable !== false
254
- && entry.safe !== false);
255
- if (senderUtxo === undefined) {
256
- throw new Error(`${options.errorPrefix}_sender_utxo_unavailable`);
257
- }
258
251
  return {
259
252
  sender: options.sender,
260
253
  changeAddress: options.state.funding.address,
261
- fixedInputs: [
262
- { txid: senderUtxo.txid, vout: senderUtxo.vout },
263
- ],
254
+ fixedInputs: [],
264
255
  outputs,
265
256
  changePosition: 1,
266
257
  expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
267
258
  expectedAnchorScriptHex: null,
268
259
  expectedAnchorValueSats: null,
269
260
  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 }))),
261
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
273
262
  errorPrefix: options.errorPrefix,
274
263
  };
275
264
  }
@@ -344,7 +333,7 @@ function validateFundedDraft(decoded, funded, plan) {
344
333
  }
345
334
  }
346
335
  async function buildTransaction(options) {
347
- return buildWalletMutationTransaction({
336
+ return buildWalletMutationTransactionWithReserveFallback({
348
337
  rpc: options.rpc,
349
338
  walletName: options.walletName,
350
339
  state: options.state,
@@ -352,6 +341,7 @@ async function buildTransaction(options) {
352
341
  validateFundedDraft,
353
342
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
354
343
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
344
+ reserveCandidates: options.state.proactiveReserveOutpoints,
355
345
  });
356
346
  }
357
347
  function createDraftMutation(options) {
@@ -5,7 +5,6 @@ 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";
9
8
  import { resolveWalletRuntimePathsForTesting, } from "../runtime.js";
10
9
  import { createDefaultWalletSecretProvider, } from "../state/provider.js";
11
10
  import { FIELD_FORMAT_BYTES, serializeDataUpdate, serializeFieldReg, } from "../cogop/index.js";
@@ -332,7 +331,6 @@ function validateFieldDraft(decoded, funded, plan) {
332
331
  }
333
332
  }
334
333
  async function buildFieldTransaction(options) {
335
- const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
336
334
  return buildWalletMutationTransactionWithReserveFallback({
337
335
  rpc: options.rpc,
338
336
  walletName: options.walletName,
@@ -341,7 +339,7 @@ async function buildFieldTransaction(options) {
341
339
  validateFundedDraft: validateFieldDraft,
342
340
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
343
341
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
344
- reserveCandidates,
342
+ reserveCandidates: options.state.proactiveReserveOutpoints,
345
343
  });
346
344
  }
347
345
  async function saveUpdatedState(options) {
@@ -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, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, 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";
@@ -459,20 +459,11 @@ function buildRegisterPlan(options) {
459
459
  anchorValueSats: options.anchorOutpoint === null ? null : options.anchorValueSats,
460
460
  });
461
461
  if (options.anchorOutpoint === null) {
462
- if (fundingUtxos.length === 0) {
463
- throw new Error("wallet_register_sender_utxo_unavailable");
464
- }
465
- const senderInput = fundingUtxos[0];
466
- const additionalFunding = fundingUtxos
467
- .slice(1)
468
- .map((entry) => ({ txid: entry.txid, vout: entry.vout }));
469
462
  return {
470
463
  registerKind: "root",
471
464
  sender: options.sender,
472
465
  changeAddress: options.state.funding.address,
473
- fixedInputs: [
474
- { txid: senderInput.txid, vout: senderInput.vout },
475
- ],
466
+ fixedInputs: [],
476
467
  outputs: rootOutputs.outputs,
477
468
  changePosition: rootOutputs.changePosition,
478
469
  expectedOpReturnScriptHex: rootOutputs.expectedOpReturnScriptHex,
@@ -483,7 +474,7 @@ function buildRegisterPlan(options) {
483
474
  expectedAnchorScriptHex: null,
484
475
  expectedAnchorValueSats: null,
485
476
  allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
486
- eligibleFundingOutpointKeys: new Set(additionalFunding.map((entry) => outpointKey(entry))),
477
+ eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey(entry))),
487
478
  };
488
479
  }
489
480
  const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint?.txid
@@ -550,7 +541,7 @@ function buildRegisterPlan(options) {
550
541
  };
551
542
  }
552
543
  async function buildRegisterTransaction(options) {
553
- return buildWalletMutationTransaction({
544
+ return buildWalletMutationTransactionWithReserveFallback({
554
545
  rpc: options.rpc,
555
546
  walletName: options.walletName,
556
547
  state: options.state,
@@ -558,6 +549,7 @@ async function buildRegisterTransaction(options) {
558
549
  validateFundedDraft,
559
550
  finalizeErrorCode: "wallet_register_finalize_failed",
560
551
  mempoolRejectPrefix: "wallet_register_mempool_rejected",
552
+ reserveCandidates: options.state.proactiveReserveOutpoints,
561
553
  });
562
554
  }
563
555
  async function reconcilePendingRegisterMutation(options) {
@@ -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 { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, 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";
@@ -218,7 +218,7 @@ function validateFundedDraft(decoded, funded, plan) {
218
218
  }
219
219
  }
220
220
  async function buildTransaction(options) {
221
- return buildWalletMutationTransaction({
221
+ return buildWalletMutationTransactionWithReserveFallback({
222
222
  rpc: options.rpc,
223
223
  walletName: options.walletName,
224
224
  state: options.state,
@@ -226,6 +226,7 @@ async function buildTransaction(options) {
226
226
  validateFundedDraft,
227
227
  finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
228
228
  mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
229
+ reserveCandidates: options.state.proactiveReserveOutpoints,
229
230
  });
230
231
  }
231
232
  function createDraftMutation(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "0.5.13",
3
+ "version": "0.5.14",
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",