@cogcoin/client 0.5.13 → 0.5.15

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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/bitcoind/bootstrap/getblock-archive.d.ts +23 -1
  3. package/dist/bitcoind/bootstrap/getblock-archive.js +127 -37
  4. package/dist/bitcoind/bootstrap.d.ts +1 -1
  5. package/dist/bitcoind/bootstrap.js +1 -1
  6. package/dist/bitcoind/client/managed-client.js +62 -40
  7. package/dist/bitcoind/testing.d.ts +1 -1
  8. package/dist/bitcoind/testing.js +1 -1
  9. package/dist/bitcoind/types.d.ts +10 -0
  10. package/dist/cli/commands/status.js +1 -1
  11. package/dist/cli/commands/sync.js +99 -1
  12. package/dist/cli/commands/wallet-mutation.js +39 -2
  13. package/dist/cli/context.js +20 -3
  14. package/dist/cli/mutation-success.d.ts +2 -0
  15. package/dist/cli/mutation-success.js +2 -0
  16. package/dist/cli/mutation-text-write.d.ts +2 -0
  17. package/dist/cli/mutation-text-write.js +7 -0
  18. package/dist/cli/output.js +22 -1
  19. package/dist/cli/types.d.ts +2 -0
  20. package/dist/cli/wallet-format.d.ts +1 -1
  21. package/dist/cli/wallet-format.js +2 -2
  22. package/dist/wallet/coin-control.d.ts +3 -2
  23. package/dist/wallet/coin-control.js +127 -15
  24. package/dist/wallet/mining/runner.d.ts +72 -2
  25. package/dist/wallet/mining/runner.js +11 -5
  26. package/dist/wallet/tx/anchor.d.ts +2 -0
  27. package/dist/wallet/tx/anchor.js +65 -20
  28. package/dist/wallet/tx/cog.js +7 -17
  29. package/dist/wallet/tx/common.d.ts +18 -3
  30. package/dist/wallet/tx/common.js +144 -22
  31. package/dist/wallet/tx/domain-admin.js +5 -4
  32. package/dist/wallet/tx/domain-market.js +7 -17
  33. package/dist/wallet/tx/field.js +17 -8
  34. package/dist/wallet/tx/register.js +7 -15
  35. package/dist/wallet/tx/reputation.js +5 -4
  36. package/package.json +1 -1
@@ -1,12 +1,12 @@
1
1
  import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
2
2
  import { createRpcClient } from "../../bitcoind/node.js";
3
3
  import type { ProgressOutputMode } from "../../bitcoind/types.js";
4
- import { type MutationSender, type WalletMutationRpcClient } from "../tx/common.js";
4
+ import { type FixedWalletInput, type MutationSender, type WalletMutationRpcClient } from "../tx/common.js";
5
5
  import { type WalletPrompter } from "../lifecycle.js";
6
6
  import { openWalletReadContext } from "../read/index.js";
7
7
  import { type WalletRuntimePaths } from "../runtime.js";
8
8
  import { type WalletSecretProvider } from "../state/provider.js";
9
- import type { MiningStateRecord } from "../types.js";
9
+ import type { MiningStateRecord, OutpointRecord, WalletStateV1 } from "../types.js";
10
10
  import type { MiningRuntimeStatusV1 } from "./types.js";
11
11
  type MiningRpcClient = WalletMutationRpcClient & {
12
12
  getBlockchainInfo(): Promise<{
@@ -64,6 +64,21 @@ type MiningRpcClient = WalletMutationRpcClient & {
64
64
  sendRawTransaction(hex: string): Promise<string>;
65
65
  saveMempool?(): Promise<null>;
66
66
  };
67
+ interface MiningCandidate {
68
+ domainId: number;
69
+ domainName: string;
70
+ localIndex: number;
71
+ sender: MutationSender;
72
+ anchorOutpoint: OutpointRecord;
73
+ sentence: string;
74
+ encodedSentenceBytes: Uint8Array;
75
+ bip39WordIndices: number[];
76
+ bip39Words: readonly string[];
77
+ canonicalBlend: bigint;
78
+ referencedBlockHashDisplay: string;
79
+ referencedBlockHashInternal: Uint8Array;
80
+ targetBlockHeight: number;
81
+ }
67
82
  interface RunnerDependencies {
68
83
  openReadContext?: typeof openWalletReadContext;
69
84
  attachService?: typeof attachOrStartManagedBitcoindService;
@@ -104,6 +119,61 @@ export interface MiningStartResult {
104
119
  started: boolean;
105
120
  snapshot: MiningRuntimeStatusV1 | null;
106
121
  }
122
+ declare function createMiningPlan(options: {
123
+ state: WalletStateV1;
124
+ candidate: MiningCandidate;
125
+ conflictOutpoint: OutpointRecord;
126
+ allUtxos: Awaited<ReturnType<MiningRpcClient["listUnspent"]>>;
127
+ feeRateSatVb: number;
128
+ }): {
129
+ sender: MutationSender;
130
+ fixedInputs: FixedWalletInput[];
131
+ outputs: unknown[];
132
+ changeAddress: string;
133
+ changePosition: number;
134
+ expectedOpReturnScriptHex: string;
135
+ expectedAnchorScriptHex: string;
136
+ expectedAnchorValueSats: bigint;
137
+ allowedFundingScriptPubKeyHex: string;
138
+ eligibleFundingOutpointKeys: Set<string>;
139
+ expectedConflictOutpoint: OutpointRecord;
140
+ feeRateSatVb: number;
141
+ };
142
+ export declare function createMiningPlanForTesting(options: {
143
+ state: WalletStateV1;
144
+ candidate: {
145
+ domainId: number;
146
+ domainName: string;
147
+ localIndex: number;
148
+ sender: MutationSender;
149
+ anchorOutpoint: OutpointRecord;
150
+ sentence: string;
151
+ encodedSentenceBytes: Uint8Array;
152
+ bip39WordIndices: number[];
153
+ bip39Words: readonly string[];
154
+ canonicalBlend: bigint;
155
+ referencedBlockHashDisplay: string;
156
+ referencedBlockHashInternal: Uint8Array;
157
+ targetBlockHeight: number;
158
+ };
159
+ conflictOutpoint: OutpointRecord;
160
+ allUtxos: Awaited<ReturnType<MiningRpcClient["listUnspent"]>>;
161
+ feeRateSatVb: number;
162
+ }): {
163
+ sender: MutationSender;
164
+ fixedInputs: FixedWalletInput[];
165
+ outputs: unknown[];
166
+ changeAddress: string;
167
+ changePosition: number;
168
+ expectedOpReturnScriptHex: string;
169
+ expectedAnchorScriptHex: string;
170
+ expectedAnchorValueSats: bigint;
171
+ allowedFundingScriptPubKeyHex: string;
172
+ eligibleFundingOutpointKeys: Set<string>;
173
+ expectedConflictOutpoint: OutpointRecord;
174
+ feeRateSatVb: number;
175
+ };
176
+ export declare function validateMiningDraftForTesting(decoded: Awaited<ReturnType<MiningRpcClient["decodePsbt"]>>, funded: Awaited<ReturnType<MiningRpcClient["walletCreateFundedPsbt"]>>, plan: ReturnType<typeof createMiningPlan>): void;
107
177
  export declare function runForegroundMining(options: RunForegroundMiningOptions): Promise<void>;
108
178
  export declare function startBackgroundMining(options: StartBackgroundMiningOptions): Promise<MiningStartResult>;
109
179
  export declare function stopBackgroundMining(options: StopBackgroundMiningOptions): Promise<MiningRuntimeStatusV1 | null>;
@@ -8,7 +8,7 @@ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
8
8
  import { createRpcClient } from "../../bitcoind/node.js";
9
9
  import { COG_OPCODES, COG_PREFIX } from "../cogop/constants.js";
10
10
  import { extractOpReturnPayloadFromScriptHex } from "../tx/register.js";
11
- import { DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB, assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, buildWalletMutationTransaction, outpointKey as walletMutationOutpointKey, isAlreadyAcceptedError, isBroadcastUnknownError, reconcilePersistentPolicyLocks, saveWalletStatePreservingUnlock, } from "../tx/common.js";
11
+ import { DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB, assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, buildWalletMutationTransaction, getDecodedInputScriptPubKeyHex, outpointKey as walletMutationOutpointKey, isAlreadyAcceptedError, isBroadcastUnknownError, reconcilePersistentPolicyLocks, saveWalletStatePreservingUnlock, } from "../tx/common.js";
12
12
  import { acquireFileLock } from "../fs/lock.js";
13
13
  import { loadOrAutoUnlockWalletState } from "../lifecycle.js";
14
14
  import { isMineableWalletDomain, openWalletReadContext, } from "../read/index.js";
@@ -610,16 +610,16 @@ function validateMiningDraft(decoded, funded, plan) {
610
610
  throw new Error("wallet_mining_missing_inputs");
611
611
  }
612
612
  assertFixedInputPrefixMatches(inputs, plan.fixedInputs, "wallet_mining_missing_inputs");
613
- if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
613
+ if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
614
614
  throw new Error("wallet_mining_sender_input_mismatch");
615
615
  }
616
- if (inputs[1]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex
616
+ if (getDecodedInputScriptPubKeyHex(decoded, 1) !== plan.allowedFundingScriptPubKeyHex
617
617
  || inputs[1]?.txid !== plan.expectedConflictOutpoint.txid
618
- || inputs[1].vout !== plan.expectedConflictOutpoint.vout) {
618
+ || inputs[1]?.vout !== plan.expectedConflictOutpoint.vout) {
619
619
  throw new Error("wallet_mining_conflict_input_mismatch");
620
620
  }
621
621
  assertFundingInputsAfterFixedPrefix({
622
- inputs,
622
+ decoded,
623
623
  fixedInputs: plan.fixedInputs,
624
624
  allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
625
625
  eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
@@ -650,6 +650,12 @@ async function buildMiningTransaction(options) {
650
650
  feeRate: options.plan.feeRateSatVb,
651
651
  });
652
652
  }
653
+ export function createMiningPlanForTesting(options) {
654
+ return createMiningPlan(options);
655
+ }
656
+ export function validateMiningDraftForTesting(decoded, funded, plan) {
657
+ validateMiningDraft(decoded, funded, plan);
658
+ }
653
659
  function resolveEligibleAnchoredRoots(context) {
654
660
  const state = context.localState.state;
655
661
  const model = context.model;
@@ -18,6 +18,7 @@ interface WalletAnchorRpcClient extends WalletMutationRpcClient {
18
18
  export interface AnchorDomainOptions {
19
19
  domainName: string;
20
20
  foundingMessageText?: string | null;
21
+ promptForFoundingMessageWhenMissing?: boolean;
21
22
  dataDir: string;
22
23
  databasePath: string;
23
24
  provider?: WalletSecretProvider;
@@ -36,6 +37,7 @@ export interface AnchorDomainResult {
36
37
  dedicatedIndex: number;
37
38
  status: "live" | "confirmed";
38
39
  reusedExisting: boolean;
40
+ foundingMessageText?: string | null;
39
41
  }
40
42
  export interface ClearPendingAnchorOptions {
41
43
  domainName: string;
@@ -5,12 +5,11 @@ 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";
12
11
  import { openWalletReadContext } from "../read/index.js";
13
- import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, getDecodedInputScriptPubKeyHex, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, inputMatchesOutpoint, } from "./common.js";
12
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, findSpendableFundingInputsFromTransaction, getDecodedInputScriptPubKeyHex, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, inputMatchesOutpoint, } from "./common.js";
14
13
  import { confirmYesNo } from "./confirm.js";
15
14
  const ACTIVE_FAMILY_STATUSES = new Set([
16
15
  "draft",
@@ -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",
@@ -524,6 +551,12 @@ function buildTx2Plan(options) {
524
551
  }
525
552
  const fundingUtxos = sortUtxos(options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
526
553
  && isSpendableConfirmedUtxo(entry)));
554
+ const tx1FundingChangeInputs = findSpendableFundingInputsFromTransaction({
555
+ allUtxos: options.allUtxos,
556
+ txid: tx1Txid,
557
+ fundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
558
+ minConf: 0,
559
+ });
527
560
  const foundingPayload = options.operation.foundingMessagePayloadHex === null
528
561
  ? undefined
529
562
  : Buffer.from(options.operation.foundingMessagePayloadHex, "hex");
@@ -535,7 +568,10 @@ function buildTx2Plan(options) {
535
568
  address: options.operation.targetIdentity.address,
536
569
  },
537
570
  changeAddress: options.state.funding.address,
538
- fixedInputs: [{ txid: provisional.txid, vout: provisional.vout }],
571
+ fixedInputs: [
572
+ { txid: provisional.txid, vout: provisional.vout },
573
+ ...tx1FundingChangeInputs,
574
+ ],
539
575
  outputs: [
540
576
  { data: Buffer.from(opReturnData).toString("hex") },
541
577
  { [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
@@ -563,7 +599,7 @@ function validateTx1Draft(decoded, funded, plan) {
563
599
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
564
600
  }
565
601
  assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
566
- const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(inputs[0]);
602
+ const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(decoded, 0);
567
603
  if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex) {
568
604
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
569
605
  }
@@ -573,7 +609,7 @@ function validateTx1Draft(decoded, funded, plan) {
573
609
  }
574
610
  }
575
611
  assertFundingInputsAfterFixedPrefix({
576
- inputs,
612
+ decoded,
577
613
  fixedInputs: plan.fixedInputs,
578
614
  allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
579
615
  eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
@@ -617,13 +653,13 @@ function validateTx2Draft(decoded, funded, plan) {
617
653
  throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
618
654
  }
619
655
  assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_provisional_input_mismatch`);
620
- const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(inputs[0]);
656
+ const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(decoded, 0);
621
657
  if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex
622
658
  || !inputMatchesOutpoint(inputs[0], plan.requiredProvisionalOutpoint)) {
623
659
  throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
624
660
  }
625
661
  assertFundingInputsAfterFixedPrefix({
626
- inputs,
662
+ decoded,
627
663
  fixedInputs: plan.fixedInputs,
628
664
  allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
629
665
  eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
@@ -653,7 +689,6 @@ function validateTx2Draft(decoded, funded, plan) {
653
689
  }
654
690
  }
655
691
  async function buildTx1(options) {
656
- const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
657
692
  return buildWalletMutationTransactionWithReserveFallback({
658
693
  rpc: options.rpc,
659
694
  walletName: options.walletName,
@@ -662,11 +697,10 @@ async function buildTx1(options) {
662
697
  validateFundedDraft: validateTx1Draft,
663
698
  finalizeErrorCode: "wallet_anchor_tx1_finalize_failed",
664
699
  mempoolRejectPrefix: "wallet_anchor_tx1_mempool_rejected",
665
- reserveCandidates,
700
+ reserveCandidates: options.state.proactiveReserveOutpoints,
666
701
  });
667
702
  }
668
703
  async function buildTx2(options) {
669
- const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
670
704
  return buildWalletMutationTransactionWithReserveFallback({
671
705
  rpc: options.rpc,
672
706
  walletName: options.walletName,
@@ -675,7 +709,8 @@ async function buildTx2(options) {
675
709
  validateFundedDraft: validateTx2Draft,
676
710
  finalizeErrorCode: "wallet_anchor_tx2_finalize_failed",
677
711
  mempoolRejectPrefix: "wallet_anchor_tx2_mempool_rejected",
678
- reserveCandidates,
712
+ availableFundingMinConf: 0,
713
+ reserveCandidates: options.state.proactiveReserveOutpoints,
679
714
  });
680
715
  }
681
716
  async function relockAnchorOutpoint(rpc, walletName, outpoint) {
@@ -1107,6 +1142,7 @@ async function submitTx2(options) {
1107
1142
  dedicatedIndex: options.operation.targetIdentity.localIndex,
1108
1143
  status: finalStatus,
1109
1144
  reusedExisting: false,
1145
+ foundingMessageText: options.operation.foundingMessageText,
1110
1146
  };
1111
1147
  }
1112
1148
  function ensureSameTipHeight(context, bestHeight, errorCode) {
@@ -1131,7 +1167,11 @@ export async function anchorDomain(options) {
1131
1167
  paths,
1132
1168
  reason: "wallet-anchor",
1133
1169
  });
1134
- const message = await encodeFoundingMessage(options.foundingMessageText ?? null);
1170
+ const message = await resolveFoundingMessage({
1171
+ foundingMessageText: options.foundingMessageText,
1172
+ promptForFoundingMessageWhenMissing: options.promptForFoundingMessageWhenMissing,
1173
+ prompter: options.prompter,
1174
+ });
1135
1175
  const readContext = await (options.openReadContext ?? openWalletReadContext)({
1136
1176
  dataDir: options.dataDir,
1137
1177
  databasePath: options.databasePath,
@@ -1144,6 +1184,9 @@ export async function anchorDomain(options) {
1144
1184
  const initialFamily = createDraftAnchorFamily(operation, nowUnixMs);
1145
1185
  const existingFamily = findAnchorFamilyByIntent(operation.state, initialFamily.intentFingerprintHex);
1146
1186
  const conflictingFamily = findActiveAnchorFamilyByDomain(operation.state, normalizedDomainName);
1187
+ if (existingFamily === null && isClearableReservedAnchorFamily(conflictingFamily)) {
1188
+ throw new Error(`wallet_anchor_clear_pending_first_${conflictingFamily.domainName}`);
1189
+ }
1147
1190
  if (existingFamily === null && conflictingFamily !== null) {
1148
1191
  throw new Error("wallet_anchor_prior_family_unresolved");
1149
1192
  }
@@ -1185,6 +1228,7 @@ export async function anchorDomain(options) {
1185
1228
  dedicatedIndex: reconciled.family.reservedDedicatedIndex ?? existingTargetIdentity.localIndex,
1186
1229
  status: reconciled.resolution,
1187
1230
  reusedExisting: true,
1231
+ foundingMessageText: reconciled.family.foundingMessageText,
1188
1232
  };
1189
1233
  }
1190
1234
  if (reconciled.resolution === "repair-required") {
@@ -1336,6 +1380,7 @@ export async function anchorDomain(options) {
1336
1380
  return {
1337
1381
  ...result,
1338
1382
  reusedExisting: resumedExisting,
1383
+ foundingMessageText: result.foundingMessageText ?? operation.foundingMessageText,
1339
1384
  };
1340
1385
  }
1341
1386
  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, getDecodedInputScriptPubKeyHex, 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
  }
@@ -308,11 +297,11 @@ function validateFundedDraft(decoded, funded, plan) {
308
297
  throw new Error(`${plan.errorPrefix}_missing_sender_input`);
309
298
  }
310
299
  assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
311
- if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
300
+ if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
312
301
  throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
313
302
  }
314
303
  assertFundingInputsAfterFixedPrefix({
315
- inputs,
304
+ decoded,
316
305
  fixedInputs: plan.fixedInputs,
317
306
  allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
318
307
  eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
@@ -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) {
@@ -1,4 +1,4 @@
1
- import type { RpcDecodedPsbt, RpcFinalizePsbtResult, RpcListUnspentEntry, RpcLockedUnspent, RpcTestMempoolAcceptResult, RpcTransaction, RpcVin, RpcWalletCreateFundedPsbtResult, RpcWalletProcessPsbtResult } from "../../bitcoind/types.js";
1
+ import type { RpcDecodedPsbt, RpcFinalizePsbtResult, RpcListUnspentEntry, RpcLockedUnspent, RpcTestMempoolAcceptResult, RpcTransaction, RpcVin, RpcWalletCreateFundedPsbtResult, RpcWalletTransaction, RpcWalletProcessPsbtResult } from "../../bitcoind/types.js";
2
2
  import { type WalletSecretProvider } from "../state/provider.js";
3
3
  import type { OutpointRecord, PendingMutationRecord, PendingMutationStatus, WalletStateV1 } from "../types.js";
4
4
  import type { WalletReadContext } from "../read/index.js";
@@ -14,12 +14,15 @@ export interface WalletMutationRpcClient {
14
14
  listUnspent(walletName: string, minConf?: number): Promise<RpcListUnspentEntry[]>;
15
15
  listLockUnspent(walletName: string): Promise<RpcLockedUnspent[]>;
16
16
  lockUnspent(walletName: string, unlock: boolean, outputs: RpcLockedUnspent[]): Promise<boolean>;
17
+ getTransaction?(walletName: string, txid: string): Promise<RpcWalletTransaction>;
17
18
  walletCreateFundedPsbt(walletName: string, inputs: Array<{
18
19
  txid: string;
19
20
  vout: number;
20
21
  }>, outputs: unknown[], locktime: number, options: Record<string, unknown>, bip32Derivs?: boolean): Promise<RpcWalletCreateFundedPsbtResult>;
21
22
  decodePsbt(psbt: string): Promise<RpcDecodedPsbt>;
23
+ walletPassphrase(walletName: string, passphrase: string, timeoutSeconds: number): Promise<null>;
22
24
  walletProcessPsbt(walletName: string, psbt: string, sign?: boolean, sighashType?: string): Promise<RpcWalletProcessPsbtResult>;
25
+ walletLock(walletName: string): Promise<null>;
23
26
  finalizePsbt(psbt: string, extract?: boolean): Promise<RpcFinalizePsbtResult>;
24
27
  decodeRawTransaction(hex: string): Promise<RpcTransaction>;
25
28
  testMempoolAccept(rawTransactions: string[]): Promise<RpcTestMempoolAcceptResult[]>;
@@ -44,6 +47,12 @@ export declare function saveWalletStatePreservingUnlock(options: {
44
47
  }): Promise<void>;
45
48
  export declare function formatCogAmount(value: bigint): string;
46
49
  export declare function outpointKey(outpoint: OutpointRecord): string;
50
+ export declare function findSpendableFundingInputsFromTransaction(options: {
51
+ allUtxos: RpcListUnspentEntry[];
52
+ txid: string;
53
+ fundingScriptPubKeyHex: string;
54
+ minConf?: number;
55
+ }): FixedWalletInput[];
47
56
  export declare function updateMutationRecord(mutation: PendingMutationRecord, status: PendingMutationStatus, nowUnixMs: number, options?: {
48
57
  attemptedTxid?: string | null;
49
58
  attemptedWtxid?: string | null;
@@ -51,12 +60,12 @@ export declare function updateMutationRecord(mutation: PendingMutationRecord, st
51
60
  }): PendingMutationRecord;
52
61
  export declare function unlockTemporaryBuilderLocks(rpc: Pick<WalletMutationRpcClient, "lockUnspent">, walletName: string, outpoints: OutpointRecord[]): Promise<void>;
53
62
  export declare function diffTemporaryLockedOutpoints(before: RpcLockedUnspent[], after: RpcLockedUnspent[]): OutpointRecord[];
54
- export declare function getDecodedInputScriptPubKeyHex(input: RpcVin): string | null;
55
63
  export declare function getDecodedInputVout(input: RpcVin): number | null;
64
+ export declare function getDecodedInputScriptPubKeyHex(decoded: RpcDecodedPsbt, inputIndex: number): string | null;
56
65
  export declare function inputMatchesOutpoint(input: RpcVin, outpoint: OutpointRecord): boolean;
57
66
  export declare function assertFixedInputPrefixMatches(inputs: RpcVin[], fixedInputs: FixedWalletInput[], errorCode: string): void;
58
67
  export declare function assertFundingInputsAfterFixedPrefix(options: {
59
- inputs: RpcVin[];
68
+ decoded: RpcDecodedPsbt;
60
69
  fixedInputs: FixedWalletInput[];
61
70
  allowedFundingScriptPubKeyHex: string;
62
71
  eligibleFundingOutpointKeys: Set<string>;
@@ -95,11 +104,14 @@ export declare function buildWalletMutationTransaction<TPlan>(options: {
95
104
  outputs: unknown[];
96
105
  changeAddress: string;
97
106
  changePosition: number;
107
+ allowedFundingScriptPubKeyHex: string;
108
+ eligibleFundingOutpointKeys: Set<string>;
98
109
  };
99
110
  validateFundedDraft(decoded: RpcDecodedPsbt, funded: RpcWalletCreateFundedPsbtResult, plan: TPlan): void;
100
111
  finalizeErrorCode: string;
101
112
  mempoolRejectPrefix: string;
102
113
  feeRate?: number;
114
+ availableFundingMinConf?: number;
103
115
  temporarilyUnlockedPolicyOutpoints?: readonly OutpointRecord[];
104
116
  }): Promise<BuiltWalletMutationTransaction>;
105
117
  export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>(options: {
@@ -111,10 +123,13 @@ export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>
111
123
  outputs: unknown[];
112
124
  changeAddress: string;
113
125
  changePosition: number;
126
+ allowedFundingScriptPubKeyHex: string;
127
+ eligibleFundingOutpointKeys: Set<string>;
114
128
  };
115
129
  validateFundedDraft(decoded: RpcDecodedPsbt, funded: RpcWalletCreateFundedPsbtResult, plan: TPlan): void;
116
130
  finalizeErrorCode: string;
117
131
  mempoolRejectPrefix: string;
118
132
  feeRate?: number;
133
+ availableFundingMinConf?: number;
119
134
  reserveCandidates: readonly OutpointRecord[];
120
135
  }): Promise<BuiltWalletMutationTransaction>;