@cogcoin/client 0.5.14 → 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/types.d.ts +10 -0
- package/dist/wallet/coin-control.d.ts +2 -1
- package/dist/wallet/coin-control.js +74 -3
- package/dist/wallet/mining/runner.d.ts +72 -2
- package/dist/wallet/mining/runner.js +11 -5
- package/dist/wallet/tx/anchor.js +16 -6
- package/dist/wallet/tx/cog.js +3 -3
- package/dist/wallet/tx/common.d.ts +14 -3
- package/dist/wallet/tx/common.js +86 -29
- package/dist/wallet/tx/domain-admin.js +3 -3
- package/dist/wallet/tx/domain-market.js +3 -3
- package/dist/wallet/tx/field.js +16 -5
- package/dist/wallet/tx/register.js +3 -3
- package/dist/wallet/tx/reputation.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@cogcoin/client`
|
|
2
2
|
|
|
3
|
-
`@cogcoin/client@0.5.
|
|
3
|
+
`@cogcoin/client@0.5.15` is the store-backed Cogcoin client package for applications that want a local wallet, durable SQLite-backed state, and a managed Bitcoin Core integration around `@cogcoin/indexer`. It publishes the reusable client APIs, the SQLite adapter, the managed `bitcoind` integration, and the first-party `cogcoin` CLI in one package.
|
|
4
4
|
|
|
5
5
|
Use Node 22 or newer.
|
|
6
6
|
|
package/dist/bitcoind/types.d.ts
CHANGED
|
@@ -275,6 +275,7 @@ export interface RpcPrevout {
|
|
|
275
275
|
}
|
|
276
276
|
export interface RpcVin {
|
|
277
277
|
txid?: string;
|
|
278
|
+
vout?: number;
|
|
278
279
|
coinbase?: string;
|
|
279
280
|
prevout?: RpcPrevout;
|
|
280
281
|
}
|
|
@@ -310,6 +311,11 @@ export interface RpcWalletTransaction {
|
|
|
310
311
|
blockheight?: number;
|
|
311
312
|
time?: number;
|
|
312
313
|
timereceived?: number;
|
|
314
|
+
details?: Array<{
|
|
315
|
+
address?: string;
|
|
316
|
+
vout?: number;
|
|
317
|
+
}>;
|
|
318
|
+
decoded?: RpcTransaction;
|
|
313
319
|
}
|
|
314
320
|
export interface RpcBlock {
|
|
315
321
|
hash: string;
|
|
@@ -409,6 +415,10 @@ export interface RpcListUnspentEntry {
|
|
|
409
415
|
}
|
|
410
416
|
export interface RpcDecodedPsbt {
|
|
411
417
|
tx: RpcTransaction;
|
|
418
|
+
inputs?: Array<{
|
|
419
|
+
witness_utxo?: RpcVout;
|
|
420
|
+
non_witness_utxo?: RpcTransaction;
|
|
421
|
+
}>;
|
|
412
422
|
}
|
|
413
423
|
export interface RpcFinalizePsbtResult {
|
|
414
424
|
psbt?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RpcListUnspentEntry, RpcLockedUnspent } from "../bitcoind/types.js";
|
|
1
|
+
import type { RpcListUnspentEntry, RpcLockedUnspent, RpcWalletTransaction } from "../bitcoind/types.js";
|
|
2
2
|
import { persistWalletStateUpdate } from "./descriptor-normalization.js";
|
|
3
3
|
import type { WalletRuntimePaths } from "./runtime.js";
|
|
4
4
|
import type { OutpointRecord, PortableWalletArchivePayloadV1, UnlockSessionStateV1, WalletStateV1 } from "./types.js";
|
|
@@ -7,6 +7,7 @@ export interface WalletCoinControlRpc {
|
|
|
7
7
|
listUnspent(walletName: string, minConf?: number): Promise<RpcListUnspentEntry[]>;
|
|
8
8
|
listLockUnspent(walletName: string): Promise<RpcLockedUnspent[]>;
|
|
9
9
|
lockUnspent(walletName: string, unlock: boolean, outputs: RpcLockedUnspent[]): Promise<boolean>;
|
|
10
|
+
getTransaction?(walletName: string, txid: string): Promise<RpcWalletTransaction>;
|
|
10
11
|
}
|
|
11
12
|
export declare function outpointKey(outpoint: OutpointRecord): string;
|
|
12
13
|
export declare function normalizeWalletStateRecord(state: WalletStateV1): WalletStateV1;
|
|
@@ -289,6 +289,64 @@ function collectPersistentPolicyLockedOutpoints(state, spendableUtxos) {
|
|
|
289
289
|
}
|
|
290
290
|
return outpoints;
|
|
291
291
|
}
|
|
292
|
+
function collectManagedScriptPubKeyHexes(state) {
|
|
293
|
+
const scripts = new Set();
|
|
294
|
+
const add = (scriptPubKeyHex) => {
|
|
295
|
+
if (typeof scriptPubKeyHex === "string" && scriptPubKeyHex.length > 0) {
|
|
296
|
+
scripts.add(scriptPubKeyHex);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
add(state.funding.scriptPubKeyHex);
|
|
300
|
+
for (const identity of state.identities) {
|
|
301
|
+
add(identity.scriptPubKeyHex);
|
|
302
|
+
}
|
|
303
|
+
for (const domain of state.domains) {
|
|
304
|
+
add(domain.currentOwnerScriptPubKeyHex);
|
|
305
|
+
}
|
|
306
|
+
for (const family of state.proactiveFamilies) {
|
|
307
|
+
add(family.sourceSenderScriptPubKeyHex);
|
|
308
|
+
add(family.reservedScriptPubKeyHex);
|
|
309
|
+
}
|
|
310
|
+
add(state.miningState.currentSenderScriptPubKeyHex);
|
|
311
|
+
return scripts;
|
|
312
|
+
}
|
|
313
|
+
function findWalletTransactionOutputScriptPubKeyHex(transaction, vout) {
|
|
314
|
+
const decodedScriptPubKeyHex = transaction?.decoded?.vout.find((output) => output.n === vout)?.scriptPubKey?.hex;
|
|
315
|
+
return typeof decodedScriptPubKeyHex === "string" && decodedScriptPubKeyHex.length > 0
|
|
316
|
+
? decodedScriptPubKeyHex
|
|
317
|
+
: null;
|
|
318
|
+
}
|
|
319
|
+
async function collectManagedInspectionUnlocks(options) {
|
|
320
|
+
if (options.rpc.getTransaction === undefined) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
const managedScripts = collectManagedScriptPubKeyHexes(options.state);
|
|
324
|
+
if (managedScripts.size === 0 || options.lockedOutpoints.length === 0) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const transactionCache = new Map();
|
|
328
|
+
const inspectionUnlocks = [];
|
|
329
|
+
const loadTransaction = (txid) => {
|
|
330
|
+
let cached = transactionCache.get(txid);
|
|
331
|
+
if (cached === undefined) {
|
|
332
|
+
cached = options.rpc.getTransaction?.(options.walletName, txid).catch(() => null) ?? Promise.resolve(null);
|
|
333
|
+
transactionCache.set(txid, cached);
|
|
334
|
+
}
|
|
335
|
+
return cached;
|
|
336
|
+
};
|
|
337
|
+
for (const outpoint of options.lockedOutpoints) {
|
|
338
|
+
const key = outpointKey(outpoint);
|
|
339
|
+
if (options.fixedInputKeys.has(key) || options.temporarilyUnlockedKeys.has(key)) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const transaction = await loadTransaction(outpoint.txid);
|
|
343
|
+
const scriptPubKeyHex = findWalletTransactionOutputScriptPubKeyHex(transaction, outpoint.vout);
|
|
344
|
+
if (scriptPubKeyHex !== null && managedScripts.has(scriptPubKeyHex)) {
|
|
345
|
+
inspectionUnlocks.push({ txid: outpoint.txid, vout: outpoint.vout });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return inspectionUnlocks;
|
|
349
|
+
}
|
|
292
350
|
export async function reconcilePersistentPolicyLocks(options) {
|
|
293
351
|
const rawReserveOutpoints = normalizeOutpointRecordList(options.state.proactiveReserveOutpoints);
|
|
294
352
|
let state = normalizeWalletStateRecord(options.state);
|
|
@@ -309,10 +367,23 @@ export async function reconcilePersistentPolicyLocks(options) {
|
|
|
309
367
|
const key = outpointKey(outpoint);
|
|
310
368
|
return lockedBeforeReserveInspectionKeys.has(key) && !fixedInputKeys.has(key);
|
|
311
369
|
});
|
|
312
|
-
|
|
313
|
-
|
|
370
|
+
const managedInspectionUnlocks = await collectManagedInspectionUnlocks({
|
|
371
|
+
rpc: options.rpc,
|
|
372
|
+
walletName: options.walletName,
|
|
373
|
+
state,
|
|
374
|
+
lockedOutpoints: lockedBeforeReserveInspection,
|
|
375
|
+
fixedInputKeys,
|
|
376
|
+
temporarilyUnlockedKeys,
|
|
377
|
+
});
|
|
378
|
+
const inspectionUnlockMap = new Map();
|
|
379
|
+
for (const outpoint of [...reserveInspectionUnlocks, ...managedInspectionUnlocks]) {
|
|
380
|
+
inspectionUnlockMap.set(outpointKey(outpoint), outpoint);
|
|
381
|
+
}
|
|
382
|
+
const inspectionUnlocks = [...inspectionUnlockMap.values()];
|
|
383
|
+
if (inspectionUnlocks.length > 0) {
|
|
384
|
+
await options.rpc.lockUnspent(options.walletName, true, inspectionUnlocks).catch(() => undefined);
|
|
314
385
|
}
|
|
315
|
-
const spendableUtxos =
|
|
386
|
+
const spendableUtxos = inspectionUnlocks.length > 0 || options.spendableUtxos === undefined
|
|
316
387
|
? await options.rpc.listUnspent(options.walletName, 0).catch(() => [])
|
|
317
388
|
: options.spendableUtxos;
|
|
318
389
|
const previouslyProtectedUniverse = collectPersistentPolicyLockedOutpoints(state, spendableUtxos);
|
|
@@ -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;
|
package/dist/wallet/tx/anchor.js
CHANGED
|
@@ -9,7 +9,7 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
|
|
|
9
9
|
import { createDefaultWalletSecretProvider, } from "../state/provider.js";
|
|
10
10
|
import { serializeDomainAnchor, serializeDomainTransfer, validateDomainName, } from "../cogop/index.js";
|
|
11
11
|
import { openWalletReadContext } from "../read/index.js";
|
|
12
|
-
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";
|
|
13
13
|
import { confirmYesNo } from "./confirm.js";
|
|
14
14
|
const ACTIVE_FAMILY_STATUSES = new Set([
|
|
15
15
|
"draft",
|
|
@@ -551,6 +551,12 @@ function buildTx2Plan(options) {
|
|
|
551
551
|
}
|
|
552
552
|
const fundingUtxos = sortUtxos(options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
|
|
553
553
|
&& isSpendableConfirmedUtxo(entry)));
|
|
554
|
+
const tx1FundingChangeInputs = findSpendableFundingInputsFromTransaction({
|
|
555
|
+
allUtxos: options.allUtxos,
|
|
556
|
+
txid: tx1Txid,
|
|
557
|
+
fundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
558
|
+
minConf: 0,
|
|
559
|
+
});
|
|
554
560
|
const foundingPayload = options.operation.foundingMessagePayloadHex === null
|
|
555
561
|
? undefined
|
|
556
562
|
: Buffer.from(options.operation.foundingMessagePayloadHex, "hex");
|
|
@@ -562,7 +568,10 @@ function buildTx2Plan(options) {
|
|
|
562
568
|
address: options.operation.targetIdentity.address,
|
|
563
569
|
},
|
|
564
570
|
changeAddress: options.state.funding.address,
|
|
565
|
-
fixedInputs: [
|
|
571
|
+
fixedInputs: [
|
|
572
|
+
{ txid: provisional.txid, vout: provisional.vout },
|
|
573
|
+
...tx1FundingChangeInputs,
|
|
574
|
+
],
|
|
566
575
|
outputs: [
|
|
567
576
|
{ data: Buffer.from(opReturnData).toString("hex") },
|
|
568
577
|
{ [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
|
|
@@ -590,7 +599,7 @@ function validateTx1Draft(decoded, funded, plan) {
|
|
|
590
599
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
591
600
|
}
|
|
592
601
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
593
|
-
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(
|
|
602
|
+
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(decoded, 0);
|
|
594
603
|
if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex) {
|
|
595
604
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
596
605
|
}
|
|
@@ -600,7 +609,7 @@ function validateTx1Draft(decoded, funded, plan) {
|
|
|
600
609
|
}
|
|
601
610
|
}
|
|
602
611
|
assertFundingInputsAfterFixedPrefix({
|
|
603
|
-
|
|
612
|
+
decoded,
|
|
604
613
|
fixedInputs: plan.fixedInputs,
|
|
605
614
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
606
615
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -644,13 +653,13 @@ function validateTx2Draft(decoded, funded, plan) {
|
|
|
644
653
|
throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
|
|
645
654
|
}
|
|
646
655
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_provisional_input_mismatch`);
|
|
647
|
-
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(
|
|
656
|
+
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(decoded, 0);
|
|
648
657
|
if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex
|
|
649
658
|
|| !inputMatchesOutpoint(inputs[0], plan.requiredProvisionalOutpoint)) {
|
|
650
659
|
throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
|
|
651
660
|
}
|
|
652
661
|
assertFundingInputsAfterFixedPrefix({
|
|
653
|
-
|
|
662
|
+
decoded,
|
|
654
663
|
fixedInputs: plan.fixedInputs,
|
|
655
664
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
656
665
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -700,6 +709,7 @@ async function buildTx2(options) {
|
|
|
700
709
|
validateFundedDraft: validateTx2Draft,
|
|
701
710
|
finalizeErrorCode: "wallet_anchor_tx2_finalize_failed",
|
|
702
711
|
mempoolRejectPrefix: "wallet_anchor_tx2_mempool_rejected",
|
|
712
|
+
availableFundingMinConf: 0,
|
|
703
713
|
reserveCandidates: options.state.proactiveReserveOutpoints,
|
|
704
714
|
});
|
|
705
715
|
}
|
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, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
10
|
+
import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, getDecodedInputScriptPubKeyHex, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
11
11
|
import { confirmTypedAcknowledgement, confirmYesNo } from "./confirm.js";
|
|
12
12
|
import { getCanonicalIdentitySelector, resolveIdentityBySelector, } from "./identity-selector.js";
|
|
13
13
|
import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
|
|
@@ -297,11 +297,11 @@ function validateFundedDraft(decoded, funded, plan) {
|
|
|
297
297
|
throw new Error(`${plan.errorPrefix}_missing_sender_input`);
|
|
298
298
|
}
|
|
299
299
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
300
|
-
if (
|
|
300
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
301
301
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
302
302
|
}
|
|
303
303
|
assertFundingInputsAfterFixedPrefix({
|
|
304
|
-
|
|
304
|
+
decoded,
|
|
305
305
|
fixedInputs: plan.fixedInputs,
|
|
306
306
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
307
307
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -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>;
|
|
@@ -102,6 +111,7 @@ export declare function buildWalletMutationTransaction<TPlan>(options: {
|
|
|
102
111
|
finalizeErrorCode: string;
|
|
103
112
|
mempoolRejectPrefix: string;
|
|
104
113
|
feeRate?: number;
|
|
114
|
+
availableFundingMinConf?: number;
|
|
105
115
|
temporarilyUnlockedPolicyOutpoints?: readonly OutpointRecord[];
|
|
106
116
|
}): Promise<BuiltWalletMutationTransaction>;
|
|
107
117
|
export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>(options: {
|
|
@@ -120,5 +130,6 @@ export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>
|
|
|
120
130
|
finalizeErrorCode: string;
|
|
121
131
|
mempoolRejectPrefix: string;
|
|
122
132
|
feeRate?: number;
|
|
133
|
+
availableFundingMinConf?: number;
|
|
123
134
|
reserveCandidates: readonly OutpointRecord[];
|
|
124
135
|
}): Promise<BuiltWalletMutationTransaction>;
|
package/dist/wallet/tx/common.js
CHANGED
|
@@ -5,6 +5,7 @@ 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;
|
|
8
9
|
function btcNumberToSats(value) {
|
|
9
10
|
return BigInt(Math.round(value * 100_000_000));
|
|
10
11
|
}
|
|
@@ -48,12 +49,24 @@ export function formatCogAmount(value) {
|
|
|
48
49
|
export function outpointKey(outpoint) {
|
|
49
50
|
return `${outpoint.txid}:${outpoint.vout}`;
|
|
50
51
|
}
|
|
51
|
-
function
|
|
52
|
+
function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex, minConf) {
|
|
52
53
|
return entry.scriptPubKey === fundingScriptPubKeyHex
|
|
53
|
-
&& entry.confirmations >=
|
|
54
|
+
&& entry.confirmations >= minConf
|
|
54
55
|
&& entry.spendable !== false
|
|
55
56
|
&& entry.safe !== false;
|
|
56
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
|
+
}
|
|
57
70
|
export function updateMutationRecord(mutation, status, nowUnixMs, options = {}) {
|
|
58
71
|
return {
|
|
59
72
|
...mutation,
|
|
@@ -79,12 +92,33 @@ export function diffTemporaryLockedOutpoints(before, after) {
|
|
|
79
92
|
vout: entry.vout,
|
|
80
93
|
}));
|
|
81
94
|
}
|
|
82
|
-
export function getDecodedInputScriptPubKeyHex(input) {
|
|
83
|
-
return input.prevout?.scriptPubKey?.hex ?? null;
|
|
84
|
-
}
|
|
85
95
|
export function getDecodedInputVout(input) {
|
|
86
|
-
|
|
87
|
-
|
|
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;
|
|
88
122
|
}
|
|
89
123
|
export function inputMatchesOutpoint(input, outpoint) {
|
|
90
124
|
return input.txid === outpoint.txid && getDecodedInputVout(input) === outpoint.vout;
|
|
@@ -100,9 +134,9 @@ export function assertFixedInputPrefixMatches(inputs, fixedInputs, errorCode) {
|
|
|
100
134
|
}
|
|
101
135
|
}
|
|
102
136
|
export function assertFundingInputsAfterFixedPrefix(options) {
|
|
103
|
-
for (let index = options.fixedInputs.length; index < options.
|
|
104
|
-
const input = options.
|
|
105
|
-
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);
|
|
106
140
|
const vout = getDecodedInputVout(input);
|
|
107
141
|
if (scriptPubKeyHex !== options.allowedFundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
|
|
108
142
|
throw new Error(options.errorCode);
|
|
@@ -155,8 +189,8 @@ function computeRemainingFundingValueSats(options) {
|
|
|
155
189
|
for (const value of options.availableFundingValueByKey.values()) {
|
|
156
190
|
remaining += value;
|
|
157
191
|
}
|
|
158
|
-
for (const input of options.
|
|
159
|
-
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(
|
|
192
|
+
for (const [index, input] of options.decoded.tx.vin.entries()) {
|
|
193
|
+
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(options.decoded, index);
|
|
160
194
|
const vout = getDecodedInputVout(input);
|
|
161
195
|
if (scriptPubKeyHex !== options.fundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
|
|
162
196
|
continue;
|
|
@@ -166,7 +200,7 @@ function computeRemainingFundingValueSats(options) {
|
|
|
166
200
|
vout,
|
|
167
201
|
})) ?? 0n;
|
|
168
202
|
}
|
|
169
|
-
for (const output of options.
|
|
203
|
+
for (const output of options.decoded.tx.vout) {
|
|
170
204
|
if (output.scriptPubKey?.hex !== options.fundingScriptPubKeyHex) {
|
|
171
205
|
continue;
|
|
172
206
|
}
|
|
@@ -211,8 +245,9 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
211
245
|
fixedInputs: options.plan.fixedInputs,
|
|
212
246
|
temporarilyUnlockedOutpoints: options.temporarilyUnlockedPolicyOutpoints,
|
|
213
247
|
});
|
|
214
|
-
const
|
|
215
|
-
|
|
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));
|
|
216
251
|
const availableFundingValueByKey = new Map(availableFundingUtxos.map((entry) => [
|
|
217
252
|
outpointKey({ txid: entry.txid, vout: entry.vout }),
|
|
218
253
|
btcNumberToSats(entry.amount),
|
|
@@ -244,7 +279,7 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
244
279
|
options.validateFundedDraft(decoded, funded, validationPlan);
|
|
245
280
|
if (options.state.proactiveReserveSats > 0) {
|
|
246
281
|
const remainingFundingValueSats = computeRemainingFundingValueSats({
|
|
247
|
-
|
|
282
|
+
decoded,
|
|
248
283
|
fundingScriptPubKeyHex: options.plan.allowedFundingScriptPubKeyHex,
|
|
249
284
|
availableFundingValueByKey,
|
|
250
285
|
});
|
|
@@ -252,16 +287,25 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
252
287
|
throw new Error("wallet_mutation_insufficient_funding_after_reserve");
|
|
253
288
|
}
|
|
254
289
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
}
|
|
259
306
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const accepted = mempoolResult[0];
|
|
263
|
-
if (accepted == null || !accepted.allowed) {
|
|
264
|
-
throw new Error(`${options.mempoolRejectPrefix}_${accepted?.["reject-reason"] ?? "unknown"}`);
|
|
307
|
+
finally {
|
|
308
|
+
await options.rpc.walletLock(options.walletName).catch(() => undefined);
|
|
265
309
|
}
|
|
266
310
|
if ((options.temporarilyUnlockedPolicyOutpoints?.length ?? 0) > 0) {
|
|
267
311
|
await reconcilePersistentPolicyLocks({
|
|
@@ -295,31 +339,44 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
295
339
|
}
|
|
296
340
|
}
|
|
297
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];
|
|
298
354
|
let unlockedReserveOutpoints = [];
|
|
299
355
|
let lastError = null;
|
|
300
|
-
for (let attempt = 0; attempt <=
|
|
356
|
+
for (let attempt = 0; attempt <= reserveCandidates.length; attempt += 1) {
|
|
301
357
|
if (attempt > 0) {
|
|
302
358
|
unlockedReserveOutpoints = [
|
|
303
359
|
...unlockedReserveOutpoints,
|
|
304
|
-
|
|
360
|
+
reserveCandidates[attempt - 1],
|
|
305
361
|
];
|
|
306
362
|
}
|
|
307
363
|
try {
|
|
308
364
|
return await buildWalletMutationTransaction({
|
|
309
365
|
rpc: options.rpc,
|
|
310
366
|
walletName: options.walletName,
|
|
311
|
-
state:
|
|
367
|
+
state: effectiveState,
|
|
312
368
|
plan: options.plan,
|
|
313
369
|
validateFundedDraft: options.validateFundedDraft,
|
|
314
370
|
finalizeErrorCode: options.finalizeErrorCode,
|
|
315
371
|
mempoolRejectPrefix: options.mempoolRejectPrefix,
|
|
316
372
|
feeRate: options.feeRate,
|
|
373
|
+
availableFundingMinConf: options.availableFundingMinConf,
|
|
317
374
|
temporarilyUnlockedPolicyOutpoints: unlockedReserveOutpoints,
|
|
318
375
|
});
|
|
319
376
|
}
|
|
320
377
|
catch (error) {
|
|
321
378
|
lastError = error;
|
|
322
|
-
if ((!isInsufficientFundsError(error) && !isReserveFloorFundingError(error)) || attempt ===
|
|
379
|
+
if ((!isInsufficientFundsError(error) && !isReserveFloorFundingError(error)) || attempt === reserveCandidates.length) {
|
|
323
380
|
throw error;
|
|
324
381
|
}
|
|
325
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, buildWalletMutationTransactionWithReserveFallback, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
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,
|
|
@@ -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, buildWalletMutationTransactionWithReserveFallback, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
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";
|
|
@@ -297,11 +297,11 @@ function validateFundedDraft(decoded, funded, plan) {
|
|
|
297
297
|
throw new Error(`${plan.errorPrefix}_missing_sender_input`);
|
|
298
298
|
}
|
|
299
299
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
300
|
-
if (
|
|
300
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
301
301
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
302
302
|
}
|
|
303
303
|
assertFundingInputsAfterFixedPrefix({
|
|
304
|
-
|
|
304
|
+
decoded,
|
|
305
305
|
fixedInputs: plan.fixedInputs,
|
|
306
306
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
307
307
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
package/dist/wallet/tx/field.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createDefaultWalletSecretProvider, } from "../state/provider.js";
|
|
|
10
10
|
import { FIELD_FORMAT_BYTES, serializeDataUpdate, serializeFieldReg, } from "../cogop/index.js";
|
|
11
11
|
import { validateFieldName } from "../cogop/validate-name.js";
|
|
12
12
|
import { findDomainField, openWalletReadContext, } from "../read/index.js";
|
|
13
|
-
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";
|
|
14
14
|
import { confirmTypedAcknowledgement as confirmSharedTypedAcknowledgement, confirmYesNo as confirmSharedYesNo, } from "./confirm.js";
|
|
15
15
|
import { getCanonicalIdentitySelector } from "./identity-selector.js";
|
|
16
16
|
import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
|
|
@@ -274,10 +274,19 @@ function buildFieldFamilyTx2Plan(options) {
|
|
|
274
274
|
&& entry.confirmations >= 1
|
|
275
275
|
&& entry.spendable !== false
|
|
276
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
|
+
});
|
|
277
283
|
return {
|
|
278
284
|
sender: options.sender,
|
|
279
285
|
changeAddress: options.state.funding.address,
|
|
280
|
-
fixedInputs: [
|
|
286
|
+
fixedInputs: [
|
|
287
|
+
{ txid: options.tx1Txid, vout: 1 },
|
|
288
|
+
...tx1FundingChangeInputs,
|
|
289
|
+
],
|
|
281
290
|
outputs: [
|
|
282
291
|
{ data: Buffer.from(options.opReturnData).toString("hex") },
|
|
283
292
|
{ [options.sender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
|
|
@@ -298,11 +307,11 @@ function validateFieldDraft(decoded, funded, plan) {
|
|
|
298
307
|
throw new Error(`${plan.errorPrefix}_missing_sender_input`);
|
|
299
308
|
}
|
|
300
309
|
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
301
|
-
if (
|
|
310
|
+
if (getDecodedInputScriptPubKeyHex(decoded, 0) !== plan.sender.scriptPubKeyHex) {
|
|
302
311
|
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
303
312
|
}
|
|
304
313
|
assertFundingInputsAfterFixedPrefix({
|
|
305
|
-
|
|
314
|
+
decoded,
|
|
306
315
|
fixedInputs: plan.fixedInputs,
|
|
307
316
|
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
308
317
|
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
@@ -339,6 +348,7 @@ async function buildFieldTransaction(options) {
|
|
|
339
348
|
validateFundedDraft: validateFieldDraft,
|
|
340
349
|
finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
|
|
341
350
|
mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
|
|
351
|
+
availableFundingMinConf: options.availableFundingMinConf,
|
|
342
352
|
reserveCandidates: options.state.proactiveReserveOutpoints,
|
|
343
353
|
});
|
|
344
354
|
}
|
|
@@ -1581,9 +1591,10 @@ async function submitFieldCreateFamily(options) {
|
|
|
1581
1591
|
rpc,
|
|
1582
1592
|
walletName,
|
|
1583
1593
|
state: workingState,
|
|
1594
|
+
availableFundingMinConf: 0,
|
|
1584
1595
|
plan: buildFieldFamilyTx2Plan({
|
|
1585
1596
|
state: workingState,
|
|
1586
|
-
allUtxos: await rpc.listUnspent(walletName,
|
|
1597
|
+
allUtxos: await rpc.listUnspent(walletName, 0),
|
|
1587
1598
|
sender: operation.sender,
|
|
1588
1599
|
tx1Txid,
|
|
1589
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, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
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,
|
|
@@ -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, buildWalletMutationTransactionWithReserveFallback, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, outpointKey, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
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,
|
package/package.json
CHANGED