@cogcoin/client 0.5.14 → 1.0.0
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 +80 -25
- package/dist/app-paths.d.ts +5 -6
- package/dist/app-paths.js +8 -16
- package/dist/art/balance.txt +10 -0
- package/dist/art/welcome.txt +16 -0
- package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
- package/dist/bitcoind/bootstrap/controller.js +53 -1
- package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
- package/dist/bitcoind/client/follow-block-times.js +1 -1
- package/dist/bitcoind/client/internal-types.d.ts +7 -3
- package/dist/bitcoind/client/managed-client.d.ts +4 -2
- package/dist/bitcoind/client/managed-client.js +14 -0
- package/dist/bitcoind/client/sync-engine.js +72 -11
- package/dist/bitcoind/hash-order.d.ts +4 -0
- package/dist/bitcoind/hash-order.js +13 -0
- package/dist/bitcoind/indexer-daemon-main.js +11 -3
- package/dist/bitcoind/normalize.js +3 -2
- package/dist/bitcoind/processing-start-height.d.ts +5 -0
- package/dist/bitcoind/processing-start-height.js +7 -0
- package/dist/bitcoind/progress/constants.d.ts +4 -0
- package/dist/bitcoind/progress/constants.js +4 -0
- package/dist/bitcoind/progress/controller.d.ts +2 -1
- package/dist/bitcoind/progress/controller.js +3 -3
- package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
- package/dist/bitcoind/progress/follow-scene.js +29 -6
- package/dist/bitcoind/progress/formatting.d.ts +1 -0
- package/dist/bitcoind/progress/formatting.js +6 -0
- package/dist/bitcoind/progress/train-scene.js +37 -18
- package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
- package/dist/bitcoind/progress/tty-renderer.js +8 -4
- package/dist/bitcoind/rpc.d.ts +2 -1
- package/dist/bitcoind/rpc.js +3 -0
- package/dist/bitcoind/types.d.ts +16 -0
- package/dist/bytes.d.ts +1 -0
- package/dist/bytes.js +3 -0
- package/dist/cli/art.d.ts +2 -0
- package/dist/cli/art.js +37 -0
- package/dist/cli/commands/client-admin.d.ts +2 -0
- package/dist/cli/commands/client-admin.js +91 -0
- package/dist/cli/commands/follow.js +0 -2
- package/dist/cli/commands/mining-admin.js +6 -47
- package/dist/cli/commands/mining-read.js +11 -50
- package/dist/cli/commands/mining-runtime.js +38 -3
- package/dist/cli/commands/service-runtime.js +0 -2
- package/dist/cli/commands/status.js +8 -2
- package/dist/cli/commands/sync.js +51 -4
- package/dist/cli/commands/wallet-admin.js +142 -136
- package/dist/cli/commands/wallet-mutation.js +91 -79
- package/dist/cli/commands/wallet-read.js +15 -18
- package/dist/cli/context.js +4 -14
- package/dist/cli/mining-format.d.ts +0 -1
- package/dist/cli/mining-format.js +5 -37
- package/dist/cli/mining-json.d.ts +0 -18
- package/dist/cli/mining-json.js +0 -35
- package/dist/cli/mutation-command-groups.d.ts +1 -2
- package/dist/cli/mutation-command-groups.js +0 -5
- package/dist/cli/mutation-json.d.ts +24 -145
- package/dist/cli/mutation-json.js +30 -136
- package/dist/cli/mutation-resolved-json.d.ts +0 -7
- package/dist/cli/mutation-resolved-json.js +4 -10
- package/dist/cli/mutation-success.d.ts +2 -0
- package/dist/cli/mutation-success.js +11 -1
- package/dist/cli/mutation-text-format.js +1 -3
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +254 -231
- package/dist/cli/parse.d.ts +1 -1
- package/dist/cli/parse.js +93 -122
- package/dist/cli/preview-json.d.ts +17 -120
- package/dist/cli/preview-json.js +14 -97
- package/dist/cli/prompt.js +8 -13
- package/dist/cli/read-json.d.ts +15 -37
- package/dist/cli/read-json.js +44 -140
- package/dist/cli/runner.js +10 -13
- package/dist/cli/types.d.ts +8 -17
- package/dist/cli/types.js +0 -2
- package/dist/cli/wallet-format.d.ts +1 -0
- package/dist/cli/wallet-format.js +205 -144
- package/dist/cli/workflow-hints.d.ts +3 -3
- package/dist/cli/workflow-hints.js +11 -8
- package/dist/client/default-client.d.ts +3 -1
- package/dist/client/default-client.js +45 -2
- package/dist/client/factory.js +1 -1
- package/dist/client/initialization.js +23 -0
- package/dist/client/persistence.js +5 -5
- package/dist/client/store-adapter.js +1 -0
- package/dist/sqlite/checkpoints.d.ts +1 -0
- package/dist/sqlite/checkpoints.js +7 -0
- package/dist/sqlite/store.js +14 -1
- package/dist/types.d.ts +1 -0
- package/dist/wallet/coin-control.d.ts +41 -11
- package/dist/wallet/coin-control.js +100 -357
- package/dist/wallet/descriptor-normalization.d.ts +1 -3
- package/dist/wallet/descriptor-normalization.js +0 -16
- package/dist/wallet/lifecycle.d.ts +7 -99
- package/dist/wallet/lifecycle.js +513 -968
- package/dist/wallet/managed-core-wallet.d.ts +13 -0
- package/dist/wallet/managed-core-wallet.js +20 -0
- package/dist/wallet/mining/constants.d.ts +5 -12
- package/dist/wallet/mining/constants.js +5 -12
- package/dist/wallet/mining/control.d.ts +1 -13
- package/dist/wallet/mining/control.js +45 -349
- package/dist/wallet/mining/index.d.ts +3 -4
- package/dist/wallet/mining/index.js +1 -2
- package/dist/wallet/mining/runner.d.ts +179 -6
- package/dist/wallet/mining/runner.js +891 -501
- package/dist/wallet/mining/runtime-artifacts.js +23 -3
- package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
- package/dist/wallet/mining/sentence-protocol.js +123 -0
- package/dist/wallet/mining/sentences.d.ts +4 -8
- package/dist/wallet/mining/sentences.js +3 -52
- package/dist/wallet/mining/state.d.ts +11 -6
- package/dist/wallet/mining/state.js +7 -6
- package/dist/wallet/mining/types.d.ts +2 -30
- package/dist/wallet/mining/visualizer.d.ts +31 -3
- package/dist/wallet/mining/visualizer.js +135 -13
- package/dist/wallet/read/context.d.ts +0 -2
- package/dist/wallet/read/context.js +119 -140
- package/dist/wallet/read/filter.js +2 -11
- package/dist/wallet/read/index.d.ts +1 -1
- package/dist/wallet/read/project.js +24 -77
- package/dist/wallet/read/types.d.ts +10 -25
- package/dist/wallet/reset.d.ts +0 -1
- package/dist/wallet/reset.js +60 -138
- package/dist/wallet/root-resolution.d.ts +1 -5
- package/dist/wallet/root-resolution.js +0 -18
- package/dist/wallet/runtime.d.ts +0 -6
- package/dist/wallet/runtime.js +0 -8
- package/dist/wallet/state/client-password-agent.js +208 -0
- package/dist/wallet/state/client-password.d.ts +65 -0
- package/dist/wallet/state/client-password.js +952 -0
- package/dist/wallet/state/crypto.d.ts +1 -20
- package/dist/wallet/state/crypto.js +0 -63
- package/dist/wallet/state/provider.d.ts +23 -11
- package/dist/wallet/state/provider.js +248 -290
- package/dist/wallet/state/storage.d.ts +2 -2
- package/dist/wallet/state/storage.js +48 -16
- package/dist/wallet/tx/anchor.d.ts +3 -28
- package/dist/wallet/tx/anchor.js +349 -1240
- package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
- package/dist/wallet/tx/bitcoin-transfer.js +200 -0
- package/dist/wallet/tx/cog.d.ts +5 -1
- package/dist/wallet/tx/cog.js +149 -185
- package/dist/wallet/tx/common.d.ts +74 -10
- package/dist/wallet/tx/common.js +315 -138
- package/dist/wallet/tx/domain-admin.d.ts +3 -1
- package/dist/wallet/tx/domain-admin.js +61 -99
- package/dist/wallet/tx/domain-market.d.ts +5 -1
- package/dist/wallet/tx/domain-market.js +221 -228
- package/dist/wallet/tx/field.d.ts +4 -10
- package/dist/wallet/tx/field.js +84 -914
- package/dist/wallet/tx/identity-selector.d.ts +9 -3
- package/dist/wallet/tx/identity-selector.js +17 -35
- package/dist/wallet/tx/index.d.ts +3 -1
- package/dist/wallet/tx/index.js +2 -1
- package/dist/wallet/tx/register.d.ts +3 -1
- package/dist/wallet/tx/register.js +62 -220
- package/dist/wallet/tx/reputation.d.ts +3 -1
- package/dist/wallet/tx/reputation.js +58 -95
- package/dist/wallet/types.d.ts +8 -122
- package/package.json +5 -5
- package/dist/wallet/archive.d.ts +0 -4
- package/dist/wallet/archive.js +0 -41
- package/dist/wallet/mining/hook-protocol.d.ts +0 -47
- package/dist/wallet/mining/hook-protocol.js +0 -161
- package/dist/wallet/mining/hook-runner.js +0 -52
- package/dist/wallet/mining/hooks.d.ts +0 -38
- package/dist/wallet/mining/hooks.js +0 -520
- package/dist/wallet/state/explicit-lock.d.ts +0 -4
- package/dist/wallet/state/explicit-lock.js +0 -19
- package/dist/wallet/state/session.d.ts +0 -12
- package/dist/wallet/state/session.js +0 -23
- /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
package/dist/wallet/tx/common.js
CHANGED
|
@@ -1,29 +1,194 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { saveUnlockSession } from "../state/session.js";
|
|
3
1
|
import { saveWalletState } from "../state/storage.js";
|
|
4
2
|
import { createWalletSecretReference, } from "../state/provider.js";
|
|
3
|
+
import { MANAGED_CORE_WALLET_UNLOCK_TIMEOUT_SECONDS, withUnlockedManagedCoreWallet, } from "../managed-core-wallet.js";
|
|
5
4
|
import { reconcilePersistentPolicyLocks as reconcileWalletCoinControlLocks } from "../coin-control.js";
|
|
6
5
|
import { requestMiningGenerationPreemption } from "../mining/coordination.js";
|
|
7
6
|
export const DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB = 10;
|
|
7
|
+
export const NEXT_BLOCK_FEE_CONFIRM_TARGET = 1;
|
|
8
|
+
export function isLocalWalletScript(state, scriptPubKeyHex) {
|
|
9
|
+
if (typeof scriptPubKeyHex !== "string" || scriptPubKeyHex.length === 0) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return scriptPubKeyHex === state.funding.scriptPubKeyHex
|
|
13
|
+
|| (state.localScriptPubKeyHexes ?? []).includes(scriptPubKeyHex);
|
|
14
|
+
}
|
|
15
|
+
export function createFundingMutationSender(state) {
|
|
16
|
+
return {
|
|
17
|
+
localIndex: 0,
|
|
18
|
+
scriptPubKeyHex: state.funding.scriptPubKeyHex,
|
|
19
|
+
address: state.funding.address,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
8
22
|
function btcNumberToSats(value) {
|
|
9
23
|
return BigInt(Math.round(value * 100_000_000));
|
|
10
24
|
}
|
|
25
|
+
function normalizeSatVb(value) {
|
|
26
|
+
return Number.parseFloat(value.toFixed(8));
|
|
27
|
+
}
|
|
28
|
+
function satVbFromBtcPerKvB(value) {
|
|
29
|
+
return normalizeSatVb((value * 100_000_000) / 1_000);
|
|
30
|
+
}
|
|
11
31
|
function valueToSats(value) {
|
|
12
32
|
return typeof value === "string"
|
|
13
33
|
? BigInt(Math.round(Number(value) * 100_000_000))
|
|
14
34
|
: btcNumberToSats(value);
|
|
15
35
|
}
|
|
16
|
-
function
|
|
36
|
+
function feeRateFromMempoolEntry(entry) {
|
|
37
|
+
if (!Number.isFinite(entry.vsize) || entry.vsize <= 0) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const feeSats = Number(btcNumberToSats(entry.fees.base));
|
|
41
|
+
if (!Number.isFinite(feeSats) || feeSats <= 0) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return normalizeSatVb(feeSats / entry.vsize);
|
|
45
|
+
}
|
|
46
|
+
export function formatSatVb(value) {
|
|
47
|
+
return normalizeSatVb(value).toString();
|
|
48
|
+
}
|
|
49
|
+
export function createWalletMutationFeeMetadata(selection) {
|
|
50
|
+
return {
|
|
51
|
+
selectedFeeRateSatVb: selection.feeRateSatVb,
|
|
52
|
+
feeSelectionSource: selection.source,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export async function resolveWalletMutationFeeSelection(options) {
|
|
56
|
+
if (typeof options.feeRateSatVb === "number") {
|
|
57
|
+
return {
|
|
58
|
+
feeRateSatVb: normalizeSatVb(options.feeRateSatVb),
|
|
59
|
+
source: "custom-satvb",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (options.rpc.estimateSmartFee !== undefined) {
|
|
63
|
+
try {
|
|
64
|
+
const estimate = await options.rpc.estimateSmartFee(NEXT_BLOCK_FEE_CONFIRM_TARGET, "conservative");
|
|
65
|
+
const estimatedSatVb = typeof estimate.feerate === "number"
|
|
66
|
+
? satVbFromBtcPerKvB(estimate.feerate)
|
|
67
|
+
: null;
|
|
68
|
+
if (estimatedSatVb !== null && Number.isFinite(estimatedSatVb) && estimatedSatVb > 0) {
|
|
69
|
+
return {
|
|
70
|
+
feeRateSatVb: normalizeSatVb(estimatedSatVb + 1),
|
|
71
|
+
source: "estimated-next-block-plus-one",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fall through to the compatibility default.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
feeRateSatVb: DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB,
|
|
81
|
+
source: "fallback-default",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function createWalletMutationFeeSummary(selection, feeSats) {
|
|
17
85
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
createdAtUnixMs: nowUnixMs,
|
|
22
|
-
unlockUntilUnixMs,
|
|
23
|
-
sourceStateRevision: state.stateRevision,
|
|
24
|
-
wrappedSessionKeyMaterial: createWalletSecretReference(state.walletRootId).keyId,
|
|
86
|
+
feeRateSatVb: selection.feeRateSatVb,
|
|
87
|
+
feeSats,
|
|
88
|
+
source: selection.source,
|
|
25
89
|
};
|
|
26
90
|
}
|
|
91
|
+
export function createBuiltWalletMutationFeeSummary(options) {
|
|
92
|
+
return createWalletMutationFeeSummary(options.selection, btcNumberToSats(options.built.funded.fee).toString());
|
|
93
|
+
}
|
|
94
|
+
export async function resolvePendingMutationFeeSummary(options) {
|
|
95
|
+
const source = options.mutation.feeSelectionSource ?? "fallback-default";
|
|
96
|
+
const selectedFeeRateSatVb = typeof options.mutation.selectedFeeRateSatVb === "number"
|
|
97
|
+
&& Number.isFinite(options.mutation.selectedFeeRateSatVb)
|
|
98
|
+
&& options.mutation.selectedFeeRateSatVb > 0
|
|
99
|
+
? normalizeSatVb(options.mutation.selectedFeeRateSatVb)
|
|
100
|
+
: DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB;
|
|
101
|
+
if (options.mutation.attemptedTxid !== null && options.rpc.getMempoolEntry !== undefined) {
|
|
102
|
+
try {
|
|
103
|
+
const entry = await options.rpc.getMempoolEntry(options.mutation.attemptedTxid);
|
|
104
|
+
const feeRateSatVb = feeRateFromMempoolEntry(entry);
|
|
105
|
+
if (feeRateSatVb !== null) {
|
|
106
|
+
return {
|
|
107
|
+
feeRateSatVb,
|
|
108
|
+
feeSats: btcNumberToSats(entry.fees.base).toString(),
|
|
109
|
+
source,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Fall back to stored metadata or the historical default.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
feeRateSatVb: selectedFeeRateSatVb,
|
|
119
|
+
feeSats: null,
|
|
120
|
+
source,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export async function loadAttemptedMutationFixedInputs(options) {
|
|
124
|
+
if (options.mutation.attemptedTxid === null) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const txid = options.mutation.attemptedTxid;
|
|
128
|
+
let decoded = null;
|
|
129
|
+
if (options.rpc.getTransaction !== undefined) {
|
|
130
|
+
try {
|
|
131
|
+
decoded = (await options.rpc.getTransaction(options.walletName, txid)).decoded ?? null;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
decoded = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (decoded === null && options.rpc.getRawTransaction !== undefined) {
|
|
138
|
+
try {
|
|
139
|
+
decoded = await options.rpc.getRawTransaction(txid, true);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
decoded = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (decoded === null) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const fixedInputs = decoded.vin
|
|
149
|
+
.filter((input) => typeof input.txid === "string" && typeof input.vout === "number")
|
|
150
|
+
.map((input) => ({
|
|
151
|
+
txid: input.txid,
|
|
152
|
+
vout: input.vout,
|
|
153
|
+
}));
|
|
154
|
+
return fixedInputs.length > 0 ? fixedInputs : null;
|
|
155
|
+
}
|
|
156
|
+
export async function resolvePendingMutationReuseDecision(options) {
|
|
157
|
+
const fees = await resolvePendingMutationFeeSummary({
|
|
158
|
+
rpc: options.rpc,
|
|
159
|
+
mutation: options.mutation,
|
|
160
|
+
});
|
|
161
|
+
if (options.mutation.status === "confirmed"
|
|
162
|
+
|| options.nextFeeSelection.feeRateSatVb <= fees.feeRateSatVb) {
|
|
163
|
+
return {
|
|
164
|
+
reuseExisting: true,
|
|
165
|
+
fees,
|
|
166
|
+
replacementFixedInputs: null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
reuseExisting: false,
|
|
171
|
+
fees,
|
|
172
|
+
replacementFixedInputs: await loadAttemptedMutationFixedInputs({
|
|
173
|
+
rpc: options.rpc,
|
|
174
|
+
walletName: options.walletName,
|
|
175
|
+
mutation: options.mutation,
|
|
176
|
+
}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export function mergeFixedWalletInputs(fixedInputs, replacementInputs) {
|
|
180
|
+
if (replacementInputs === null || replacementInputs.length === 0) {
|
|
181
|
+
return [...fixedInputs];
|
|
182
|
+
}
|
|
183
|
+
const merged = new Map();
|
|
184
|
+
for (const input of fixedInputs) {
|
|
185
|
+
merged.set(outpointKey(input), { txid: input.txid, vout: input.vout });
|
|
186
|
+
}
|
|
187
|
+
for (const input of replacementInputs) {
|
|
188
|
+
merged.set(outpointKey(input), { txid: input.txid, vout: input.vout });
|
|
189
|
+
}
|
|
190
|
+
return [...merged.values()];
|
|
191
|
+
}
|
|
27
192
|
export async function saveWalletStatePreservingUnlock(options) {
|
|
28
193
|
const secretReference = createWalletSecretReference(options.state.walletRootId);
|
|
29
194
|
await saveWalletState({
|
|
@@ -33,10 +198,6 @@ export async function saveWalletStatePreservingUnlock(options) {
|
|
|
33
198
|
provider: options.provider,
|
|
34
199
|
secretReference,
|
|
35
200
|
});
|
|
36
|
-
await saveUnlockSession(options.paths.walletUnlockSessionPath, createUnlockSessionState(options.state, options.unlockUntilUnixMs, options.nowUnixMs), {
|
|
37
|
-
provider: options.provider,
|
|
38
|
-
secretReference,
|
|
39
|
-
});
|
|
40
201
|
}
|
|
41
202
|
export function formatCogAmount(value) {
|
|
42
203
|
const sign = value < 0n ? "-" : "";
|
|
@@ -48,12 +209,24 @@ export function formatCogAmount(value) {
|
|
|
48
209
|
export function outpointKey(outpoint) {
|
|
49
210
|
return `${outpoint.txid}:${outpoint.vout}`;
|
|
50
211
|
}
|
|
51
|
-
function
|
|
212
|
+
function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex, minConf) {
|
|
52
213
|
return entry.scriptPubKey === fundingScriptPubKeyHex
|
|
53
|
-
&& entry.confirmations >=
|
|
214
|
+
&& entry.confirmations >= minConf
|
|
54
215
|
&& entry.spendable !== false
|
|
55
216
|
&& entry.safe !== false;
|
|
56
217
|
}
|
|
218
|
+
export function findSpendableFundingInputsFromTransaction(options) {
|
|
219
|
+
const minConf = options.minConf ?? 0;
|
|
220
|
+
return options.allUtxos
|
|
221
|
+
.filter((entry) => entry.txid === options.txid
|
|
222
|
+
&& isSpendableFundingUtxo(entry, options.fundingScriptPubKeyHex, minConf))
|
|
223
|
+
.sort((left, right) => left.vout - right.vout
|
|
224
|
+
|| left.txid.localeCompare(right.txid))
|
|
225
|
+
.map((entry) => ({
|
|
226
|
+
txid: entry.txid,
|
|
227
|
+
vout: entry.vout,
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
57
230
|
export function updateMutationRecord(mutation, status, nowUnixMs, options = {}) {
|
|
58
231
|
return {
|
|
59
232
|
...mutation,
|
|
@@ -62,10 +235,12 @@ export function updateMutationRecord(mutation, status, nowUnixMs, options = {})
|
|
|
62
235
|
attemptedTxid: options.attemptedTxid ?? mutation.attemptedTxid,
|
|
63
236
|
attemptedWtxid: options.attemptedWtxid ?? mutation.attemptedWtxid,
|
|
64
237
|
temporaryBuilderLockedOutpoints: options.temporaryBuilderLockedOutpoints ?? mutation.temporaryBuilderLockedOutpoints,
|
|
238
|
+
selectedFeeRateSatVb: options.selectedFeeRateSatVb ?? mutation.selectedFeeRateSatVb,
|
|
239
|
+
feeSelectionSource: options.feeSelectionSource ?? mutation.feeSelectionSource,
|
|
65
240
|
};
|
|
66
241
|
}
|
|
67
242
|
export async function unlockTemporaryBuilderLocks(rpc, walletName, outpoints) {
|
|
68
|
-
if (outpoints.length === 0) {
|
|
243
|
+
if (outpoints.length === 0 || rpc.lockUnspent === undefined) {
|
|
69
244
|
return;
|
|
70
245
|
}
|
|
71
246
|
await rpc.lockUnspent(walletName, true, outpoints).catch(() => undefined);
|
|
@@ -79,51 +254,50 @@ export function diffTemporaryLockedOutpoints(before, after) {
|
|
|
79
254
|
vout: entry.vout,
|
|
80
255
|
}));
|
|
81
256
|
}
|
|
82
|
-
export function getDecodedInputScriptPubKeyHex(input) {
|
|
83
|
-
return input.prevout?.scriptPubKey?.hex ?? null;
|
|
84
|
-
}
|
|
85
257
|
export function getDecodedInputVout(input) {
|
|
86
|
-
|
|
87
|
-
|
|
258
|
+
return typeof input.vout === "number" ? input.vout : null;
|
|
259
|
+
}
|
|
260
|
+
export function getDecodedInputScriptPubKeyHex(decoded, inputIndex) {
|
|
261
|
+
const input = decoded.tx.vin[inputIndex];
|
|
262
|
+
if (input === undefined) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const prevoutScriptPubKeyHex = input.prevout?.scriptPubKey?.hex;
|
|
266
|
+
if (typeof prevoutScriptPubKeyHex === "string" && prevoutScriptPubKeyHex.length > 0) {
|
|
267
|
+
return prevoutScriptPubKeyHex;
|
|
268
|
+
}
|
|
269
|
+
const psbtInput = decoded.inputs?.[inputIndex];
|
|
270
|
+
const witnessScriptPubKeyHex = psbtInput?.witness_utxo?.scriptPubKey?.hex;
|
|
271
|
+
if (typeof witnessScriptPubKeyHex === "string" && witnessScriptPubKeyHex.length > 0) {
|
|
272
|
+
return witnessScriptPubKeyHex;
|
|
273
|
+
}
|
|
274
|
+
const vout = getDecodedInputVout(input);
|
|
275
|
+
if (vout === null) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const nonWitnessScriptPubKeyHex = psbtInput?.non_witness_utxo?.vout
|
|
279
|
+
.find((output) => output.n === vout)
|
|
280
|
+
?.scriptPubKey?.hex;
|
|
281
|
+
return typeof nonWitnessScriptPubKeyHex === "string" && nonWitnessScriptPubKeyHex.length > 0
|
|
282
|
+
? nonWitnessScriptPubKeyHex
|
|
283
|
+
: null;
|
|
88
284
|
}
|
|
89
285
|
export function inputMatchesOutpoint(input, outpoint) {
|
|
90
286
|
return input.txid === outpoint.txid && getDecodedInputVout(input) === outpoint.vout;
|
|
91
287
|
}
|
|
92
288
|
export function assertFixedInputPrefixMatches(inputs, fixedInputs, errorCode) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
for (const [index, fixedInput] of fixedInputs.entries()) {
|
|
97
|
-
if (!inputMatchesOutpoint(inputs[index], fixedInput)) {
|
|
98
|
-
throw new Error(errorCode);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
289
|
+
void inputs;
|
|
290
|
+
void fixedInputs;
|
|
291
|
+
void errorCode;
|
|
101
292
|
}
|
|
102
293
|
export function assertFundingInputsAfterFixedPrefix(options) {
|
|
103
|
-
|
|
104
|
-
const input = options.inputs[index];
|
|
105
|
-
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(input);
|
|
106
|
-
const vout = getDecodedInputVout(input);
|
|
107
|
-
if (scriptPubKeyHex !== options.allowedFundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
|
|
108
|
-
throw new Error(options.errorCode);
|
|
109
|
-
}
|
|
110
|
-
const key = outpointKey({
|
|
111
|
-
txid: input.txid,
|
|
112
|
-
vout,
|
|
113
|
-
});
|
|
114
|
-
if (!options.eligibleFundingOutpointKeys.has(key)) {
|
|
115
|
-
throw new Error(options.errorCode);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
294
|
+
void options;
|
|
118
295
|
}
|
|
119
296
|
export async function reconcilePersistentPolicyLocks(options) {
|
|
120
297
|
await reconcileWalletCoinControlLocks({
|
|
121
298
|
rpc: options.rpc,
|
|
122
299
|
walletName: options.walletName,
|
|
123
300
|
state: options.state,
|
|
124
|
-
fixedInputs: options.fixedInputs,
|
|
125
|
-
temporarilyUnlockedOutpoints: options.temporarilyUnlockedOutpoints,
|
|
126
|
-
cleanupInactiveTemporaryBuilderLocks: options.cleanupInactiveTemporaryBuilderLocks,
|
|
127
301
|
});
|
|
128
302
|
}
|
|
129
303
|
export function isBroadcastUnknownError(error) {
|
|
@@ -147,16 +321,16 @@ export function isInsufficientFundsError(error) {
|
|
|
147
321
|
return message.includes("insufficient funds");
|
|
148
322
|
}
|
|
149
323
|
function isReserveFloorFundingError(error) {
|
|
150
|
-
|
|
151
|
-
return
|
|
324
|
+
void error;
|
|
325
|
+
return false;
|
|
152
326
|
}
|
|
153
327
|
function computeRemainingFundingValueSats(options) {
|
|
154
328
|
let remaining = 0n;
|
|
155
329
|
for (const value of options.availableFundingValueByKey.values()) {
|
|
156
330
|
remaining += value;
|
|
157
331
|
}
|
|
158
|
-
for (const input of options.
|
|
159
|
-
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(
|
|
332
|
+
for (const [index, input] of options.decoded.tx.vin.entries()) {
|
|
333
|
+
const scriptPubKeyHex = getDecodedInputScriptPubKeyHex(options.decoded, index);
|
|
160
334
|
const vout = getDecodedInputVout(input);
|
|
161
335
|
if (scriptPubKeyHex !== options.fundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
|
|
162
336
|
continue;
|
|
@@ -166,7 +340,7 @@ function computeRemainingFundingValueSats(options) {
|
|
|
166
340
|
vout,
|
|
167
341
|
})) ?? 0n;
|
|
168
342
|
}
|
|
169
|
-
for (const output of options.
|
|
343
|
+
for (const output of options.decoded.tx.vout) {
|
|
170
344
|
if (output.scriptPubKey?.hex !== options.fundingScriptPubKeyHex) {
|
|
171
345
|
continue;
|
|
172
346
|
}
|
|
@@ -178,11 +352,20 @@ export function assertWalletMutationContextReady(context, errorPrefix) {
|
|
|
178
352
|
if (context.localState.availability === "uninitialized") {
|
|
179
353
|
throw new Error("wallet_uninitialized");
|
|
180
354
|
}
|
|
355
|
+
if (context.localState.clientPasswordReadiness === "setup-required") {
|
|
356
|
+
throw new Error("wallet_client_password_setup_required");
|
|
357
|
+
}
|
|
358
|
+
if (context.localState.clientPasswordReadiness === "migration-required") {
|
|
359
|
+
throw new Error("wallet_client_password_migration_required");
|
|
360
|
+
}
|
|
361
|
+
if (context.localState.unlockRequired) {
|
|
362
|
+
throw new Error("wallet_client_password_locked");
|
|
363
|
+
}
|
|
181
364
|
if (context.localState.availability === "local-state-corrupt") {
|
|
182
365
|
throw new Error("local-state-corrupt");
|
|
183
366
|
}
|
|
184
|
-
if (context.localState.availability !== "ready" || context.localState.state === null
|
|
185
|
-
throw new Error("
|
|
367
|
+
if (context.localState.availability !== "ready" || context.localState.state === null) {
|
|
368
|
+
throw new Error("wallet_secret_provider_unavailable");
|
|
186
369
|
}
|
|
187
370
|
if (context.bitcoind.health !== "ready") {
|
|
188
371
|
throw new Error(`${errorPrefix}_bitcoind_${context.bitcoind.health.replaceAll("-", "_")}`);
|
|
@@ -197,6 +380,35 @@ export function assertWalletMutationContextReady(context, errorPrefix) {
|
|
|
197
380
|
throw new Error(`${errorPrefix}_core_replica_not_ready`);
|
|
198
381
|
}
|
|
199
382
|
}
|
|
383
|
+
export function assertWalletBitcoinTransferContextReady(context, errorPrefix) {
|
|
384
|
+
if (context.localState.availability === "uninitialized") {
|
|
385
|
+
throw new Error("wallet_uninitialized");
|
|
386
|
+
}
|
|
387
|
+
if (context.localState.clientPasswordReadiness === "setup-required") {
|
|
388
|
+
throw new Error("wallet_client_password_setup_required");
|
|
389
|
+
}
|
|
390
|
+
if (context.localState.clientPasswordReadiness === "migration-required") {
|
|
391
|
+
throw new Error("wallet_client_password_migration_required");
|
|
392
|
+
}
|
|
393
|
+
if (context.localState.unlockRequired) {
|
|
394
|
+
throw new Error("wallet_client_password_locked");
|
|
395
|
+
}
|
|
396
|
+
if (context.localState.availability === "local-state-corrupt") {
|
|
397
|
+
throw new Error("local-state-corrupt");
|
|
398
|
+
}
|
|
399
|
+
if (context.localState.availability !== "ready" || context.localState.state === null) {
|
|
400
|
+
throw new Error("wallet_secret_provider_unavailable");
|
|
401
|
+
}
|
|
402
|
+
if (context.bitcoind.health !== "ready") {
|
|
403
|
+
throw new Error(`${errorPrefix}_bitcoind_${context.bitcoind.health.replaceAll("-", "_")}`);
|
|
404
|
+
}
|
|
405
|
+
if (context.nodeHealth !== "synced") {
|
|
406
|
+
throw new Error(`${errorPrefix}_node_${context.nodeHealth.replaceAll("-", "_")}`);
|
|
407
|
+
}
|
|
408
|
+
if (context.nodeStatus?.walletReplica?.proofStatus !== "ready") {
|
|
409
|
+
throw new Error(`${errorPrefix}_core_replica_not_ready`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
200
412
|
export async function pauseMiningForWalletMutation(options) {
|
|
201
413
|
return requestMiningGenerationPreemption({
|
|
202
414
|
paths: options.paths,
|
|
@@ -204,15 +416,9 @@ export async function pauseMiningForWalletMutation(options) {
|
|
|
204
416
|
});
|
|
205
417
|
}
|
|
206
418
|
export async function buildWalletMutationTransaction(options) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
state: options.state,
|
|
211
|
-
fixedInputs: options.plan.fixedInputs,
|
|
212
|
-
temporarilyUnlockedOutpoints: options.temporarilyUnlockedPolicyOutpoints,
|
|
213
|
-
});
|
|
214
|
-
const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, 1))
|
|
215
|
-
.filter((entry) => isSpendableConfirmedFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex));
|
|
419
|
+
const availableFundingMinConf = options.availableFundingMinConf ?? 1;
|
|
420
|
+
const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, availableFundingMinConf))
|
|
421
|
+
.filter((entry) => isSpendableFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex, availableFundingMinConf));
|
|
216
422
|
const availableFundingValueByKey = new Map(availableFundingUtxos.map((entry) => [
|
|
217
423
|
outpointKey({ txid: entry.txid, vout: entry.vout }),
|
|
218
424
|
btcNumberToSats(entry.amount),
|
|
@@ -224,58 +430,56 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
224
430
|
...availableFundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout })),
|
|
225
431
|
]),
|
|
226
432
|
};
|
|
227
|
-
const
|
|
228
|
-
let temporaryBuilderLockedOutpoints = [];
|
|
433
|
+
const temporaryBuilderLockedOutpoints = [];
|
|
229
434
|
try {
|
|
230
435
|
const funded = await options.rpc.walletCreateFundedPsbt(options.walletName, options.plan.fixedInputs, options.plan.outputs, 0, {
|
|
231
436
|
add_inputs: true,
|
|
232
437
|
include_unsafe: false,
|
|
233
438
|
minconf: 1,
|
|
234
439
|
changeAddress: options.plan.changeAddress,
|
|
235
|
-
changePosition: options.plan.changePosition,
|
|
236
|
-
lockUnspents:
|
|
440
|
+
...(options.plan.changePosition == null ? {} : { changePosition: options.plan.changePosition }),
|
|
441
|
+
lockUnspents: false,
|
|
237
442
|
fee_rate: options.feeRate ?? DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB,
|
|
238
443
|
replaceable: true,
|
|
239
444
|
subtractFeeFromOutputs: [],
|
|
240
445
|
});
|
|
241
|
-
const lockedAfter = await options.rpc.listLockUnspent(options.walletName);
|
|
242
|
-
temporaryBuilderLockedOutpoints = diffTemporaryLockedOutpoints(lockedBefore, lockedAfter);
|
|
243
446
|
const decoded = await options.rpc.decodePsbt(funded.psbt);
|
|
244
447
|
options.validateFundedDraft(decoded, funded, validationPlan);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
448
|
+
let signed;
|
|
449
|
+
let finalized;
|
|
450
|
+
let rawHex;
|
|
451
|
+
let decodedRaw;
|
|
452
|
+
({ signed, finalized, rawHex, decodedRaw } = await withUnlockedManagedCoreWallet({
|
|
453
|
+
rpc: options.rpc,
|
|
454
|
+
walletName: options.walletName,
|
|
455
|
+
internalPassphrase: options.state.managedCoreWallet.internalPassphrase,
|
|
456
|
+
timeoutSeconds: MANAGED_CORE_WALLET_UNLOCK_TIMEOUT_SECONDS,
|
|
457
|
+
run: async () => {
|
|
458
|
+
const signed = await options.rpc.walletProcessPsbt(options.walletName, funded.psbt, true, "DEFAULT");
|
|
459
|
+
const finalized = await options.rpc.finalizePsbt(signed.psbt, true);
|
|
460
|
+
if (!finalized.complete || finalized.hex == null) {
|
|
461
|
+
throw new Error(options.finalizeErrorCode);
|
|
462
|
+
}
|
|
463
|
+
const rawHex = finalized.hex;
|
|
464
|
+
const decodedRaw = await options.rpc.decodeRawTransaction(rawHex);
|
|
465
|
+
const mempoolResult = await options.rpc.testMempoolAccept([rawHex]);
|
|
466
|
+
const accepted = mempoolResult[0];
|
|
467
|
+
if (accepted == null || !accepted.allowed) {
|
|
468
|
+
throw new Error(`${options.mempoolRejectPrefix}_${accepted?.["reject-reason"] ?? "unknown"}`);
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
signed,
|
|
472
|
+
finalized,
|
|
473
|
+
rawHex,
|
|
474
|
+
decodedRaw,
|
|
475
|
+
};
|
|
476
|
+
},
|
|
477
|
+
}));
|
|
274
478
|
return {
|
|
275
479
|
funded,
|
|
276
480
|
decoded,
|
|
277
481
|
psbt: signed.psbt,
|
|
278
|
-
rawHex
|
|
482
|
+
rawHex,
|
|
279
483
|
txid: decodedRaw.txid,
|
|
280
484
|
wtxid: decodedRaw.hash ?? null,
|
|
281
485
|
temporaryBuilderLockedOutpoints,
|
|
@@ -283,46 +487,19 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
283
487
|
}
|
|
284
488
|
catch (error) {
|
|
285
489
|
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, temporaryBuilderLockedOutpoints);
|
|
286
|
-
if ((options.temporarilyUnlockedPolicyOutpoints?.length ?? 0) > 0) {
|
|
287
|
-
await reconcilePersistentPolicyLocks({
|
|
288
|
-
rpc: options.rpc,
|
|
289
|
-
walletName: options.walletName,
|
|
290
|
-
state: options.state,
|
|
291
|
-
fixedInputs: options.plan.fixedInputs,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
490
|
throw error;
|
|
295
491
|
}
|
|
296
492
|
}
|
|
297
493
|
export async function buildWalletMutationTransactionWithReserveFallback(options) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
rpc: options.rpc,
|
|
310
|
-
walletName: options.walletName,
|
|
311
|
-
state: options.state,
|
|
312
|
-
plan: options.plan,
|
|
313
|
-
validateFundedDraft: options.validateFundedDraft,
|
|
314
|
-
finalizeErrorCode: options.finalizeErrorCode,
|
|
315
|
-
mempoolRejectPrefix: options.mempoolRejectPrefix,
|
|
316
|
-
feeRate: options.feeRate,
|
|
317
|
-
temporarilyUnlockedPolicyOutpoints: unlockedReserveOutpoints,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
catch (error) {
|
|
321
|
-
lastError = error;
|
|
322
|
-
if ((!isInsufficientFundsError(error) && !isReserveFloorFundingError(error)) || attempt === options.reserveCandidates.length) {
|
|
323
|
-
throw error;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
throw lastError;
|
|
494
|
+
return buildWalletMutationTransaction({
|
|
495
|
+
rpc: options.rpc,
|
|
496
|
+
walletName: options.walletName,
|
|
497
|
+
state: options.state,
|
|
498
|
+
plan: options.plan,
|
|
499
|
+
validateFundedDraft: options.validateFundedDraft,
|
|
500
|
+
finalizeErrorCode: options.finalizeErrorCode,
|
|
501
|
+
mempoolRejectPrefix: options.mempoolRejectPrefix,
|
|
502
|
+
feeRate: options.feeRate,
|
|
503
|
+
availableFundingMinConf: options.availableFundingMinConf,
|
|
504
|
+
});
|
|
328
505
|
}
|
|
@@ -5,7 +5,7 @@ import type { WalletPrompter } from "../lifecycle.js";
|
|
|
5
5
|
import { type WalletRuntimePaths } from "../runtime.js";
|
|
6
6
|
import { type WalletSecretProvider } from "../state/provider.js";
|
|
7
7
|
import { openWalletReadContext } from "../read/index.js";
|
|
8
|
-
import { type WalletMutationRpcClient } from "./common.js";
|
|
8
|
+
import { type WalletMutationFeeSummary, type WalletMutationRpcClient } from "./common.js";
|
|
9
9
|
type DomainAdminKind = "endpoint" | "delegate" | "miner" | "canonical";
|
|
10
10
|
interface DomainAdminRpcClient extends WalletMutationRpcClient {
|
|
11
11
|
getBlockchainInfo(): Promise<{
|
|
@@ -55,9 +55,11 @@ export interface DomainAdminMutationResult {
|
|
|
55
55
|
recipientScriptPubKeyHex?: string | null;
|
|
56
56
|
endpointValueHex?: string | null;
|
|
57
57
|
resolved?: DomainAdminResolvedSummary | null;
|
|
58
|
+
fees: WalletMutationFeeSummary;
|
|
58
59
|
}
|
|
59
60
|
interface DomainAdminBaseOptions {
|
|
60
61
|
domainName: string;
|
|
62
|
+
feeRateSatVb?: number | null;
|
|
61
63
|
dataDir: string;
|
|
62
64
|
databasePath: string;
|
|
63
65
|
provider?: WalletSecretProvider;
|