@cogcoin/client 0.5.15 → 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.
Files changed (172) hide show
  1. package/README.md +80 -25
  2. package/dist/app-paths.d.ts +5 -6
  3. package/dist/app-paths.js +8 -16
  4. package/dist/art/balance.txt +10 -0
  5. package/dist/art/welcome.txt +16 -0
  6. package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
  7. package/dist/bitcoind/bootstrap/controller.js +53 -1
  8. package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
  9. package/dist/bitcoind/client/follow-block-times.js +1 -1
  10. package/dist/bitcoind/client/internal-types.d.ts +7 -3
  11. package/dist/bitcoind/client/managed-client.d.ts +4 -2
  12. package/dist/bitcoind/client/managed-client.js +14 -0
  13. package/dist/bitcoind/client/sync-engine.js +72 -11
  14. package/dist/bitcoind/hash-order.d.ts +4 -0
  15. package/dist/bitcoind/hash-order.js +13 -0
  16. package/dist/bitcoind/indexer-daemon-main.js +11 -3
  17. package/dist/bitcoind/normalize.js +3 -2
  18. package/dist/bitcoind/processing-start-height.d.ts +5 -0
  19. package/dist/bitcoind/processing-start-height.js +7 -0
  20. package/dist/bitcoind/progress/constants.d.ts +4 -0
  21. package/dist/bitcoind/progress/constants.js +4 -0
  22. package/dist/bitcoind/progress/controller.d.ts +2 -1
  23. package/dist/bitcoind/progress/controller.js +3 -3
  24. package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
  25. package/dist/bitcoind/progress/follow-scene.js +29 -6
  26. package/dist/bitcoind/progress/formatting.d.ts +1 -0
  27. package/dist/bitcoind/progress/formatting.js +6 -0
  28. package/dist/bitcoind/progress/train-scene.js +37 -18
  29. package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
  30. package/dist/bitcoind/progress/tty-renderer.js +8 -4
  31. package/dist/bitcoind/rpc.d.ts +2 -1
  32. package/dist/bitcoind/rpc.js +3 -0
  33. package/dist/bitcoind/types.d.ts +6 -0
  34. package/dist/bytes.d.ts +1 -0
  35. package/dist/bytes.js +3 -0
  36. package/dist/cli/art.d.ts +2 -0
  37. package/dist/cli/art.js +37 -0
  38. package/dist/cli/commands/client-admin.d.ts +2 -0
  39. package/dist/cli/commands/client-admin.js +91 -0
  40. package/dist/cli/commands/follow.js +0 -2
  41. package/dist/cli/commands/mining-admin.js +6 -47
  42. package/dist/cli/commands/mining-read.js +11 -50
  43. package/dist/cli/commands/mining-runtime.js +38 -3
  44. package/dist/cli/commands/service-runtime.js +0 -2
  45. package/dist/cli/commands/status.js +8 -2
  46. package/dist/cli/commands/sync.js +51 -4
  47. package/dist/cli/commands/wallet-admin.js +142 -136
  48. package/dist/cli/commands/wallet-mutation.js +91 -79
  49. package/dist/cli/commands/wallet-read.js +15 -18
  50. package/dist/cli/context.js +4 -14
  51. package/dist/cli/mining-format.d.ts +0 -1
  52. package/dist/cli/mining-format.js +5 -37
  53. package/dist/cli/mining-json.d.ts +0 -18
  54. package/dist/cli/mining-json.js +0 -35
  55. package/dist/cli/mutation-command-groups.d.ts +1 -2
  56. package/dist/cli/mutation-command-groups.js +0 -5
  57. package/dist/cli/mutation-json.d.ts +24 -145
  58. package/dist/cli/mutation-json.js +30 -136
  59. package/dist/cli/mutation-resolved-json.d.ts +0 -7
  60. package/dist/cli/mutation-resolved-json.js +4 -10
  61. package/dist/cli/mutation-success.d.ts +2 -0
  62. package/dist/cli/mutation-success.js +11 -1
  63. package/dist/cli/mutation-text-format.js +1 -3
  64. package/dist/cli/output.d.ts +1 -1
  65. package/dist/cli/output.js +254 -231
  66. package/dist/cli/parse.d.ts +1 -1
  67. package/dist/cli/parse.js +93 -122
  68. package/dist/cli/preview-json.d.ts +17 -120
  69. package/dist/cli/preview-json.js +14 -97
  70. package/dist/cli/prompt.js +8 -13
  71. package/dist/cli/read-json.d.ts +15 -37
  72. package/dist/cli/read-json.js +44 -140
  73. package/dist/cli/runner.js +10 -13
  74. package/dist/cli/types.d.ts +8 -17
  75. package/dist/cli/types.js +0 -2
  76. package/dist/cli/wallet-format.d.ts +1 -0
  77. package/dist/cli/wallet-format.js +205 -144
  78. package/dist/cli/workflow-hints.d.ts +3 -3
  79. package/dist/cli/workflow-hints.js +11 -8
  80. package/dist/client/default-client.d.ts +3 -1
  81. package/dist/client/default-client.js +45 -2
  82. package/dist/client/factory.js +1 -1
  83. package/dist/client/initialization.js +23 -0
  84. package/dist/client/persistence.js +5 -5
  85. package/dist/client/store-adapter.js +1 -0
  86. package/dist/sqlite/checkpoints.d.ts +1 -0
  87. package/dist/sqlite/checkpoints.js +7 -0
  88. package/dist/sqlite/store.js +14 -1
  89. package/dist/types.d.ts +1 -0
  90. package/dist/wallet/coin-control.d.ts +41 -12
  91. package/dist/wallet/coin-control.js +100 -428
  92. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  93. package/dist/wallet/descriptor-normalization.js +0 -16
  94. package/dist/wallet/lifecycle.d.ts +7 -99
  95. package/dist/wallet/lifecycle.js +513 -968
  96. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  97. package/dist/wallet/managed-core-wallet.js +20 -0
  98. package/dist/wallet/mining/constants.d.ts +5 -12
  99. package/dist/wallet/mining/constants.js +5 -12
  100. package/dist/wallet/mining/control.d.ts +1 -13
  101. package/dist/wallet/mining/control.js +45 -349
  102. package/dist/wallet/mining/index.d.ts +3 -4
  103. package/dist/wallet/mining/index.js +1 -2
  104. package/dist/wallet/mining/runner.d.ts +116 -13
  105. package/dist/wallet/mining/runner.js +885 -501
  106. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  107. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  108. package/dist/wallet/mining/sentence-protocol.js +123 -0
  109. package/dist/wallet/mining/sentences.d.ts +4 -8
  110. package/dist/wallet/mining/sentences.js +3 -52
  111. package/dist/wallet/mining/state.d.ts +11 -6
  112. package/dist/wallet/mining/state.js +7 -6
  113. package/dist/wallet/mining/types.d.ts +2 -30
  114. package/dist/wallet/mining/visualizer.d.ts +31 -3
  115. package/dist/wallet/mining/visualizer.js +135 -13
  116. package/dist/wallet/read/context.d.ts +0 -2
  117. package/dist/wallet/read/context.js +119 -140
  118. package/dist/wallet/read/filter.js +2 -11
  119. package/dist/wallet/read/index.d.ts +1 -1
  120. package/dist/wallet/read/project.js +24 -77
  121. package/dist/wallet/read/types.d.ts +10 -25
  122. package/dist/wallet/reset.d.ts +0 -1
  123. package/dist/wallet/reset.js +60 -138
  124. package/dist/wallet/root-resolution.d.ts +1 -5
  125. package/dist/wallet/root-resolution.js +0 -18
  126. package/dist/wallet/runtime.d.ts +0 -6
  127. package/dist/wallet/runtime.js +0 -8
  128. package/dist/wallet/state/client-password-agent.js +208 -0
  129. package/dist/wallet/state/client-password.d.ts +65 -0
  130. package/dist/wallet/state/client-password.js +952 -0
  131. package/dist/wallet/state/crypto.d.ts +1 -20
  132. package/dist/wallet/state/crypto.js +0 -63
  133. package/dist/wallet/state/provider.d.ts +23 -11
  134. package/dist/wallet/state/provider.js +248 -290
  135. package/dist/wallet/state/storage.d.ts +2 -2
  136. package/dist/wallet/state/storage.js +48 -16
  137. package/dist/wallet/tx/anchor.d.ts +3 -28
  138. package/dist/wallet/tx/anchor.js +349 -1250
  139. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  140. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  141. package/dist/wallet/tx/cog.d.ts +5 -1
  142. package/dist/wallet/tx/cog.js +149 -185
  143. package/dist/wallet/tx/common.d.ts +61 -8
  144. package/dist/wallet/tx/common.js +266 -146
  145. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  146. package/dist/wallet/tx/domain-admin.js +61 -99
  147. package/dist/wallet/tx/domain-market.d.ts +5 -1
  148. package/dist/wallet/tx/domain-market.js +221 -228
  149. package/dist/wallet/tx/field.d.ts +4 -10
  150. package/dist/wallet/tx/field.js +83 -924
  151. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  152. package/dist/wallet/tx/identity-selector.js +17 -35
  153. package/dist/wallet/tx/index.d.ts +3 -1
  154. package/dist/wallet/tx/index.js +2 -1
  155. package/dist/wallet/tx/register.d.ts +3 -1
  156. package/dist/wallet/tx/register.js +62 -220
  157. package/dist/wallet/tx/reputation.d.ts +3 -1
  158. package/dist/wallet/tx/reputation.js +58 -95
  159. package/dist/wallet/types.d.ts +8 -122
  160. package/package.json +5 -5
  161. package/dist/wallet/archive.d.ts +0 -4
  162. package/dist/wallet/archive.js +0 -41
  163. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  164. package/dist/wallet/mining/hook-protocol.js +0 -161
  165. package/dist/wallet/mining/hook-runner.js +0 -52
  166. package/dist/wallet/mining/hooks.d.ts +0 -38
  167. package/dist/wallet/mining/hooks.js +0 -520
  168. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  169. package/dist/wallet/state/explicit-lock.js +0 -19
  170. package/dist/wallet/state/session.d.ts +0 -12
  171. package/dist/wallet/state/session.js +0 -23
  172. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
@@ -1,20 +1,34 @@
1
- import type { RpcDecodedPsbt, RpcFinalizePsbtResult, RpcListUnspentEntry, RpcLockedUnspent, RpcTestMempoolAcceptResult, RpcTransaction, RpcVin, RpcWalletCreateFundedPsbtResult, RpcWalletTransaction, RpcWalletProcessPsbtResult } from "../../bitcoind/types.js";
1
+ import type { RpcDecodedPsbt, RpcEstimateSmartFeeResult, RpcFinalizePsbtResult, RpcListUnspentEntry, RpcLockedUnspent, RpcMempoolEntry, 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";
5
5
  import type { WalletRuntimePaths } from "../runtime.js";
6
6
  import { type MiningPreemptionHandle } from "../mining/coordination.js";
7
7
  export declare const DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB = 10;
8
+ export declare const NEXT_BLOCK_FEE_CONFIRM_TARGET = 1;
9
+ export type WalletMutationFeeSelectionSource = "custom-satvb" | "estimated-next-block-plus-one" | "fallback-default";
10
+ export interface WalletMutationFeeSelection {
11
+ feeRateSatVb: number;
12
+ source: WalletMutationFeeSelectionSource;
13
+ }
14
+ export interface WalletMutationFeeSummary extends WalletMutationFeeSelection {
15
+ feeSats: string | null;
16
+ }
8
17
  export interface MutationSender {
9
18
  localIndex: number;
10
19
  scriptPubKeyHex: string;
11
20
  address: string;
12
21
  }
22
+ export declare function isLocalWalletScript(state: WalletStateV1, scriptPubKeyHex: string | null | undefined): boolean;
23
+ export declare function createFundingMutationSender(state: WalletStateV1): MutationSender;
13
24
  export interface WalletMutationRpcClient {
14
25
  listUnspent(walletName: string, minConf?: number): Promise<RpcListUnspentEntry[]>;
15
- listLockUnspent(walletName: string): Promise<RpcLockedUnspent[]>;
16
- lockUnspent(walletName: string, unlock: boolean, outputs: RpcLockedUnspent[]): Promise<boolean>;
26
+ listLockUnspent?(walletName: string): Promise<RpcLockedUnspent[]>;
27
+ lockUnspent?(walletName: string, unlock: boolean, outputs: RpcLockedUnspent[]): Promise<boolean>;
17
28
  getTransaction?(walletName: string, txid: string): Promise<RpcWalletTransaction>;
29
+ getRawTransaction?(txid: string, verbose?: boolean): Promise<RpcTransaction>;
30
+ getMempoolEntry?(txid: string): Promise<RpcMempoolEntry>;
31
+ estimateSmartFee?(confirmTarget: number, mode: "conservative" | "economical"): Promise<RpcEstimateSmartFeeResult>;
18
32
  walletCreateFundedPsbt(walletName: string, inputs: Array<{
19
33
  txid: string;
20
34
  vout: number;
@@ -38,11 +52,44 @@ export interface BuiltWalletMutationTransaction {
38
52
  }
39
53
  export interface FixedWalletInput extends OutpointRecord {
40
54
  }
55
+ export declare function formatSatVb(value: number): string;
56
+ export declare function createWalletMutationFeeMetadata(selection: WalletMutationFeeSelection): {
57
+ selectedFeeRateSatVb: number;
58
+ feeSelectionSource: WalletMutationFeeSelectionSource;
59
+ };
60
+ export declare function resolveWalletMutationFeeSelection(options: {
61
+ rpc: Pick<WalletMutationRpcClient, "estimateSmartFee">;
62
+ feeRateSatVb?: number | null;
63
+ }): Promise<WalletMutationFeeSelection>;
64
+ export declare function createWalletMutationFeeSummary(selection: WalletMutationFeeSelection, feeSats: string | null): WalletMutationFeeSummary;
65
+ export declare function createBuiltWalletMutationFeeSummary(options: {
66
+ selection: WalletMutationFeeSelection;
67
+ built: BuiltWalletMutationTransaction;
68
+ }): WalletMutationFeeSummary;
69
+ export declare function resolvePendingMutationFeeSummary(options: {
70
+ rpc: Pick<WalletMutationRpcClient, "getMempoolEntry">;
71
+ mutation: PendingMutationRecord;
72
+ }): Promise<WalletMutationFeeSummary>;
73
+ export declare function loadAttemptedMutationFixedInputs(options: {
74
+ rpc: Pick<WalletMutationRpcClient, "getTransaction" | "getRawTransaction">;
75
+ walletName: string;
76
+ mutation: PendingMutationRecord;
77
+ }): Promise<FixedWalletInput[] | null>;
78
+ export declare function resolvePendingMutationReuseDecision(options: {
79
+ rpc: Pick<WalletMutationRpcClient, "getMempoolEntry" | "getTransaction" | "getRawTransaction">;
80
+ walletName: string;
81
+ mutation: PendingMutationRecord;
82
+ nextFeeSelection: WalletMutationFeeSelection;
83
+ }): Promise<{
84
+ reuseExisting: boolean;
85
+ fees: WalletMutationFeeSummary;
86
+ replacementFixedInputs: FixedWalletInput[] | null;
87
+ }>;
88
+ export declare function mergeFixedWalletInputs(fixedInputs: readonly FixedWalletInput[], replacementInputs: readonly FixedWalletInput[] | null): FixedWalletInput[];
41
89
  export declare function saveWalletStatePreservingUnlock(options: {
42
90
  state: WalletStateV1;
43
91
  provider: WalletSecretProvider;
44
- unlockUntilUnixMs: number;
45
- nowUnixMs: number;
92
+ nowUnixMs?: number;
46
93
  paths: WalletRuntimePaths;
47
94
  }): Promise<void>;
48
95
  export declare function formatCogAmount(value: bigint): string;
@@ -57,6 +104,8 @@ export declare function updateMutationRecord(mutation: PendingMutationRecord, st
57
104
  attemptedTxid?: string | null;
58
105
  attemptedWtxid?: string | null;
59
106
  temporaryBuilderLockedOutpoints?: OutpointRecord[];
107
+ selectedFeeRateSatVb?: number | null;
108
+ feeSelectionSource?: WalletMutationFeeSelectionSource | null;
60
109
  }): PendingMutationRecord;
61
110
  export declare function unlockTemporaryBuilderLocks(rpc: Pick<WalletMutationRpcClient, "lockUnspent">, walletName: string, outpoints: OutpointRecord[]): Promise<void>;
62
111
  export declare function diffTemporaryLockedOutpoints(before: RpcLockedUnspent[], after: RpcLockedUnspent[]): OutpointRecord[];
@@ -86,11 +135,16 @@ export declare function assertWalletMutationContextReady(context: WalletReadCont
86
135
  localState: {
87
136
  availability: "ready";
88
137
  state: WalletStateV1;
89
- unlockUntilUnixMs: number;
90
138
  };
91
139
  snapshot: NonNullable<WalletReadContext["snapshot"]>;
92
140
  model: NonNullable<WalletReadContext["model"]>;
93
141
  };
142
+ export declare function assertWalletBitcoinTransferContextReady(context: WalletReadContext, errorPrefix: string): asserts context is WalletReadContext & {
143
+ localState: {
144
+ availability: "ready";
145
+ state: WalletStateV1;
146
+ };
147
+ };
94
148
  export declare function pauseMiningForWalletMutation(options: {
95
149
  paths: WalletRuntimePaths;
96
150
  reason: string;
@@ -103,7 +157,7 @@ export declare function buildWalletMutationTransaction<TPlan>(options: {
103
157
  fixedInputs: FixedWalletInput[];
104
158
  outputs: unknown[];
105
159
  changeAddress: string;
106
- changePosition: number;
160
+ changePosition?: number | null;
107
161
  allowedFundingScriptPubKeyHex: string;
108
162
  eligibleFundingOutpointKeys: Set<string>;
109
163
  };
@@ -131,5 +185,4 @@ export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>
131
185
  mempoolRejectPrefix: string;
132
186
  feeRate?: number;
133
187
  availableFundingMinConf?: number;
134
- reserveCandidates: readonly OutpointRecord[];
135
188
  }): Promise<BuiltWalletMutationTransaction>;
@@ -1,30 +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;
8
- const MANAGED_CORE_WALLET_SIGNING_UNLOCK_TIMEOUT_SECONDS = 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
+ }
9
22
  function btcNumberToSats(value) {
10
23
  return BigInt(Math.round(value * 100_000_000));
11
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
+ }
12
31
  function valueToSats(value) {
13
32
  return typeof value === "string"
14
33
  ? BigInt(Math.round(Number(value) * 100_000_000))
15
34
  : btcNumberToSats(value);
16
35
  }
17
- function createUnlockSessionState(state, unlockUntilUnixMs, nowUnixMs) {
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) {
18
50
  return {
19
- schemaVersion: 1,
20
- walletRootId: state.walletRootId,
21
- sessionId: randomBytes(16).toString("hex"),
22
- createdAtUnixMs: nowUnixMs,
23
- unlockUntilUnixMs,
24
- sourceStateRevision: state.stateRevision,
25
- wrappedSessionKeyMaterial: createWalletSecretReference(state.walletRootId).keyId,
51
+ selectedFeeRateSatVb: selection.feeRateSatVb,
52
+ feeSelectionSource: selection.source,
26
53
  };
27
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) {
85
+ return {
86
+ feeRateSatVb: selection.feeRateSatVb,
87
+ feeSats,
88
+ source: selection.source,
89
+ };
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
+ }
28
192
  export async function saveWalletStatePreservingUnlock(options) {
29
193
  const secretReference = createWalletSecretReference(options.state.walletRootId);
30
194
  await saveWalletState({
@@ -34,10 +198,6 @@ export async function saveWalletStatePreservingUnlock(options) {
34
198
  provider: options.provider,
35
199
  secretReference,
36
200
  });
37
- await saveUnlockSession(options.paths.walletUnlockSessionPath, createUnlockSessionState(options.state, options.unlockUntilUnixMs, options.nowUnixMs), {
38
- provider: options.provider,
39
- secretReference,
40
- });
41
201
  }
42
202
  export function formatCogAmount(value) {
43
203
  const sign = value < 0n ? "-" : "";
@@ -75,10 +235,12 @@ export function updateMutationRecord(mutation, status, nowUnixMs, options = {})
75
235
  attemptedTxid: options.attemptedTxid ?? mutation.attemptedTxid,
76
236
  attemptedWtxid: options.attemptedWtxid ?? mutation.attemptedWtxid,
77
237
  temporaryBuilderLockedOutpoints: options.temporaryBuilderLockedOutpoints ?? mutation.temporaryBuilderLockedOutpoints,
238
+ selectedFeeRateSatVb: options.selectedFeeRateSatVb ?? mutation.selectedFeeRateSatVb,
239
+ feeSelectionSource: options.feeSelectionSource ?? mutation.feeSelectionSource,
78
240
  };
79
241
  }
80
242
  export async function unlockTemporaryBuilderLocks(rpc, walletName, outpoints) {
81
- if (outpoints.length === 0) {
243
+ if (outpoints.length === 0 || rpc.lockUnspent === undefined) {
82
244
  return;
83
245
  }
84
246
  await rpc.lockUnspent(walletName, true, outpoints).catch(() => undefined);
@@ -124,40 +286,18 @@ export function inputMatchesOutpoint(input, outpoint) {
124
286
  return input.txid === outpoint.txid && getDecodedInputVout(input) === outpoint.vout;
125
287
  }
126
288
  export function assertFixedInputPrefixMatches(inputs, fixedInputs, errorCode) {
127
- if (inputs.length < fixedInputs.length) {
128
- throw new Error(errorCode);
129
- }
130
- for (const [index, fixedInput] of fixedInputs.entries()) {
131
- if (!inputMatchesOutpoint(inputs[index], fixedInput)) {
132
- throw new Error(errorCode);
133
- }
134
- }
289
+ void inputs;
290
+ void fixedInputs;
291
+ void errorCode;
135
292
  }
136
293
  export function assertFundingInputsAfterFixedPrefix(options) {
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);
140
- const vout = getDecodedInputVout(input);
141
- if (scriptPubKeyHex !== options.allowedFundingScriptPubKeyHex || vout === null || typeof input.txid !== "string") {
142
- throw new Error(options.errorCode);
143
- }
144
- const key = outpointKey({
145
- txid: input.txid,
146
- vout,
147
- });
148
- if (!options.eligibleFundingOutpointKeys.has(key)) {
149
- throw new Error(options.errorCode);
150
- }
151
- }
294
+ void options;
152
295
  }
153
296
  export async function reconcilePersistentPolicyLocks(options) {
154
297
  await reconcileWalletCoinControlLocks({
155
298
  rpc: options.rpc,
156
299
  walletName: options.walletName,
157
300
  state: options.state,
158
- fixedInputs: options.fixedInputs,
159
- temporarilyUnlockedOutpoints: options.temporarilyUnlockedOutpoints,
160
- cleanupInactiveTemporaryBuilderLocks: options.cleanupInactiveTemporaryBuilderLocks,
161
301
  });
162
302
  }
163
303
  export function isBroadcastUnknownError(error) {
@@ -181,8 +321,8 @@ export function isInsufficientFundsError(error) {
181
321
  return message.includes("insufficient funds");
182
322
  }
183
323
  function isReserveFloorFundingError(error) {
184
- const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
185
- return message.includes("insufficient_funding_after_reserve");
324
+ void error;
325
+ return false;
186
326
  }
187
327
  function computeRemainingFundingValueSats(options) {
188
328
  let remaining = 0n;
@@ -212,11 +352,20 @@ export function assertWalletMutationContextReady(context, errorPrefix) {
212
352
  if (context.localState.availability === "uninitialized") {
213
353
  throw new Error("wallet_uninitialized");
214
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
+ }
215
364
  if (context.localState.availability === "local-state-corrupt") {
216
365
  throw new Error("local-state-corrupt");
217
366
  }
218
- if (context.localState.availability !== "ready" || context.localState.state === null || context.localState.unlockUntilUnixMs === null) {
219
- throw new Error("wallet_locked");
367
+ if (context.localState.availability !== "ready" || context.localState.state === null) {
368
+ throw new Error("wallet_secret_provider_unavailable");
220
369
  }
221
370
  if (context.bitcoind.health !== "ready") {
222
371
  throw new Error(`${errorPrefix}_bitcoind_${context.bitcoind.health.replaceAll("-", "_")}`);
@@ -231,6 +380,35 @@ export function assertWalletMutationContextReady(context, errorPrefix) {
231
380
  throw new Error(`${errorPrefix}_core_replica_not_ready`);
232
381
  }
233
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
+ }
234
412
  export async function pauseMiningForWalletMutation(options) {
235
413
  return requestMiningGenerationPreemption({
236
414
  paths: options.paths,
@@ -238,13 +416,6 @@ export async function pauseMiningForWalletMutation(options) {
238
416
  });
239
417
  }
240
418
  export async function buildWalletMutationTransaction(options) {
241
- await reconcilePersistentPolicyLocks({
242
- rpc: options.rpc,
243
- walletName: options.walletName,
244
- state: options.state,
245
- fixedInputs: options.plan.fixedInputs,
246
- temporarilyUnlockedOutpoints: options.temporarilyUnlockedPolicyOutpoints,
247
- });
248
419
  const availableFundingMinConf = options.availableFundingMinConf ?? 1;
249
420
  const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, availableFundingMinConf))
250
421
  .filter((entry) => isSpendableFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex, availableFundingMinConf));
@@ -259,67 +430,56 @@ export async function buildWalletMutationTransaction(options) {
259
430
  ...availableFundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout })),
260
431
  ]),
261
432
  };
262
- const lockedBefore = await options.rpc.listLockUnspent(options.walletName);
263
- let temporaryBuilderLockedOutpoints = [];
433
+ const temporaryBuilderLockedOutpoints = [];
264
434
  try {
265
435
  const funded = await options.rpc.walletCreateFundedPsbt(options.walletName, options.plan.fixedInputs, options.plan.outputs, 0, {
266
436
  add_inputs: true,
267
437
  include_unsafe: false,
268
438
  minconf: 1,
269
439
  changeAddress: options.plan.changeAddress,
270
- changePosition: options.plan.changePosition,
271
- lockUnspents: true,
440
+ ...(options.plan.changePosition == null ? {} : { changePosition: options.plan.changePosition }),
441
+ lockUnspents: false,
272
442
  fee_rate: options.feeRate ?? DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB,
273
443
  replaceable: true,
274
444
  subtractFeeFromOutputs: [],
275
445
  });
276
- const lockedAfter = await options.rpc.listLockUnspent(options.walletName);
277
- temporaryBuilderLockedOutpoints = diffTemporaryLockedOutpoints(lockedBefore, lockedAfter);
278
446
  const decoded = await options.rpc.decodePsbt(funded.psbt);
279
447
  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
448
  let signed;
292
449
  let finalized;
450
+ let rawHex;
293
451
  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
- }
306
- }
307
- finally {
308
- await options.rpc.walletLock(options.walletName).catch(() => undefined);
309
- }
310
- if ((options.temporarilyUnlockedPolicyOutpoints?.length ?? 0) > 0) {
311
- await reconcilePersistentPolicyLocks({
312
- rpc: options.rpc,
313
- walletName: options.walletName,
314
- state: options.state,
315
- fixedInputs: options.plan.fixedInputs,
316
- });
317
- }
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
+ }));
318
478
  return {
319
479
  funded,
320
480
  decoded,
321
481
  psbt: signed.psbt,
322
- rawHex: finalized.hex,
482
+ rawHex,
323
483
  txid: decodedRaw.txid,
324
484
  wtxid: decodedRaw.hash ?? null,
325
485
  temporaryBuilderLockedOutpoints,
@@ -327,59 +487,19 @@ export async function buildWalletMutationTransaction(options) {
327
487
  }
328
488
  catch (error) {
329
489
  await unlockTemporaryBuilderLocks(options.rpc, options.walletName, temporaryBuilderLockedOutpoints);
330
- if ((options.temporarilyUnlockedPolicyOutpoints?.length ?? 0) > 0) {
331
- await reconcilePersistentPolicyLocks({
332
- rpc: options.rpc,
333
- walletName: options.walletName,
334
- state: options.state,
335
- fixedInputs: options.plan.fixedInputs,
336
- });
337
- }
338
490
  throw error;
339
491
  }
340
492
  }
341
493
  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];
354
- let unlockedReserveOutpoints = [];
355
- let lastError = null;
356
- for (let attempt = 0; attempt <= reserveCandidates.length; attempt += 1) {
357
- if (attempt > 0) {
358
- unlockedReserveOutpoints = [
359
- ...unlockedReserveOutpoints,
360
- reserveCandidates[attempt - 1],
361
- ];
362
- }
363
- try {
364
- return await buildWalletMutationTransaction({
365
- rpc: options.rpc,
366
- walletName: options.walletName,
367
- state: effectiveState,
368
- plan: options.plan,
369
- validateFundedDraft: options.validateFundedDraft,
370
- finalizeErrorCode: options.finalizeErrorCode,
371
- mempoolRejectPrefix: options.mempoolRejectPrefix,
372
- feeRate: options.feeRate,
373
- availableFundingMinConf: options.availableFundingMinConf,
374
- temporarilyUnlockedPolicyOutpoints: unlockedReserveOutpoints,
375
- });
376
- }
377
- catch (error) {
378
- lastError = error;
379
- if ((!isInsufficientFundsError(error) && !isReserveFloorFundingError(error)) || attempt === reserveCandidates.length) {
380
- throw error;
381
- }
382
- }
383
- }
384
- 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
+ });
385
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;