@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.
- 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/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 +1 -1
- package/dist/wallet/coin-control.js +56 -15
- package/dist/wallet/tx/anchor.d.ts +2 -0
- package/dist/wallet/tx/anchor.js +49 -14
- package/dist/wallet/tx/cog.js +5 -15
- package/dist/wallet/tx/common.d.ts +4 -0
- package/dist/wallet/tx/common.js +67 -2
- package/dist/wallet/tx/domain-admin.js +3 -2
- package/dist/wallet/tx/domain-market.js +5 -15
- package/dist/wallet/tx/field.js +1 -3
- package/dist/wallet/tx/register.js +5 -13
- package/dist/wallet/tx/reputation.js +3 -2
- package/package.json +1 -1
package/dist/wallet/tx/anchor.js
CHANGED
|
@@ -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: [
|
|
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
|
|
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 {
|
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, 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
|
|
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;
|
package/dist/wallet/tx/common.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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) {
|
package/dist/wallet/tx/field.js
CHANGED
|
@@ -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,
|
|
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(
|
|
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
|
|
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,
|
|
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
|
|
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