@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.
- package/README.md +1 -1
- package/dist/bitcoind/bootstrap/getblock-archive.d.ts +23 -1
- package/dist/bitcoind/bootstrap/getblock-archive.js +127 -37
- package/dist/bitcoind/bootstrap.d.ts +1 -1
- package/dist/bitcoind/bootstrap.js +1 -1
- package/dist/bitcoind/client/managed-client.js +62 -40
- package/dist/bitcoind/testing.d.ts +1 -1
- package/dist/bitcoind/testing.js +1 -1
- package/dist/bitcoind/types.d.ts +10 -0
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/commands/sync.js +99 -1
- package/dist/cli/commands/wallet-mutation.js +39 -2
- package/dist/cli/context.js +20 -3
- package/dist/cli/mutation-success.d.ts +2 -0
- package/dist/cli/mutation-success.js +2 -0
- package/dist/cli/mutation-text-write.d.ts +2 -0
- package/dist/cli/mutation-text-write.js +7 -0
- package/dist/cli/output.js +22 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/wallet-format.d.ts +1 -1
- package/dist/cli/wallet-format.js +2 -2
- package/dist/wallet/coin-control.d.ts +3 -2
- package/dist/wallet/coin-control.js +127 -15
- package/dist/wallet/mining/runner.d.ts +72 -2
- package/dist/wallet/mining/runner.js +11 -5
- package/dist/wallet/tx/anchor.d.ts +2 -0
- package/dist/wallet/tx/anchor.js +65 -20
- package/dist/wallet/tx/cog.js +7 -17
- package/dist/wallet/tx/common.d.ts +18 -3
- package/dist/wallet/tx/common.js +144 -22
- package/dist/wallet/tx/domain-admin.js +5 -4
- package/dist/wallet/tx/domain-market.js +7 -17
- package/dist/wallet/tx/field.js +17 -8
- package/dist/wallet/tx/register.js +7 -15
- package/dist/wallet/tx/reputation.js +5 -4
- 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 (
|
|
613
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
614
614
|
throw new Error("wallet_mining_sender_input_mismatch");
|
|
615
615
|
}
|
|
616
|
-
if (
|
|
616
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 1) !== plan.allowedFundingScriptPubKeyHex
|
|
617
617
|
|| inputs[1]?.txid !== plan.expectedConflictOutpoint.txid
|
|
618
|
-
|| inputs[1]
|
|
618
|
+
|| inputs[1]?.vout !== plan.expectedConflictOutpoint.vout) {
|
|
619
619
|
throw new Error("wallet_mining_conflict_input_mismatch");
|
|
620
620
|
}
|
|
621
621
|
assertFundingInputsAfterFixedPrefix({
|
|
622
|
-
|
|
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;
|
package/dist/wallet/tx/anchor.js
CHANGED
|
@@ -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: [
|
|
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: [
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
package/dist/wallet/tx/cog.js
CHANGED
|
@@ -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,
|
|
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 (
|
|
300
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
312
301
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
313
302
|
}
|
|
314
303
|
assertFundingInputsAfterFixedPrefix({
|
|
315
|
-
|
|
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
|
|
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
|
-
|
|
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>;
|