@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
package/dist/wallet/tx/common.js
CHANGED
|
@@ -5,6 +5,15 @@ 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
|
+
const MANAGED_CORE_WALLET_SIGNING_UNLOCK_TIMEOUT_SECONDS = 10;
|
|
9
|
+
function btcNumberToSats(value) {
|
|
10
|
+
return BigInt(Math.round(value * 100_000_000));
|
|
11
|
+
}
|
|
12
|
+
function valueToSats(value) {
|
|
13
|
+
return typeof value === "string"
|
|
14
|
+
? BigInt(Math.round(Number(value) * 100_000_000))
|
|
15
|
+
: btcNumberToSats(value);
|
|
16
|
+
}
|
|
8
17
|
function createUnlockSessionState(state, unlockUntilUnixMs, nowUnixMs) {
|
|
9
18
|
return {
|
|
10
19
|
schemaVersion: 1,
|
|
@@ -40,6 +49,24 @@ export function formatCogAmount(value) {
|
|
|
40
49
|
export function outpointKey(outpoint) {
|
|
41
50
|
return `${outpoint.txid}:${outpoint.vout}`;
|
|
42
51
|
}
|
|
52
|
+
function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex, minConf) {
|
|
53
|
+
return entry.scriptPubKey === fundingScriptPubKeyHex
|
|
54
|
+
&& entry.confirmations >= minConf
|
|
55
|
+
&& entry.spendable !== false
|
|
56
|
+
&& entry.safe !== false;
|
|
57
|
+
}
|
|
58
|
+
export function findSpendableFundingInputsFromTransaction(options) {
|
|
59
|
+
const minConf = options.minConf ?? 0;
|
|
60
|
+
return options.allUtxos
|
|
61
|
+
.filter((entry) => entry.txid === options.txid
|
|
62
|
+
&& isSpendableFundingUtxo(entry, options.fundingScriptPubKeyHex, minConf))
|
|
63
|
+
.sort((left, right) => left.vout - right.vout
|
|
64
|
+
|| left.txid.localeCompare(right.txid))
|
|
65
|
+
.map((entry) => ({
|
|
66
|
+
txid: entry.txid,
|
|
67
|
+
vout: entry.vout,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
43
70
|
export function updateMutationRecord(mutation, status, nowUnixMs, options = {}) {
|
|
44
71
|
return {
|
|
45
72
|
...mutation,
|
|
@@ -65,12 +92,33 @@ export function diffTemporaryLockedOutpoints(before, after) {
|
|
|
65
92
|
vout: entry.vout,
|
|
66
93
|
}));
|
|
67
94
|
}
|
|
68
|
-
export function getDecodedInputScriptPubKeyHex(input) {
|
|
69
|
-
return input.prevout?.scriptPubKey?.hex ?? null;
|
|
70
|
-
}
|
|
71
95
|
export function getDecodedInputVout(input) {
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
return typeof input.vout === "number" ? input.vout : null;
|
|
97
|
+
}
|
|
98
|
+
export function getDecodedInputScriptPubKeyHex(decoded, inputIndex) {
|
|
99
|
+
const input = decoded.tx.vin[inputIndex];
|
|
100
|
+
if (input === undefined) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const prevoutScriptPubKeyHex = input.prevout?.scriptPubKey?.hex;
|
|
104
|
+
if (typeof prevoutScriptPubKeyHex === "string" && prevoutScriptPubKeyHex.length > 0) {
|
|
105
|
+
return prevoutScriptPubKeyHex;
|
|
106
|
+
}
|
|
107
|
+
const psbtInput = decoded.inputs?.[inputIndex];
|
|
108
|
+
const witnessScriptPubKeyHex = psbtInput?.witness_utxo?.scriptPubKey?.hex;
|
|
109
|
+
if (typeof witnessScriptPubKeyHex === "string" && witnessScriptPubKeyHex.length > 0) {
|
|
110
|
+
return witnessScriptPubKeyHex;
|
|
111
|
+
}
|
|
112
|
+
const vout = getDecodedInputVout(input);
|
|
113
|
+
if (vout === null) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const nonWitnessScriptPubKeyHex = psbtInput?.non_witness_utxo?.vout
|
|
117
|
+
.find((output) => output.n === vout)
|
|
118
|
+
?.scriptPubKey?.hex;
|
|
119
|
+
return typeof nonWitnessScriptPubKeyHex === "string" && nonWitnessScriptPubKeyHex.length > 0
|
|
120
|
+
? nonWitnessScriptPubKeyHex
|
|
121
|
+
: null;
|
|
74
122
|
}
|
|
75
123
|
export function inputMatchesOutpoint(input, outpoint) {
|
|
76
124
|
return input.txid === outpoint.txid && getDecodedInputVout(input) === outpoint.vout;
|
|
@@ -86,9 +134,9 @@ export function assertFixedInputPrefixMatches(inputs, fixedInputs, errorCode) {
|
|
|
86
134
|
}
|
|
87
135
|
}
|
|
88
136
|
export function assertFundingInputsAfterFixedPrefix(options) {
|
|
89
|
-
for (let index = options.fixedInputs.length; index < options.
|
|
90
|
-
const input = options.
|
|
91
|
-
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(
|
|
137
|
+
for (let index = options.fixedInputs.length; index < options.decoded.tx.vin.length; index += 1) {
|
|
138
|
+
const input = options.decoded.tx.vin[index];
|
|
139
|
+
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(options.decoded, index);
|
|
92
140
|
const vout = getDecodedInputVout(input);
|
|
93
141
|
if (scriptPubKeyHex !== options.allowedFundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
|
|
94
142
|
throw new Error(options.errorCode);
|
|
@@ -132,6 +180,34 @@ export function isInsufficientFundsError(error) {
|
|
|
132
180
|
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
133
181
|
return message.includes("insufficient funds");
|
|
134
182
|
}
|
|
183
|
+
function isReserveFloorFundingError(error) {
|
|
184
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
185
|
+
return message.includes("insufficient_funding_after_reserve");
|
|
186
|
+
}
|
|
187
|
+
function computeRemainingFundingValueSats(options) {
|
|
188
|
+
let remaining = 0n;
|
|
189
|
+
for (const value of options.availableFundingValueByKey.values()) {
|
|
190
|
+
remaining += value;
|
|
191
|
+
}
|
|
192
|
+
for (const [index, input] of options.decoded.tx.vin.entries()) {
|
|
193
|
+
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(options.decoded, index);
|
|
194
|
+
const vout = getDecodedInputVout(input);
|
|
195
|
+
if (scriptPubKeyHex !== options.fundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
remaining -= options.availableFundingValueByKey.get(outpointKey({
|
|
199
|
+
txid: input.txid,
|
|
200
|
+
vout,
|
|
201
|
+
})) ?? 0n;
|
|
202
|
+
}
|
|
203
|
+
for (const output of options.decoded.tx.vout) {
|
|
204
|
+
if (output.scriptPubKey?.hex !== options.fundingScriptPubKeyHex) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
remaining += valueToSats(output.value);
|
|
208
|
+
}
|
|
209
|
+
return remaining;
|
|
210
|
+
}
|
|
135
211
|
export function assertWalletMutationContextReady(context, errorPrefix) {
|
|
136
212
|
if (context.localState.availability === "uninitialized") {
|
|
137
213
|
throw new Error("wallet_uninitialized");
|
|
@@ -169,6 +245,20 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
169
245
|
fixedInputs: options.plan.fixedInputs,
|
|
170
246
|
temporarilyUnlockedOutpoints: options.temporarilyUnlockedPolicyOutpoints,
|
|
171
247
|
});
|
|
248
|
+
const availableFundingMinConf = options.availableFundingMinConf ?? 1;
|
|
249
|
+
const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, availableFundingMinConf))
|
|
250
|
+
.filter((entry) => isSpendableFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex, availableFundingMinConf));
|
|
251
|
+
const availableFundingValueByKey = new Map(availableFundingUtxos.map((entry) => [
|
|
252
|
+
outpointKey({ txid: entry.txid, vout: entry.vout }),
|
|
253
|
+
btcNumberToSats(entry.amount),
|
|
254
|
+
]));
|
|
255
|
+
const validationPlan = {
|
|
256
|
+
...options.plan,
|
|
257
|
+
eligibleFundingOutpointKeys: new Set([
|
|
258
|
+
...options.plan.eligibleFundingOutpointKeys,
|
|
259
|
+
...availableFundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout })),
|
|
260
|
+
]),
|
|
261
|
+
};
|
|
172
262
|
const lockedBefore = await options.rpc.listLockUnspent(options.walletName);
|
|
173
263
|
let temporaryBuilderLockedOutpoints = [];
|
|
174
264
|
try {
|
|
@@ -186,17 +276,36 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
186
276
|
const lockedAfter = await options.rpc.listLockUnspent(options.walletName);
|
|
187
277
|
temporaryBuilderLockedOutpoints = diffTemporaryLockedOutpoints(lockedBefore, lockedAfter);
|
|
188
278
|
const decoded = await options.rpc.decodePsbt(funded.psbt);
|
|
189
|
-
options.validateFundedDraft(decoded, funded,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
279
|
+
options.validateFundedDraft(decoded, funded, validationPlan);
|
|
280
|
+
if (options.state.proactiveReserveSats > 0) {
|
|
281
|
+
const remainingFundingValueSats = computeRemainingFundingValueSats({
|
|
282
|
+
decoded,
|
|
283
|
+
fundingScriptPubKeyHex: options.plan.allowedFundingScriptPubKeyHex,
|
|
284
|
+
availableFundingValueByKey,
|
|
285
|
+
});
|
|
286
|
+
if (remainingFundingValueSats < BigInt(options.state.proactiveReserveSats)) {
|
|
287
|
+
throw new Error("wallet_mutation_insufficient_funding_after_reserve");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
await options.rpc.walletPassphrase(options.walletName, options.state.managedCoreWallet.internalPassphrase, MANAGED_CORE_WALLET_SIGNING_UNLOCK_TIMEOUT_SECONDS);
|
|
291
|
+
let signed;
|
|
292
|
+
let finalized;
|
|
293
|
+
let decodedRaw;
|
|
294
|
+
try {
|
|
295
|
+
signed = await options.rpc.walletProcessPsbt(options.walletName, funded.psbt, true, "DEFAULT");
|
|
296
|
+
finalized = await options.rpc.finalizePsbt(signed.psbt, true);
|
|
297
|
+
if (!finalized.complete || finalized.hex == null) {
|
|
298
|
+
throw new Error(options.finalizeErrorCode);
|
|
299
|
+
}
|
|
300
|
+
decodedRaw = await options.rpc.decodeRawTransaction(finalized.hex);
|
|
301
|
+
const mempoolResult = await options.rpc.testMempoolAccept([finalized.hex]);
|
|
302
|
+
const accepted = mempoolResult[0];
|
|
303
|
+
if (accepted == null || !accepted.allowed) {
|
|
304
|
+
throw new Error(`${options.mempoolRejectPrefix}_${accepted?.["reject-reason"] ?? "unknown"}`);
|
|
305
|
+
}
|
|
194
306
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const accepted = mempoolResult[0];
|
|
198
|
-
if (accepted == null || !accepted.allowed) {
|
|
199
|
-
throw new Error(`${options.mempoolRejectPrefix}_${accepted?.["reject-reason"] ?? "unknown"}`);
|
|
307
|
+
finally {
|
|
308
|
+
await options.rpc.walletLock(options.walletName).catch(() => undefined);
|
|
200
309
|
}
|
|
201
310
|
if ((options.temporarilyUnlockedPolicyOutpoints?.length ?? 0) > 0) {
|
|
202
311
|
await reconcilePersistentPolicyLocks({
|
|
@@ -230,31 +339,44 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
230
339
|
}
|
|
231
340
|
}
|
|
232
341
|
export async function buildWalletMutationTransactionWithReserveFallback(options) {
|
|
342
|
+
const preflightReconciled = options.reserveCandidates.length > 0
|
|
343
|
+
? null
|
|
344
|
+
: await reconcileWalletCoinControlLocks({
|
|
345
|
+
rpc: options.rpc,
|
|
346
|
+
walletName: options.walletName,
|
|
347
|
+
state: options.state,
|
|
348
|
+
fixedInputs: options.plan.fixedInputs,
|
|
349
|
+
});
|
|
350
|
+
const effectiveState = preflightReconciled?.state ?? options.state;
|
|
351
|
+
const reserveCandidates = options.reserveCandidates.length > 0
|
|
352
|
+
? [...options.reserveCandidates]
|
|
353
|
+
: [...effectiveState.proactiveReserveOutpoints];
|
|
233
354
|
let unlockedReserveOutpoints = [];
|
|
234
355
|
let lastError = null;
|
|
235
|
-
for (let attempt = 0; attempt <=
|
|
356
|
+
for (let attempt = 0; attempt <= reserveCandidates.length; attempt += 1) {
|
|
236
357
|
if (attempt > 0) {
|
|
237
358
|
unlockedReserveOutpoints = [
|
|
238
359
|
...unlockedReserveOutpoints,
|
|
239
|
-
|
|
360
|
+
reserveCandidates[attempt - 1],
|
|
240
361
|
];
|
|
241
362
|
}
|
|
242
363
|
try {
|
|
243
364
|
return await buildWalletMutationTransaction({
|
|
244
365
|
rpc: options.rpc,
|
|
245
366
|
walletName: options.walletName,
|
|
246
|
-
state:
|
|
367
|
+
state: effectiveState,
|
|
247
368
|
plan: options.plan,
|
|
248
369
|
validateFundedDraft: options.validateFundedDraft,
|
|
249
370
|
finalizeErrorCode: options.finalizeErrorCode,
|
|
250
371
|
mempoolRejectPrefix: options.mempoolRejectPrefix,
|
|
251
372
|
feeRate: options.feeRate,
|
|
373
|
+
availableFundingMinConf: options.availableFundingMinConf,
|
|
252
374
|
temporarilyUnlockedPolicyOutpoints: unlockedReserveOutpoints,
|
|
253
375
|
});
|
|
254
376
|
}
|
|
255
377
|
catch (error) {
|
|
256
378
|
lastError = error;
|
|
257
|
-
if (!isInsufficientFundsError(error) || attempt ===
|
|
379
|
+
if ((!isInsufficientFundsError(error) && !isReserveFloorFundingError(error)) || attempt === reserveCandidates.length) {
|
|
258
380
|
throw error;
|
|
259
381
|
}
|
|
260
382
|
}
|
|
@@ -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, getDecodedInputScriptPubKeyHex, 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";
|
|
@@ -156,11 +156,11 @@ function validateFundedDraft(decoded, funded, plan) {
|
|
|
156
156
|
throw new Error(`${plan.errorPrefix}_missing_sender_input`);
|
|
157
157
|
}
|
|
158
158
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
159
|
-
if (
|
|
159
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
160
160
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
161
161
|
}
|
|
162
162
|
assertFundingInputsAfterFixedPrefix({
|
|
163
|
-
|
|
163
|
+
decoded,
|
|
164
164
|
fixedInputs: plan.fixedInputs,
|
|
165
165
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
166
166
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -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, 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 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
|
}
|
|
@@ -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) {
|
package/dist/wallet/tx/field.js
CHANGED
|
@@ -5,13 +5,12 @@ 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";
|
|
12
11
|
import { validateFieldName } from "../cogop/validate-name.js";
|
|
13
12
|
import { findDomainField, openWalletReadContext, } from "../read/index.js";
|
|
14
|
-
import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
13
|
+
import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, findSpendableFundingInputsFromTransaction, getDecodedInputScriptPubKeyHex, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
15
14
|
import { confirmTypedAcknowledgement as confirmSharedTypedAcknowledgement, confirmYesNo as confirmSharedYesNo, } from "./confirm.js";
|
|
16
15
|
import { getCanonicalIdentitySelector } from "./identity-selector.js";
|
|
17
16
|
import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
|
|
@@ -275,10 +274,19 @@ function buildFieldFamilyTx2Plan(options) {
|
|
|
275
274
|
&& entry.confirmations >= 1
|
|
276
275
|
&& entry.spendable !== false
|
|
277
276
|
&& entry.safe !== false);
|
|
277
|
+
const tx1FundingChangeInputs = findSpendableFundingInputsFromTransaction({
|
|
278
|
+
allUtxos: options.allUtxos,
|
|
279
|
+
txid: options.tx1Txid,
|
|
280
|
+
fundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
281
|
+
minConf: 0,
|
|
282
|
+
});
|
|
278
283
|
return {
|
|
279
284
|
sender: options.sender,
|
|
280
285
|
changeAddress: options.state.funding.address,
|
|
281
|
-
fixedInputs: [
|
|
286
|
+
fixedInputs: [
|
|
287
|
+
{ txid: options.tx1Txid, vout: 1 },
|
|
288
|
+
...tx1FundingChangeInputs,
|
|
289
|
+
],
|
|
282
290
|
outputs: [
|
|
283
291
|
{ data: Buffer.from(options.opReturnData).toString("hex") },
|
|
284
292
|
{ [options.sender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
|
|
@@ -299,11 +307,11 @@ function validateFieldDraft(decoded, funded, plan) {
|
|
|
299
307
|
throw new Error(`${plan.errorPrefix}_missing_sender_input`);
|
|
300
308
|
}
|
|
301
309
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
302
|
-
if (
|
|
310
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
303
311
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
304
312
|
}
|
|
305
313
|
assertFundingInputsAfterFixedPrefix({
|
|
306
|
-
|
|
314
|
+
decoded,
|
|
307
315
|
fixedInputs: plan.fixedInputs,
|
|
308
316
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
309
317
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -332,7 +340,6 @@ function validateFieldDraft(decoded, funded, plan) {
|
|
|
332
340
|
}
|
|
333
341
|
}
|
|
334
342
|
async function buildFieldTransaction(options) {
|
|
335
|
-
const reserveCandidates = computeDesignatedProactiveReserveOutpoints(options.state, await options.rpc.listUnspent(options.walletName, 1));
|
|
336
343
|
return buildWalletMutationTransactionWithReserveFallback({
|
|
337
344
|
rpc: options.rpc,
|
|
338
345
|
walletName: options.walletName,
|
|
@@ -341,7 +348,8 @@ async function buildFieldTransaction(options) {
|
|
|
341
348
|
validateFundedDraft: validateFieldDraft,
|
|
342
349
|
finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
|
|
343
350
|
mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
|
|
344
|
-
|
|
351
|
+
availableFundingMinConf: options.availableFundingMinConf,
|
|
352
|
+
reserveCandidates: options.state.proactiveReserveOutpoints,
|
|
345
353
|
});
|
|
346
354
|
}
|
|
347
355
|
async function saveUpdatedState(options) {
|
|
@@ -1583,9 +1591,10 @@ async function submitFieldCreateFamily(options) {
|
|
|
1583
1591
|
rpc,
|
|
1584
1592
|
walletName,
|
|
1585
1593
|
state: workingState,
|
|
1594
|
+
availableFundingMinConf: 0,
|
|
1586
1595
|
plan: buildFieldFamilyTx2Plan({
|
|
1587
1596
|
state: workingState,
|
|
1588
|
-
allUtxos: await rpc.listUnspent(walletName,
|
|
1597
|
+
allUtxos: await rpc.listUnspent(walletName, 0),
|
|
1589
1598
|
sender: operation.sender,
|
|
1590
1599
|
tx1Txid,
|
|
1591
1600
|
opReturnData: serializeDataUpdate(operation.chainDomain.domainId, resumedFamily.expectedFieldId ?? operation.chainDomain.nextFieldId, options.value.format, options.value.value).opReturnData,
|
|
@@ -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, getDecodedInputScriptPubKeyHex, 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";
|
|
@@ -402,11 +402,11 @@ function validateFundedDraft(decoded, funded, plan) {
|
|
|
402
402
|
throw new Error("wallet_register_missing_sender_input");
|
|
403
403
|
}
|
|
404
404
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, "wallet_register_sender_input_mismatch");
|
|
405
|
-
if (
|
|
405
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
406
406
|
throw new Error("wallet_register_sender_input_mismatch");
|
|
407
407
|
}
|
|
408
408
|
assertFundingInputsAfterFixedPrefix({
|
|
409
|
-
|
|
409
|
+
decoded,
|
|
410
410
|
fixedInputs: plan.fixedInputs,
|
|
411
411
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
412
412
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -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, getDecodedInputScriptPubKeyHex, 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";
|
|
@@ -185,11 +185,11 @@ function validateFundedDraft(decoded, funded, plan) {
|
|
|
185
185
|
throw new Error(`${plan.errorPrefix}_missing_sender_input`);
|
|
186
186
|
}
|
|
187
187
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
188
|
-
if (
|
|
188
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
189
189
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
190
190
|
}
|
|
191
191
|
assertFundingInputsAfterFixedPrefix({
|
|
192
|
-
|
|
192
|
+
decoded,
|
|
193
193
|
fixedInputs: plan.fixedInputs,
|
|
194
194
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
195
195
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -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