beignet 0.2.0 → 0.3.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 (195) hide show
  1. package/README.md +284 -1
  2. package/dist/cli/beignet-node.js +1019 -47
  3. package/dist/cli/beignet-node.js.map +1 -1
  4. package/dist/cli/cli.js +1201 -16
  5. package/dist/cli/cli.js.map +1 -1
  6. package/dist/cli/config.js +21 -1
  7. package/dist/cli/config.js.map +1 -1
  8. package/dist/cli/daemon.js +342 -16
  9. package/dist/cli/daemon.js.map +1 -1
  10. package/dist/cli/errors.js +2 -0
  11. package/dist/cli/errors.js.map +1 -1
  12. package/dist/cli/openapi.js +1207 -14
  13. package/dist/cli/openapi.js.map +1 -1
  14. package/dist/cli/restore.js +89 -0
  15. package/dist/cli/restore.js.map +1 -0
  16. package/dist/cli/wallet-storage.js +34 -0
  17. package/dist/cli/wallet-storage.js.map +1 -0
  18. package/dist/electrum/index.js +105 -10
  19. package/dist/electrum/index.js.map +1 -1
  20. package/dist/lightning/advisor/fee-tuner.js +42 -0
  21. package/dist/lightning/advisor/fee-tuner.js.map +1 -0
  22. package/dist/lightning/advisor/index.js +9 -1
  23. package/dist/lightning/advisor/index.js.map +1 -1
  24. package/dist/lightning/advisor/rebalance-planner.js +55 -0
  25. package/dist/lightning/advisor/rebalance-planner.js.map +1 -0
  26. package/dist/lightning/backup/index.js +18 -0
  27. package/dist/lightning/backup/index.js.map +1 -0
  28. package/dist/lightning/backup/scb.js +42 -0
  29. package/dist/lightning/backup/scb.js.map +1 -0
  30. package/dist/lightning/chain/chain-monitor.js +27 -0
  31. package/dist/lightning/chain/chain-monitor.js.map +1 -1
  32. package/dist/lightning/chain/output-resolver.js +55 -6
  33. package/dist/lightning/chain/output-resolver.js.map +1 -1
  34. package/dist/lightning/chain/types.js +1 -0
  35. package/dist/lightning/chain/types.js.map +1 -1
  36. package/dist/lightning/channel/channel-manager.js +64 -0
  37. package/dist/lightning/channel/channel-manager.js.map +1 -1
  38. package/dist/lightning/channel/channel-state.js.map +1 -1
  39. package/dist/lightning/channel/channel.js +124 -4
  40. package/dist/lightning/channel/channel.js.map +1 -1
  41. package/dist/lightning/channel/dual-funding.js +6 -5
  42. package/dist/lightning/channel/dual-funding.js.map +1 -1
  43. package/dist/lightning/channel/types.js +3 -1
  44. package/dist/lightning/channel/types.js.map +1 -1
  45. package/dist/lightning/channel/validation.js +3 -3
  46. package/dist/lightning/channel/validation.js.map +1 -1
  47. package/dist/lightning/crypto/index.js +1 -0
  48. package/dist/lightning/crypto/index.js.map +1 -1
  49. package/dist/lightning/crypto/message-signing.js +116 -0
  50. package/dist/lightning/crypto/message-signing.js.map +1 -0
  51. package/dist/lightning/crypto/xchacha20poly1305.js +77 -0
  52. package/dist/lightning/crypto/xchacha20poly1305.js.map +1 -0
  53. package/dist/lightning/features/flags.js +1 -0
  54. package/dist/lightning/features/flags.js.map +1 -1
  55. package/dist/lightning/gossip/messages.js +37 -1
  56. package/dist/lightning/gossip/messages.js.map +1 -1
  57. package/dist/lightning/index.js +2 -1
  58. package/dist/lightning/index.js.map +1 -1
  59. package/dist/lightning/keys/wallet-keys.js +1 -0
  60. package/dist/lightning/keys/wallet-keys.js.map +1 -1
  61. package/dist/lightning/message/index.js +1 -0
  62. package/dist/lightning/message/index.js.map +1 -1
  63. package/dist/lightning/message/peer-storage.js +43 -0
  64. package/dist/lightning/message/peer-storage.js.map +1 -0
  65. package/dist/lightning/message/types.js +2 -0
  66. package/dist/lightning/message/types.js.map +1 -1
  67. package/dist/lightning/node/lightning-node.js +1583 -235
  68. package/dist/lightning/node/lightning-node.js.map +1 -1
  69. package/dist/lightning/node/types.js +18 -1
  70. package/dist/lightning/node/types.js.map +1 -1
  71. package/dist/lightning/offer/offer-manager.js +2 -5
  72. package/dist/lightning/offer/offer-manager.js.map +1 -1
  73. package/dist/lightning/offer/tlv.js +3 -1
  74. package/dist/lightning/offer/tlv.js.map +1 -1
  75. package/dist/lightning/storage/encryption.js +67 -0
  76. package/dist/lightning/storage/encryption.js.map +1 -0
  77. package/dist/lightning/storage/index.js +1 -0
  78. package/dist/lightning/storage/index.js.map +1 -1
  79. package/dist/lightning/storage/serialization.js +6 -2
  80. package/dist/lightning/storage/serialization.js.map +1 -1
  81. package/dist/lightning/storage/sqlite-storage.js +387 -33
  82. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  83. package/dist/lightning/watchtower/blob.js +135 -0
  84. package/dist/lightning/watchtower/blob.js.map +1 -0
  85. package/dist/lightning/watchtower/index.js +55 -0
  86. package/dist/lightning/watchtower/index.js.map +1 -0
  87. package/dist/lightning/watchtower/justice.js +182 -0
  88. package/dist/lightning/watchtower/justice.js.map +1 -0
  89. package/dist/lightning/watchtower/tower-connection.js +208 -0
  90. package/dist/lightning/watchtower/tower-connection.js.map +1 -0
  91. package/dist/lightning/watchtower/types.js +3 -0
  92. package/dist/lightning/watchtower/types.js.map +1 -0
  93. package/dist/lightning/watchtower/watchtower-client.js +402 -0
  94. package/dist/lightning/watchtower/watchtower-client.js.map +1 -0
  95. package/dist/lightning/watchtower/wtwire.js +201 -0
  96. package/dist/lightning/watchtower/wtwire.js.map +1 -0
  97. package/dist/shapes/electrum.js +32 -1
  98. package/dist/shapes/electrum.js.map +1 -1
  99. package/dist/shapes/wallet.js +11 -1
  100. package/dist/shapes/wallet.js.map +1 -1
  101. package/dist/transaction/index.js +163 -15
  102. package/dist/transaction/index.js.map +1 -1
  103. package/dist/types/cli/beignet-node.d.ts +144 -3
  104. package/dist/types/cli/daemon.d.ts +2 -0
  105. package/dist/types/cli/errors.d.ts +2 -0
  106. package/dist/types/cli/restore.d.ts +10 -0
  107. package/dist/types/cli/types.d.ts +228 -1
  108. package/dist/types/cli/wallet-storage.d.ts +3 -0
  109. package/dist/types/electrum/index.d.ts +12 -0
  110. package/dist/types/electrum.js +1 -0
  111. package/dist/types/electrum.js.map +1 -1
  112. package/dist/types/lightning/advisor/fee-tuner.d.ts +20 -0
  113. package/dist/types/lightning/advisor/index.d.ts +4 -0
  114. package/dist/types/lightning/advisor/rebalance-planner.d.ts +12 -0
  115. package/dist/types/lightning/backup/index.d.ts +1 -0
  116. package/dist/types/lightning/backup/scb.d.ts +24 -0
  117. package/dist/types/lightning/chain/chain-monitor.d.ts +1 -0
  118. package/dist/types/lightning/chain/types.d.ts +1 -0
  119. package/dist/types/lightning/channel/channel-manager.d.ts +7 -0
  120. package/dist/types/lightning/channel/channel-state.d.ts +2 -0
  121. package/dist/types/lightning/channel/channel.d.ts +6 -0
  122. package/dist/types/lightning/channel/dual-funding.d.ts +2 -1
  123. package/dist/types/lightning/channel/types.d.ts +2 -0
  124. package/dist/types/lightning/channel/validation.d.ts +1 -1
  125. package/dist/types/lightning/crypto/index.d.ts +1 -0
  126. package/dist/types/lightning/crypto/message-signing.d.ts +9 -0
  127. package/dist/types/lightning/crypto/xchacha20poly1305.d.ts +3 -0
  128. package/dist/types/lightning/features/flags.d.ts +1 -0
  129. package/dist/types/lightning/gossip/messages.d.ts +4 -0
  130. package/dist/types/lightning/index.d.ts +1 -0
  131. package/dist/types/lightning/keys/wallet-keys.d.ts +2 -1
  132. package/dist/types/lightning/message/index.d.ts +1 -0
  133. package/dist/types/lightning/message/peer-storage.d.ts +9 -0
  134. package/dist/types/lightning/message/types.d.ts +2 -0
  135. package/dist/types/lightning/node/lightning-node.d.ts +116 -4
  136. package/dist/types/lightning/node/types.d.ts +62 -1
  137. package/dist/types/lightning/storage/encryption.d.ts +11 -0
  138. package/dist/types/lightning/storage/index.d.ts +1 -0
  139. package/dist/types/lightning/storage/serialization.d.ts +2 -0
  140. package/dist/types/lightning/storage/sqlite-storage.d.ts +43 -3
  141. package/dist/types/lightning/storage/types.d.ts +62 -0
  142. package/dist/types/lightning/watchtower/blob.d.ts +32 -0
  143. package/dist/types/lightning/watchtower/index.d.ts +9 -0
  144. package/dist/types/lightning/watchtower/justice.d.ts +29 -0
  145. package/dist/types/lightning/watchtower/tower-connection.d.ts +34 -0
  146. package/dist/types/lightning/watchtower/types.d.ts +44 -0
  147. package/dist/types/lightning/watchtower/watchtower-client.d.ts +67 -0
  148. package/dist/types/lightning/watchtower/wtwire.d.ts +90 -0
  149. package/dist/types/shapes/electrum.d.ts +3 -1
  150. package/dist/types/transaction/index.d.ts +6 -0
  151. package/dist/types/transaction.js.map +1 -1
  152. package/dist/types/types/electrum.d.ts +3 -2
  153. package/dist/types/types/transaction.d.ts +1 -1
  154. package/dist/types/types/wallet.d.ts +78 -4
  155. package/dist/types/utils/descriptors.d.ts +2 -0
  156. package/dist/types/utils/fees.d.ts +3 -0
  157. package/dist/types/utils/helpers.d.ts +3 -1
  158. package/dist/types/utils/index.d.ts +5 -0
  159. package/dist/types/utils/multisig.d.ts +29 -0
  160. package/dist/types/utils/transaction.d.ts +7 -0
  161. package/dist/types/utils/wallet-storage-encryption.d.ts +3 -0
  162. package/dist/types/utils/wallet.d.ts +1 -1
  163. package/dist/types/utils/watch-only.d.ts +12 -0
  164. package/dist/types/wallet/constants.d.ts +1 -0
  165. package/dist/types/wallet/index.d.ts +61 -4
  166. package/dist/types/wallet.js +3 -0
  167. package/dist/types/wallet.js.map +1 -1
  168. package/dist/utils/derivation-path.js +8 -7
  169. package/dist/utils/derivation-path.js.map +1 -1
  170. package/dist/utils/descriptors.js +56 -0
  171. package/dist/utils/descriptors.js.map +1 -0
  172. package/dist/utils/electrum.js +10 -2
  173. package/dist/utils/electrum.js.map +1 -1
  174. package/dist/utils/fees.js +19 -0
  175. package/dist/utils/fees.js.map +1 -0
  176. package/dist/utils/helpers.js +22 -5
  177. package/dist/utils/helpers.js.map +1 -1
  178. package/dist/utils/index.js +5 -0
  179. package/dist/utils/index.js.map +1 -1
  180. package/dist/utils/multisig.js +90 -0
  181. package/dist/utils/multisig.js.map +1 -0
  182. package/dist/utils/transaction.js +30 -3
  183. package/dist/utils/transaction.js.map +1 -1
  184. package/dist/utils/wallet-storage-encryption.js +45 -0
  185. package/dist/utils/wallet-storage-encryption.js.map +1 -0
  186. package/dist/utils/wallet.js +3 -1
  187. package/dist/utils/wallet.js.map +1 -1
  188. package/dist/utils/watch-only.js +102 -0
  189. package/dist/utils/watch-only.js.map +1 -0
  190. package/dist/wallet/constants.js +2 -1
  191. package/dist/wallet/constants.js.map +1 -1
  192. package/dist/wallet/index.js +787 -30
  193. package/dist/wallet/index.js.map +1 -1
  194. package/docs/ROADMAP.md +292 -0
  195. package/package.json +1 -1
@@ -29,7 +29,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.Wallet = void 0;
30
30
  const bip39 = __importStar(require("bip39"));
31
31
  const bitcoin = __importStar(require("bitcoinjs-lib"));
32
- const bitcoinjs_lib_1 = require("bitcoinjs-lib");
33
32
  const bip32_1 = __importDefault(require("bip32"));
34
33
  const ecc = __importStar(require("@bitcoinerlab/secp256k1"));
35
34
  const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
@@ -42,12 +41,12 @@ const constants_1 = require("./constants");
42
41
  const conversion_1 = require("../utils/conversion");
43
42
  const bip32 = (0, bip32_1.default)(ecc);
44
43
  class Wallet {
45
- constructor({ mnemonic, passphrase, name, network = types_1.EAvailableNetworks.mainnet, addressType = types_1.EAddressType.p2wpkh, coinSelectPreference = types_1.ECoinSelectPreference.consolidate, storage, electrumOptions, onMessage = () => null, customGetAddress, customGetScriptHash, rbf = false, selectedFeeId = types_1.EFeeId.normal, disableMessages = false, disableMessagesOnCreate = false, addressTypesToMonitor = Object.values(types_1.EAddressType), gapLimitOptions = {
44
+ constructor({ mnemonic, xpub, passphrase, name, network = types_1.EAvailableNetworks.mainnet, addressType, account = 0, birthdayHeight, coinSelectPreference = types_1.ECoinSelectPreference.consolidate, storage, electrumOptions, onMessage = () => null, customGetAddress, customGetScriptHash, rbf = false, selectedFeeId = types_1.EFeeId.normal, feeEstimationSource = 'auto', disableMessages = false, disableMessagesOnCreate = false, addressTypesToMonitor = Object.values(types_1.EAddressType), gapLimitOptions = {
46
45
  lookBehind: constants_1.GAP_LIMIT,
47
46
  lookAhead: constants_1.GAP_LIMIT,
48
47
  lookBehindChange: constants_1.GAP_LIMIT_CHANGE,
49
48
  lookAheadChange: constants_1.GAP_LIMIT_CHANGE
50
- } }) {
49
+ }, multisig }) {
51
50
  this._pendingRefreshPromises = [];
52
51
  this.savingOperations = {};
53
52
  this.getReceiveAddress = async ({ addressType = this.addressType }) => {
@@ -95,7 +94,9 @@ class Wallet {
95
94
  };
96
95
  this.getBip32Interface = async () => {
97
96
  try {
98
- const network = bitcoinjs_lib_1.networks[this._network];
97
+ if (!this._mnemonic)
98
+ return (0, utils_1.err)(new utils_1.WatchOnlySigningError());
99
+ const network = (0, utils_1.getBitcoinJsNetwork)(this._network);
99
100
  const seed = await bip39.mnemonicToSeed(this._mnemonic, this._passphrase);
100
101
  const root = bip32.fromSeed(seed, network);
101
102
  return (0, utils_1.ok)(root);
@@ -104,17 +105,67 @@ class Wallet {
104
105
  return (0, utils_1.err)(e);
105
106
  }
106
107
  };
107
- if (!mnemonic)
108
+ if (!mnemonic && !xpub && !multisig) {
108
109
  throw new Error('No mnemonic specified.');
109
- if (!(0, utils_1.validateMnemonic)(mnemonic))
110
- throw new Error('Invalid mnemonic.');
110
+ }
111
111
  if (name && name.includes('-'))
112
112
  throw new Error('Wallet name cannot include a hyphen (-).');
113
+ if (!Number.isInteger(account) || account < 0) {
114
+ throw new Error('account must be a non-negative integer.');
115
+ }
116
+ if (birthdayHeight !== undefined &&
117
+ (!Number.isInteger(birthdayHeight) || birthdayHeight < 0)) {
118
+ throw new Error('birthdayHeight must be a non-negative integer.');
119
+ }
120
+ this._account = String(account);
121
+ this._birthdayHeightOption = birthdayHeight;
113
122
  this._network = network;
114
- this._mnemonic = mnemonic;
115
123
  this._passphrase = passphrase ?? '';
116
- this._seed = (0, utils_1.getSeed)(this._mnemonic, this._passphrase);
117
- this._root = bip32.fromSeed(this._seed, this.getBitcoinNetwork(this._network));
124
+ this.isWatchOnly = !mnemonic;
125
+ if (mnemonic) {
126
+ if (!(0, utils_1.validateMnemonic)(mnemonic))
127
+ throw new Error('Invalid mnemonic.');
128
+ this._mnemonic = mnemonic;
129
+ this._seed = (0, utils_1.getSeed)(this._mnemonic, this._passphrase);
130
+ this._root = bip32.fromSeed(this._seed, this.getBitcoinNetwork(this._network));
131
+ }
132
+ if (multisig) {
133
+ if (addressType && addressType !== types_1.EAddressType.p2wsh) {
134
+ throw new Error('Multisig wallets only support the p2wsh address type.');
135
+ }
136
+ const setup = this._initMultisig(multisig);
137
+ this._multisig = setup.config;
138
+ this.id = setup.id;
139
+ this.addressType = types_1.EAddressType.p2wsh;
140
+ addressTypesToMonitor = [types_1.EAddressType.p2wsh];
141
+ }
142
+ else if (mnemonic) {
143
+ if (addressType === types_1.EAddressType.p2wsh) {
144
+ throw new Error('p2wsh is a multisig address type. Use Wallet.createMultisig.');
145
+ }
146
+ this.id = (0, utils_1.generateWalletId)(this._seed);
147
+ this.addressType = addressType ?? types_1.EAddressType.p2wpkh;
148
+ }
149
+ else {
150
+ const parseRes = (0, utils_1.parseExtendedPublicKey)(xpub, this._network);
151
+ if (parseRes.isErr())
152
+ throw parseRes.error;
153
+ const { node, addressType: inferredType } = parseRes.value;
154
+ if (addressType && inferredType && addressType !== inferredType) {
155
+ throw new Error(`The provided xpub prefix implies ${inferredType}, but addressType ${addressType} was requested.`);
156
+ }
157
+ const resolvedType = addressType ?? inferredType ?? types_1.EAddressType.p2wpkh;
158
+ if (resolvedType === types_1.EAddressType.p2wsh) {
159
+ throw new Error('p2wsh is a multisig address type. Use Wallet.createMultisig with every cosigner xpub.');
160
+ }
161
+ this._accountNode = node;
162
+ this.addressType = resolvedType;
163
+ this.id = (0, utils_1.generateWalletId)(Buffer.concat([node.publicKey, node.chainCode]));
164
+ addressTypesToMonitor = [this.addressType];
165
+ }
166
+ if (!this._multisig) {
167
+ addressTypesToMonitor = addressTypesToMonitor.filter((type) => type !== types_1.EAddressType.p2wsh);
168
+ }
118
169
  this._data = (0, utils_1.getDefaultWalletData)();
119
170
  this._getData = storage?.getData ?? utils_1.getDataFallback;
120
171
  this._setData = storage?.setData;
@@ -123,9 +174,7 @@ class Wallet {
123
174
  this._customGetAddress = customGetAddress;
124
175
  if (customGetScriptHash)
125
176
  this._customGetScriptHash = customGetScriptHash;
126
- this.id = (0, utils_1.generateWalletId)(this._seed);
127
177
  this.name = name ?? this.id;
128
- this.addressType = addressType;
129
178
  this.coinSelectPreference = coinSelectPreference;
130
179
  this.transaction = new transaction_1.Transaction({
131
180
  wallet: this
@@ -145,6 +194,7 @@ class Wallet {
145
194
  });
146
195
  this.rbf = rbf;
147
196
  this.selectedFeeId = selectedFeeId;
197
+ this.feeEstimationSource = feeEstimationSource;
148
198
  this.isRefreshing = false;
149
199
  this.isSwitchingNetworks = false;
150
200
  this.addressTypesToMonitor = addressTypesToMonitor;
@@ -185,6 +235,23 @@ class Wallet {
185
235
  get network() {
186
236
  return this._network;
187
237
  }
238
+ get account() {
239
+ return Number(this._account);
240
+ }
241
+ get birthdayHeight() {
242
+ return this._data.birthdayHeight ?? 0;
243
+ }
244
+ get isMultisig() {
245
+ return !!this._multisig;
246
+ }
247
+ get multisigInfo() {
248
+ if (!this._multisig)
249
+ return undefined;
250
+ return {
251
+ threshold: this._multisig.threshold,
252
+ totalCosigners: this._multisig.cosigners.length
253
+ };
254
+ }
188
255
  static async create(params) {
189
256
  try {
190
257
  const wallet = new Wallet(params);
@@ -201,6 +268,136 @@ class Wallet {
201
268
  return (0, utils_1.err)(e);
202
269
  }
203
270
  }
271
+ static async createWatchOnly(params) {
272
+ if (!params?.xpub)
273
+ return (0, utils_1.err)('No xpub provided.');
274
+ return await Wallet.create(params);
275
+ }
276
+ static async createMultisig(params) {
277
+ if (!params)
278
+ return (0, utils_1.err)('No multisig parameters provided.');
279
+ const { threshold, cosigners, ourXpub, ...walletParams } = params;
280
+ return await Wallet.create({
281
+ ...walletParams,
282
+ addressType: types_1.EAddressType.p2wsh,
283
+ multisig: { threshold, cosigners, ourXpub }
284
+ });
285
+ }
286
+ _initMultisig(multisig) {
287
+ const { threshold, cosigners, ourXpub } = multisig;
288
+ if (!Number.isInteger(threshold) || threshold < 1) {
289
+ throw new Error('Multisig threshold must be a positive integer.');
290
+ }
291
+ const keyId = (node) => Buffer.concat([node.publicKey, node.chainCode]).toString('hex');
292
+ const entries = [];
293
+ const addCosigner = (xpub) => {
294
+ const res = (0, utils_1.parseExtendedPublicKey)(xpub, this._network);
295
+ if (res.isErr())
296
+ throw res.error;
297
+ const node = res.value.node;
298
+ if (entries.some((e) => keyId(e.node) === keyId(node))) {
299
+ throw new Error(`Duplicate cosigner xpub provided: ${xpub}`);
300
+ }
301
+ entries.push({ node, isOurs: false });
302
+ };
303
+ for (const xpub of cosigners ?? [])
304
+ addCosigner(xpub);
305
+ if (this._root) {
306
+ const coinType = this._network === types_1.EAvailableNetworks.bitcoin ? '0' : '1';
307
+ const ourNode = this._root
308
+ .derivePath(`m/48'/${coinType}'/${this._account}'/2'`)
309
+ .neutered();
310
+ if (ourXpub) {
311
+ const parsed = (0, utils_1.parseExtendedPublicKey)(ourXpub, this._network);
312
+ if (parsed.isErr())
313
+ throw parsed.error;
314
+ if (keyId(parsed.value.node) !== keyId(ourNode)) {
315
+ throw new Error(`ourXpub does not match the account xpub derived from the mnemonic at m/48'/${coinType}'/${this._account}'/2'.`);
316
+ }
317
+ }
318
+ const existing = entries.find((e) => keyId(e.node) === keyId(ourNode));
319
+ if (existing) {
320
+ existing.isOurs = true;
321
+ }
322
+ else {
323
+ entries.push({ node: ourNode, isOurs: true });
324
+ }
325
+ }
326
+ else if (ourXpub) {
327
+ addCosigner(ourXpub);
328
+ }
329
+ const n = entries.length;
330
+ if (threshold > n) {
331
+ throw new Error(`Multisig threshold (${threshold}) exceeds the number of cosigners (${n}).`);
332
+ }
333
+ if (n > 15) {
334
+ throw new Error(`Multisig supports at most 15 cosigners, received ${n}.`);
335
+ }
336
+ entries.sort((a, b) => a.node.publicKey.compare(b.node.publicKey));
337
+ const id = (0, utils_1.generateWalletId)(Buffer.concat([
338
+ Buffer.from('sortedmulti'),
339
+ Buffer.from([threshold, n]),
340
+ ...entries.map((e) => Buffer.concat([e.node.publicKey, e.node.chainCode]))
341
+ ]));
342
+ return { config: { threshold, cosigners: entries }, id };
343
+ }
344
+ getMultisigPayment(path) {
345
+ try {
346
+ if (!this._multisig) {
347
+ return (0, utils_1.err)('This wallet is not a multisig wallet.');
348
+ }
349
+ const parsed = (0, utils_1.parseBip48Path)(path);
350
+ if (parsed.isErr())
351
+ return (0, utils_1.err)(parsed.error.message);
352
+ const { coinType, account, change, index } = parsed.value;
353
+ const expectedCoin = this._network === types_1.EAvailableNetworks.bitcoin ? '0' : '1';
354
+ if (coinType !== expectedCoin || account !== this._account) {
355
+ return (0, utils_1.err)(`Path ${path} does not match this wallet (expected m/48'/${expectedCoin}'/${this._account}'/2'/change/index).`);
356
+ }
357
+ const payment = (0, utils_1.buildSortedMultisigPayment)({
358
+ threshold: this._multisig.threshold,
359
+ cosignerNodes: this._multisig.cosigners.map((c) => c.node),
360
+ change,
361
+ index,
362
+ network: this.getBitcoinNetwork()
363
+ });
364
+ if (payment.isErr())
365
+ return (0, utils_1.err)(payment.error.message);
366
+ const perCosigner = this._multisig.cosigners
367
+ .map((c) => {
368
+ let masterFingerprint;
369
+ if (c.isOurs && this._root) {
370
+ masterFingerprint = Buffer.from(this._root.fingerprint);
371
+ }
372
+ else {
373
+ masterFingerprint = Buffer.alloc(4);
374
+ masterFingerprint.writeUInt32BE(c.node.parentFingerprint ?? 0, 0);
375
+ }
376
+ return {
377
+ masterFingerprint,
378
+ path,
379
+ pubkey: c.node.derive(change).derive(index).publicKey,
380
+ isOurs: c.isOurs
381
+ };
382
+ })
383
+ .sort((a, b) => a.pubkey.compare(b.pubkey));
384
+ const ours = perCosigner.find((d) => d.isOurs);
385
+ return (0, utils_1.ok)({
386
+ address: payment.value.address,
387
+ output: payment.value.output,
388
+ witnessScript: payment.value.witnessScript,
389
+ publicKey: ours && this._root ? ours.pubkey.toString('hex') : '',
390
+ derivations: perCosigner.map((d) => ({
391
+ masterFingerprint: d.masterFingerprint,
392
+ path: d.path,
393
+ pubkey: d.pubkey
394
+ }))
395
+ });
396
+ }
397
+ catch (e) {
398
+ return (0, utils_1.err)(e);
399
+ }
400
+ }
204
401
  async stop() {
205
402
  try {
206
403
  if (this.isRefreshing) {
@@ -219,6 +416,12 @@ class Wallet {
219
416
  this.coinSelectPreference = coinSelectPreference;
220
417
  }
221
418
  async switchNetwork(network, servers) {
419
+ if (this._multisig) {
420
+ return (0, utils_1.err)('Multisig wallets cannot switch networks. Create a new multisig wallet with cosigner xpubs for the target network.');
421
+ }
422
+ if (this.isWatchOnly) {
423
+ return (0, utils_1.err)('Watch-only wallets cannot switch networks. Create a new watch-only wallet with an xpub for the target network.');
424
+ }
222
425
  this.isSwitchingNetworks = true;
223
426
  await this.electrum.disconnect();
224
427
  this._network = network;
@@ -227,6 +430,8 @@ class Wallet {
227
430
  ...this,
228
431
  mnemonic: this._mnemonic,
229
432
  passphrase: this._passphrase,
433
+ account: Number(this._account),
434
+ birthdayHeight: this._birthdayHeightOption,
230
435
  network,
231
436
  electrumOptions: {
232
437
  servers,
@@ -249,6 +454,12 @@ class Wallet {
249
454
  return (0, utils_1.ok)(this);
250
455
  }
251
456
  async updateAddressType(addressType) {
457
+ if (addressType === types_1.EAddressType.p2wsh && !this._multisig) {
458
+ throw new Error('p2wsh is a multisig address type. Use Wallet.createMultisig.');
459
+ }
460
+ if (this._multisig && addressType !== types_1.EAddressType.p2wsh) {
461
+ throw new Error('Multisig wallets only support the p2wsh address type.');
462
+ }
252
463
  this.addressType = addressType;
253
464
  if (!this.addressTypesToMonitor.includes(this.addressType)) {
254
465
  this.addressTypesToMonitor.push(this.addressType);
@@ -324,6 +535,7 @@ class Wallet {
324
535
  if (walletDataResponse.isErr())
325
536
  return (0, utils_1.err)(walletDataResponse.error.message);
326
537
  this._data = walletDataResponse.value;
538
+ await this._applyBirthdayHeightOption();
327
539
  return (0, utils_1.ok)(true);
328
540
  }
329
541
  catch (e) {
@@ -346,7 +558,19 @@ class Wallet {
346
558
  return (0, utils_1.ok)("ID's match, it's safe to continue.");
347
559
  }
348
560
  getWalletDataKey(key) {
349
- return (0, utils_1.getWalletDataStorageKey)(this.name, this._network, key);
561
+ return (0, utils_1.getWalletDataStorageKey)(this.name, this._network, key, Number(this._account));
562
+ }
563
+ async _applyBirthdayHeightOption() {
564
+ if (this._birthdayHeightOption === undefined)
565
+ return;
566
+ const stored = this._data.birthdayHeight ?? 0;
567
+ const next = stored > 0
568
+ ? Math.min(stored, this._birthdayHeightOption)
569
+ : this._birthdayHeightOption;
570
+ if (next !== stored) {
571
+ this._data.birthdayHeight = next;
572
+ await this.saveWalletData('birthdayHeight', next);
573
+ }
350
574
  }
351
575
  async getWalletData() {
352
576
  try {
@@ -413,6 +637,12 @@ class Wallet {
413
637
  case 'feeEstimates':
414
638
  walletData[key] = data;
415
639
  break;
640
+ case 'addressLabels':
641
+ walletData[key] = data;
642
+ break;
643
+ case 'birthdayHeight':
644
+ walletData[key] = data;
645
+ break;
416
646
  default:
417
647
  console.log(`Unhandled key in getWalletData: ${key}`);
418
648
  break;
@@ -428,13 +658,77 @@ class Wallet {
428
658
  getBitcoinNetwork(network) {
429
659
  if (!network)
430
660
  network = this._network;
431
- return bitcoin.networks[network];
661
+ return (0, utils_1.getBitcoinJsNetwork)(network);
432
662
  }
433
663
  isValid(mnemonic) {
434
- return mnemonic === this._mnemonic && (0, utils_1.validateMnemonic)(mnemonic);
664
+ return (!!this._mnemonic &&
665
+ mnemonic === this._mnemonic &&
666
+ (0, utils_1.validateMnemonic)(mnemonic));
667
+ }
668
+ _derivePathKeyPair(path) {
669
+ if (this._root)
670
+ return this._root.derivePath(path);
671
+ return this._deriveWatchOnlyNode(path);
672
+ }
673
+ _deriveWatchOnlyNode(path) {
674
+ if (!this._accountNode) {
675
+ throw new Error('Watch-only account node is unavailable.');
676
+ }
677
+ const segments = path.replace(/'/g, '').split('/');
678
+ if (segments.length !== 6) {
679
+ throw new Error(`Watch-only wallets require a full path (m/purpose'/coin'/account'/change/index): ${path}`);
680
+ }
681
+ const expectedPathRes = (0, utils_1.getKeyDerivationPathObject)({
682
+ path: shapes_1.addressTypes[this.addressType].path,
683
+ network: this._network
684
+ });
685
+ if (expectedPathRes.isErr())
686
+ throw expectedPathRes.error;
687
+ if (segments[1] !== expectedPathRes.value.purpose) {
688
+ throw new Error(`This watch-only wallet only derives ${this.addressType} paths (purpose ${expectedPathRes.value.purpose}'), received: ${path}`);
689
+ }
690
+ const change = Number(segments[4]);
691
+ const index = Number(segments[5]);
692
+ if (!Number.isInteger(change) ||
693
+ !Number.isInteger(index) ||
694
+ change < 0 ||
695
+ index < 0) {
696
+ throw new Error(`Invalid change/index segments in path: ${path}`);
697
+ }
698
+ return this._accountNode.derive(change).derive(index);
699
+ }
700
+ derivePublicNode(path) {
701
+ try {
702
+ if (this._root)
703
+ return (0, utils_1.ok)(this._root.derivePath(path).neutered());
704
+ return (0, utils_1.ok)(this._deriveWatchOnlyNode(path));
705
+ }
706
+ catch (e) {
707
+ return (0, utils_1.err)(e);
708
+ }
709
+ }
710
+ getMasterFingerprint() {
711
+ if (this._root)
712
+ return Buffer.from(this._root.fingerprint);
713
+ const fingerprint = Buffer.alloc(4);
714
+ fingerprint.writeUInt32BE(this._accountNode?.parentFingerprint ?? 0, 0);
715
+ return fingerprint;
435
716
  }
436
717
  async _getAddress(path, addressType) {
437
718
  try {
719
+ if (addressType === types_1.EAddressType.p2wsh) {
720
+ if (!this._multisig) {
721
+ return (0, utils_1.err)('p2wsh addresses require a multisig wallet (Wallet.createMultisig).');
722
+ }
723
+ const payment = this.getMultisigPayment(path);
724
+ if (payment.isErr())
725
+ return (0, utils_1.err)(payment.error.message);
726
+ return (0, utils_1.ok)({
727
+ address: payment.value.address,
728
+ publicKey: payment.value.publicKey,
729
+ path
730
+ });
731
+ }
438
732
  if (this._customGetAddress) {
439
733
  const data = {
440
734
  path,
@@ -445,7 +739,7 @@ class Wallet {
445
739
  if (res.isErr())
446
740
  return (0, utils_1.err)(res.error.message);
447
741
  }
448
- const keyPair = this._root.derivePath(path);
742
+ const keyPair = this._derivePathKeyPair(path);
449
743
  const network = this.getBitcoinNetwork(this._network);
450
744
  const addressInfo = (0, utils_1.getAddressFromKeyPair)({
451
745
  keyPair,
@@ -473,6 +767,7 @@ class Wallet {
473
767
  addressType,
474
768
  changeAddress,
475
769
  index,
770
+ accountType: this._account,
476
771
  network: this._network
477
772
  });
478
773
  if (pathRes.isErr()) {
@@ -559,6 +854,8 @@ class Wallet {
559
854
  return (0, utils_1.getScriptHash)({ address, network });
560
855
  }
561
856
  getPrivateKey(path) {
857
+ if (!this._root)
858
+ throw new utils_1.WatchOnlySigningError();
562
859
  const keyPair = this._root.derivePath(path);
563
860
  return keyPair.toWIF();
564
861
  }
@@ -582,6 +879,7 @@ class Wallet {
582
879
  if (keyDerivationPathResponse.isErr())
583
880
  return (0, utils_1.err)(keyDerivationPathResponse.error.message);
584
881
  keyDerivationPath = keyDerivationPathResponse.value;
882
+ keyDerivationPath.account = this._account;
585
883
  }
586
884
  const addresses = {};
587
885
  const changeAddresses = {};
@@ -681,6 +979,7 @@ class Wallet {
681
979
  return (0, utils_1.err)(result.error.message);
682
980
  }
683
981
  const { pathObject: keyDerivationPath } = result.value;
982
+ keyDerivationPath.account = this._account;
684
983
  let addressIndex = currentWallet.addressIndex[addressType];
685
984
  let lastUsedAddressIndex = currentWallet.lastUsedAddressIndex[addressType];
686
985
  let changeAddressIndex = currentWallet.changeAddressIndex[addressType];
@@ -941,6 +1240,7 @@ class Wallet {
941
1240
  return (0, utils_1.err)(keyDerivationPathResponse.error.message);
942
1241
  }
943
1242
  keyDerivationPath = keyDerivationPathResponse.value;
1243
+ keyDerivationPath.account = this._account;
944
1244
  }
945
1245
  const generatedAddresses = await this.generateAddresses({
946
1246
  addressAmount,
@@ -1141,6 +1441,7 @@ class Wallet {
1141
1441
  return (0, utils_1.err)(keyDerivationPathResponse.error.message);
1142
1442
  }
1143
1443
  keyDerivationPath = keyDerivationPathResponse.value;
1444
+ keyDerivationPath.account = this._account;
1144
1445
  }
1145
1446
  const addresses = currentWallet.addresses[addressType];
1146
1447
  const currentAddressIndex = currentWallet.addressIndex[addressType].index;
@@ -1234,6 +1535,174 @@ class Wallet {
1234
1535
  listUtxos() {
1235
1536
  return this.data.utxos;
1236
1537
  }
1538
+ isUtxoFrozen(txid, index) {
1539
+ return this.data.blacklistedUtxos.some((frozen) => frozen.tx_hash === txid && frozen.tx_pos === index);
1540
+ }
1541
+ async freezeUtxo({ txid, index }) {
1542
+ if (typeof txid !== 'string' || !/^[0-9a-fA-F]{64}$/.test(txid)) {
1543
+ return (0, utils_1.err)('txid must be a 64-character hex string.');
1544
+ }
1545
+ if (!Number.isInteger(index) || index < 0) {
1546
+ return (0, utils_1.err)('index must be a non-negative integer.');
1547
+ }
1548
+ const utxo = this.data.utxos.find((u) => u.tx_hash === txid && u.tx_pos === index);
1549
+ if (!utxo) {
1550
+ return (0, utils_1.err)(`UTXO ${txid}:${index} is not known to this wallet.`);
1551
+ }
1552
+ if (this.isUtxoFrozen(txid, index)) {
1553
+ return (0, utils_1.ok)(`UTXO ${txid}:${index} is already frozen.`);
1554
+ }
1555
+ const { keyPair, ...frozen } = utxo;
1556
+ void keyPair;
1557
+ this._data.blacklistedUtxos.push(frozen);
1558
+ await this.saveWalletData('blacklistedUtxos', this._data.blacklistedUtxos);
1559
+ return (0, utils_1.ok)(`UTXO ${txid}:${index} frozen.`);
1560
+ }
1561
+ async unfreezeUtxo({ txid, index }) {
1562
+ const before = this._data.blacklistedUtxos.length;
1563
+ this._data.blacklistedUtxos = this._data.blacklistedUtxos.filter((frozen) => !(frozen.tx_hash === txid && frozen.tx_pos === index));
1564
+ if (this._data.blacklistedUtxos.length === before) {
1565
+ return (0, utils_1.err)(`UTXO ${txid}:${index} is not frozen.`);
1566
+ }
1567
+ await this.saveWalletData('blacklistedUtxos', this._data.blacklistedUtxos);
1568
+ return (0, utils_1.ok)(`UTXO ${txid}:${index} unfrozen.`);
1569
+ }
1570
+ listFrozenUtxos() {
1571
+ return this.data.blacklistedUtxos;
1572
+ }
1573
+ getBalanceBreakdown() {
1574
+ const total = this.getBalance();
1575
+ const frozen = this.data.utxos.reduce((acc, utxo) => {
1576
+ return this.isUtxoFrozen(utxo.tx_hash, utxo.tx_pos)
1577
+ ? acc + utxo.value
1578
+ : acc;
1579
+ }, 0);
1580
+ return { total, spendable: total - frozen, frozen };
1581
+ }
1582
+ async setAddressLabel(address, label) {
1583
+ if (!address || !this.validateAddress(address)) {
1584
+ return (0, utils_1.err)(`Invalid ${this._network} address: ${address}`);
1585
+ }
1586
+ if (typeof label !== 'string') {
1587
+ return (0, utils_1.err)('label must be a string.');
1588
+ }
1589
+ if (label.length > 255) {
1590
+ return (0, utils_1.err)('label must be 255 characters or fewer.');
1591
+ }
1592
+ if (label === '') {
1593
+ delete this._data.addressLabels[address];
1594
+ }
1595
+ else {
1596
+ this._data.addressLabels[address] = label;
1597
+ }
1598
+ await this.saveWalletData('addressLabels', this._data.addressLabels);
1599
+ return (0, utils_1.ok)(label === '' ? 'Label removed.' : 'Label saved.');
1600
+ }
1601
+ getAddressLabel(address) {
1602
+ return this.data.addressLabels[address];
1603
+ }
1604
+ listAddressLabels() {
1605
+ return { ...this.data.addressLabels };
1606
+ }
1607
+ exportDescriptors() {
1608
+ try {
1609
+ const fingerprint = this.getMasterFingerprint().toString('hex');
1610
+ const descriptors = [];
1611
+ const wrap = (addressType, keyExpr) => {
1612
+ switch (addressType) {
1613
+ case types_1.EAddressType.p2pkh:
1614
+ return `pkh(${keyExpr})`;
1615
+ case types_1.EAddressType.p2sh:
1616
+ return `sh(wpkh(${keyExpr}))`;
1617
+ case types_1.EAddressType.p2wpkh:
1618
+ return `wpkh(${keyExpr})`;
1619
+ case types_1.EAddressType.p2tr:
1620
+ return `tr(${keyExpr})`;
1621
+ case types_1.EAddressType.p2wsh:
1622
+ return `wsh(${keyExpr})`;
1623
+ }
1624
+ };
1625
+ if (this._multisig) {
1626
+ const coinType = this._network === types_1.EAvailableNetworks.bitcoin ? '0' : '1';
1627
+ const keyExpr = (chain) => {
1628
+ const keys = this._multisig.cosigners.map((cosigner) => {
1629
+ const xpub = this._toStandardBase58(cosigner.node);
1630
+ let origin;
1631
+ if (cosigner.isOurs && this._root) {
1632
+ origin = `[${fingerprint}/48h/${coinType}h/${this._account}h/2h]`;
1633
+ }
1634
+ else {
1635
+ const parentFp = Buffer.alloc(4);
1636
+ parentFp.writeUInt32BE(cosigner.node.parentFingerprint ?? 0, 0);
1637
+ origin = `[${parentFp.toString('hex')}]`;
1638
+ }
1639
+ return `${origin}${xpub}/${chain}/*`;
1640
+ });
1641
+ return `sortedmulti(${this._multisig.threshold},${keys.join(',')})`;
1642
+ };
1643
+ descriptors.push({
1644
+ addressType: types_1.EAddressType.p2wsh,
1645
+ external: (0, utils_1.appendDescriptorChecksum)(`wsh(${keyExpr(0)})`),
1646
+ internal: (0, utils_1.appendDescriptorChecksum)(`wsh(${keyExpr(1)})`)
1647
+ });
1648
+ }
1649
+ else if (this.isWatchOnly) {
1650
+ if (!this._accountNode) {
1651
+ return (0, utils_1.err)('Watch-only account node is unavailable.');
1652
+ }
1653
+ const xpub = this._toStandardBase58(this._accountNode);
1654
+ descriptors.push({
1655
+ addressType: this.addressType,
1656
+ external: (0, utils_1.appendDescriptorChecksum)(wrap(this.addressType, `${xpub}/0/*`)),
1657
+ internal: (0, utils_1.appendDescriptorChecksum)(wrap(this.addressType, `${xpub}/1/*`))
1658
+ });
1659
+ }
1660
+ else {
1661
+ if (!this._root)
1662
+ return (0, utils_1.err)('Wallet root is unavailable.');
1663
+ const coinType = this._network === types_1.EAvailableNetworks.bitcoin ? '0' : '1';
1664
+ const singleSigTypes = Object.values(types_1.EAddressType).filter((type) => type !== types_1.EAddressType.p2wsh);
1665
+ for (const addressType of singleSigTypes) {
1666
+ const pathRes = (0, utils_1.getKeyDerivationPathObject)({
1667
+ path: shapes_1.addressTypes[addressType].path,
1668
+ network: this._network
1669
+ });
1670
+ if (pathRes.isErr())
1671
+ return (0, utils_1.err)(pathRes.error.message);
1672
+ const purpose = pathRes.value.purpose;
1673
+ const accountPath = `m/${purpose}'/${coinType}'/${this._account}'`;
1674
+ const accountXpub = this._toStandardBase58(this._root.derivePath(accountPath).neutered());
1675
+ const origin = `[${fingerprint}/${purpose}h/${coinType}h/${this._account}h]`;
1676
+ descriptors.push({
1677
+ addressType,
1678
+ external: (0, utils_1.appendDescriptorChecksum)(wrap(addressType, `${origin}${accountXpub}/0/*`)),
1679
+ internal: (0, utils_1.appendDescriptorChecksum)(wrap(addressType, `${origin}${accountXpub}/1/*`))
1680
+ });
1681
+ }
1682
+ }
1683
+ const birthdayHeight = this.birthdayHeight;
1684
+ return (0, utils_1.ok)({
1685
+ fingerprint,
1686
+ network: this._network,
1687
+ account: Number(this._account),
1688
+ ...(birthdayHeight > 0 ? { birthdayHeight } : {}),
1689
+ watchOnly: this.isWatchOnly,
1690
+ descriptors
1691
+ });
1692
+ }
1693
+ catch (e) {
1694
+ return (0, utils_1.err)(e);
1695
+ }
1696
+ }
1697
+ _toStandardBase58(node) {
1698
+ const standard = this.getBitcoinNetwork(this._network);
1699
+ if (node.network?.bip32?.public === standard.bip32.public) {
1700
+ return node.toBase58();
1701
+ }
1702
+ const clone = Object.create(Object.getPrototypeOf(node), Object.getOwnPropertyDescriptors(node));
1703
+ clone.network = standard;
1704
+ return clone.toBase58();
1705
+ }
1237
1706
  async saveWalletData(key, data) {
1238
1707
  if (!this._setData)
1239
1708
  return 'No setData method has been provided';
@@ -1850,24 +2319,59 @@ class Wallet {
1850
2319
  return (0, utils_1.ok)(generateAddressResponse.value.changeAddresses[0]);
1851
2320
  }
1852
2321
  async getFeeEstimates(network = this._network) {
2322
+ if (network === types_1.EAvailableNetworks.bitcoinRegtest) {
2323
+ return { ...shapes_1.defaultFeesShape, timestamp: Date.now() };
2324
+ }
2325
+ if (this.feeEstimationSource !== 'http') {
2326
+ const electrumFees = await this.getFeeEstimatesFromElectrum();
2327
+ if (electrumFees.isOk()) {
2328
+ return electrumFees.value;
2329
+ }
2330
+ if (this.feeEstimationSource === 'electrum') {
2331
+ return this.feeEstimates;
2332
+ }
2333
+ }
2334
+ return this.getFeeEstimatesFromHttp(network);
2335
+ }
2336
+ async getFeeEstimatesFromElectrum() {
2337
+ if (!this.electrum.connectedToElectrum) {
2338
+ return (0, utils_1.err)('Not connected to an Electrum server.');
2339
+ }
2340
+ const targets = [2, 6, 24, 144];
2341
+ const results = await Promise.all(targets.map((blocks) => this.electrum.getFeeEstimate(blocks)));
2342
+ const [fast, normal, slow, minimum] = results;
2343
+ if (fast.isErr() || normal.isErr() || slow.isErr() || minimum.isErr()) {
2344
+ return (0, utils_1.err)('Electrum returned unusable fee estimates.');
2345
+ }
2346
+ return (0, utils_1.ok)({
2347
+ fast: fast.value,
2348
+ normal: normal.value,
2349
+ slow: slow.value,
2350
+ minimum: minimum.value,
2351
+ timestamp: Date.now()
2352
+ });
2353
+ }
2354
+ async getFeeEstimatesFromHttp(network = this._network) {
1853
2355
  try {
1854
- if (network === types_1.EAvailableNetworks.bitcoinRegtest) {
1855
- return { ...shapes_1.defaultFeesShape, timestamp: Date.now() };
2356
+ let urlModifier = '';
2357
+ if (network !== types_1.EAvailableNetworks.bitcoinMainnet) {
2358
+ urlModifier =
2359
+ network === types_1.EAvailableNetworks.bitcoinSignet ? 'signet/' : 'testnet/';
1856
2360
  }
1857
- const urlModifier = network === 'bitcoin' ? '' : 'testnet/';
1858
2361
  const response = await fetch(`https://mempool.space/${urlModifier}api/v1/fees/recommended`);
1859
2362
  const res = await response.json();
1860
- if (!(res.fastestFee > 0 &&
1861
- res.halfHourFee > 0 &&
1862
- res.hourFee > 0 &&
1863
- res.minimumFee > 0)) {
2363
+ const fast = (0, utils_1.clampFeeRate)(res.fastestFee);
2364
+ const normal = (0, utils_1.clampFeeRate)(res.halfHourFee);
2365
+ const slow = (0, utils_1.clampFeeRate)(res.hourFee);
2366
+ const minimum = (0, utils_1.clampFeeRate)(res.minimumFee);
2367
+ if (!(fast > 0 && normal > 0 && slow > 0 && minimum > 0)) {
1864
2368
  throw new Error('Unexpected response from mempool.space');
1865
2369
  }
1866
2370
  return {
1867
- fast: res.fastestFee,
1868
- normal: res.halfHourFee,
1869
- slow: res.hourFee,
1870
- minimum: res.minimumFee,
2371
+ fast,
2372
+ normal,
2373
+ slow,
2374
+ minimum,
1871
2375
  timestamp: Date.now()
1872
2376
  };
1873
2377
  }
@@ -1888,10 +2392,15 @@ class Wallet {
1888
2392
  res?.onchain?.feeRates?.slow > 0)) {
1889
2393
  throw new Error('Unexpected response from blocktank');
1890
2394
  }
1891
- const { fast, mid, slow } = res.onchain.feeRates;
2395
+ const fast = (0, utils_1.clampFeeRate)(res.onchain.feeRates.fast);
2396
+ const normal = (0, utils_1.clampFeeRate)(res.onchain.feeRates.mid);
2397
+ const slow = (0, utils_1.clampFeeRate)(res.onchain.feeRates.slow);
2398
+ if (!(fast > 0 && normal > 0 && slow > 0)) {
2399
+ throw new Error('Unexpected response from blocktank');
2400
+ }
1892
2401
  return {
1893
2402
  fast,
1894
- normal: mid,
2403
+ normal,
1895
2404
  slow,
1896
2405
  minimum: slow,
1897
2406
  timestamp: Date.now()
@@ -1915,6 +2424,10 @@ class Wallet {
1915
2424
  });
1916
2425
  }
1917
2426
  async sendMany({ txs = [], satsPerByte = this.feeEstimates.normal, rbf, broadcast = true, shuffleOutputs = true }) {
2427
+ if (this._multisig)
2428
+ return (0, utils_1.err)(new utils_1.MultisigSpendError());
2429
+ if (this.isWatchOnly)
2430
+ return (0, utils_1.err)(new utils_1.WatchOnlySigningError());
1918
2431
  if (!this.data.utxos.length) {
1919
2432
  return (0, utils_1.err)('No UTXOs available.');
1920
2433
  }
@@ -1968,6 +2481,10 @@ class Wallet {
1968
2481
  return (0, utils_1.ok)(broadcastRes.value);
1969
2482
  }
1970
2483
  async sendMax({ address, satsPerByte, rbf = false, broadcast = true } = {}) {
2484
+ if (this._multisig)
2485
+ return (0, utils_1.err)(new utils_1.MultisigSpendError());
2486
+ if (this.isWatchOnly)
2487
+ return (0, utils_1.err)(new utils_1.WatchOnlySigningError());
1971
2488
  if (!this.data.utxos.length) {
1972
2489
  return (0, utils_1.err)('No UTXOs available.');
1973
2490
  }
@@ -2009,6 +2526,242 @@ class Wallet {
2009
2526
  shuffleOutputs
2010
2527
  });
2011
2528
  }
2529
+ async buildPsbt({ txs, address, amount, message = '', satsPerByte = this.feeEstimates.normal, rbf = false, shuffleOutputs = true }) {
2530
+ try {
2531
+ let sendTxs = txs ?? [];
2532
+ if (!sendTxs.length) {
2533
+ if (!address || !amount) {
2534
+ return (0, utils_1.err)('No outputs provided. Specify txs or address and amount.');
2535
+ }
2536
+ sendTxs = [{ address, amount, message }];
2537
+ }
2538
+ if (!this.data.utxos.length) {
2539
+ return (0, utils_1.err)('No UTXOs available.');
2540
+ }
2541
+ await this.resetSendTransaction();
2542
+ const setupTransactionRes = await this.transaction.setupTransaction({
2543
+ rbf,
2544
+ satsPerByte
2545
+ });
2546
+ if (setupTransactionRes.isErr()) {
2547
+ return (0, utils_1.err)(setupTransactionRes.error.message);
2548
+ }
2549
+ let index = 0;
2550
+ for (const tx of sendTxs) {
2551
+ const updateSendTransactionRes = this.transaction.updateSendTransaction({
2552
+ transaction: {
2553
+ label: tx.message,
2554
+ outputs: [{ address: tx.address, value: tx.amount, index }]
2555
+ }
2556
+ });
2557
+ if (updateSendTransactionRes.isErr()) {
2558
+ return (0, utils_1.err)(updateSendTransactionRes.error.message);
2559
+ }
2560
+ index++;
2561
+ }
2562
+ const updateFeeRes = this.transaction.updateFee({ satsPerByte });
2563
+ if (updateFeeRes.isErr())
2564
+ return (0, utils_1.err)(updateFeeRes.error.message);
2565
+ const psbtRes = await this.transaction.createUnsignedPsbt({
2566
+ shuffleOutputs
2567
+ });
2568
+ if (psbtRes.isErr())
2569
+ return (0, utils_1.err)(psbtRes.error.message);
2570
+ const psbt = psbtRes.value;
2571
+ const txData = this.transaction.data;
2572
+ const inputValue = this.transaction.getTransactionInputValue({
2573
+ inputs: txData.inputs
2574
+ });
2575
+ const outputValue = psbt.txOutputs.reduce((acc, o) => acc + o.value, 0);
2576
+ const fee = inputValue - outputValue;
2577
+ const effectiveSatsPerByte = txData.satsPerByte || satsPerByte;
2578
+ const vsizeEstimate = effectiveSatsPerByte > 0 ? Math.round(fee / effectiveSatsPerByte) : 0;
2579
+ const network = this.getBitcoinNetwork();
2580
+ const outputs = psbt.txOutputs.map((output, i) => {
2581
+ let outputAddress = output.address;
2582
+ if (!outputAddress) {
2583
+ try {
2584
+ outputAddress = bitcoin.address.fromOutputScript(output.script, network);
2585
+ }
2586
+ catch {
2587
+ outputAddress = undefined;
2588
+ }
2589
+ }
2590
+ return { address: outputAddress, value: output.value, index: i };
2591
+ });
2592
+ const inputs = txData.inputs.map((input) => ({
2593
+ tx_hash: input.tx_hash,
2594
+ tx_pos: input.tx_pos,
2595
+ address: input.address,
2596
+ value: input.value,
2597
+ path: input.path
2598
+ }));
2599
+ return (0, utils_1.ok)({
2600
+ psbtBase64: psbt.toBase64(),
2601
+ fee,
2602
+ vsizeEstimate,
2603
+ satsPerByte: effectiveSatsPerByte,
2604
+ inputs,
2605
+ outputs
2606
+ });
2607
+ }
2608
+ catch (e) {
2609
+ return (0, utils_1.err)(e);
2610
+ }
2611
+ }
2612
+ importSignedPsbt(psbtBase64) {
2613
+ try {
2614
+ if (!psbtBase64)
2615
+ return (0, utils_1.err)('No PSBT provided.');
2616
+ const network = this.getBitcoinNetwork();
2617
+ const psbt = bitcoin.Psbt.fromBase64(psbtBase64, { network });
2618
+ if (psbt.inputCount === 0)
2619
+ return (0, utils_1.err)('PSBT has no inputs.');
2620
+ for (let i = 0; i < psbt.inputCount; i++) {
2621
+ const input = psbt.data.inputs[i];
2622
+ const finalized = !!(input.finalScriptSig || input.finalScriptWitness);
2623
+ if (finalized)
2624
+ continue;
2625
+ const hasSignature = (input.partialSig?.length ?? 0) > 0 ||
2626
+ !!input.tapKeySig ||
2627
+ (input.tapScriptSig?.length ?? 0) > 0;
2628
+ if (!hasSignature) {
2629
+ return (0, utils_1.err)(`Input ${i} is missing a signature.`);
2630
+ }
2631
+ const multisigCheck = this._enforceMultisigThreshold(psbt, i);
2632
+ if (multisigCheck.isErr())
2633
+ return (0, utils_1.err)(multisigCheck.error.message);
2634
+ let valid = false;
2635
+ try {
2636
+ valid = psbt.validateSignaturesOfInput(i, utils_1.validatePsbtSignature);
2637
+ }
2638
+ catch (e) {
2639
+ return (0, utils_1.err)(`Unable to validate the signature for input ${i}: ${e instanceof Error ? e.message : String(e)}`);
2640
+ }
2641
+ if (!valid)
2642
+ return (0, utils_1.err)(`Input ${i} has an invalid signature.`);
2643
+ psbt.finalizeInput(i);
2644
+ }
2645
+ const tx = psbt.extractTransaction();
2646
+ return (0, utils_1.ok)({ txHex: tx.toHex(), txid: tx.getId() });
2647
+ }
2648
+ catch (e) {
2649
+ return (0, utils_1.err)(e);
2650
+ }
2651
+ }
2652
+ _enforceMultisigThreshold(psbt, i) {
2653
+ const input = psbt.data.inputs[i];
2654
+ const witnessScript = input.witnessScript;
2655
+ if (!witnessScript)
2656
+ return (0, utils_1.ok)('Not a multisig input.');
2657
+ let m;
2658
+ let pubkeys;
2659
+ try {
2660
+ const p2ms = bitcoin.payments.p2ms({ output: witnessScript });
2661
+ m = p2ms.m;
2662
+ pubkeys = p2ms.pubkeys;
2663
+ }
2664
+ catch {
2665
+ return (0, utils_1.ok)('Not a multisig witnessScript.');
2666
+ }
2667
+ if (!m || !pubkeys?.length)
2668
+ return (0, utils_1.ok)('Not a multisig witnessScript.');
2669
+ const p2wsh = bitcoin.payments.p2wsh({
2670
+ redeem: { output: witnessScript }
2671
+ });
2672
+ if (!input.witnessUtxo ||
2673
+ !p2wsh.output ||
2674
+ !input.witnessUtxo.script.equals(p2wsh.output)) {
2675
+ return (0, utils_1.err)(`Input ${i}: witnessScript does not match the witnessUtxo script.`);
2676
+ }
2677
+ const partials = input.partialSig ?? [];
2678
+ for (const ps of partials) {
2679
+ if (!pubkeys.some((pk) => pk.equals(ps.pubkey))) {
2680
+ return (0, utils_1.err)(`Input ${i} carries a signature from a key that is not in the multisig script.`);
2681
+ }
2682
+ }
2683
+ const signedKeys = new Set(partials.map((ps) => ps.pubkey.toString('hex')));
2684
+ const have = signedKeys.size;
2685
+ if (have < m) {
2686
+ return (0, utils_1.err)(`Input ${i} is below the multisig threshold: have ${have} signature(s), need ${m}.`);
2687
+ }
2688
+ if (have > m) {
2689
+ const keep = [];
2690
+ for (const pk of pubkeys) {
2691
+ if (keep.length >= m)
2692
+ break;
2693
+ const ps = partials.find((p) => p.pubkey.equals(pk));
2694
+ if (ps)
2695
+ keep.push(ps);
2696
+ }
2697
+ input.partialSig = keep;
2698
+ }
2699
+ return (0, utils_1.ok)('Multisig threshold satisfied.');
2700
+ }
2701
+ signPsbtWithOurKey(psbtBase64) {
2702
+ try {
2703
+ if (!this._root)
2704
+ return (0, utils_1.err)(new utils_1.WatchOnlySigningError());
2705
+ if (!psbtBase64)
2706
+ return (0, utils_1.err)('No PSBT provided.');
2707
+ const network = this.getBitcoinNetwork();
2708
+ const psbt = bitcoin.Psbt.fromBase64(psbtBase64, { network });
2709
+ if (psbt.inputCount === 0)
2710
+ return (0, utils_1.err)('PSBT has no inputs.');
2711
+ let matched = 0;
2712
+ for (let i = 0; i < psbt.inputCount; i++) {
2713
+ const input = psbt.data.inputs[i];
2714
+ if (input.finalScriptSig || input.finalScriptWitness)
2715
+ continue;
2716
+ for (const derivation of input.bip32Derivation ?? []) {
2717
+ let keyPair;
2718
+ try {
2719
+ keyPair = this._root.derivePath(derivation.path);
2720
+ }
2721
+ catch {
2722
+ continue;
2723
+ }
2724
+ if (!keyPair.publicKey.equals(derivation.pubkey))
2725
+ continue;
2726
+ matched++;
2727
+ const alreadySigned = (input.partialSig ?? []).some((ps) => ps.pubkey.equals(keyPair.publicKey));
2728
+ if (!alreadySigned)
2729
+ psbt.signInput(i, keyPair);
2730
+ break;
2731
+ }
2732
+ }
2733
+ if (matched === 0) {
2734
+ return (0, utils_1.err)('No PSBT input carries a bip32Derivation this wallet can sign for.');
2735
+ }
2736
+ return (0, utils_1.ok)(psbt.toBase64());
2737
+ }
2738
+ catch (e) {
2739
+ return (0, utils_1.err)(e);
2740
+ }
2741
+ }
2742
+ combinePsbts(psbts) {
2743
+ try {
2744
+ if (!psbts || psbts.length < 2) {
2745
+ return (0, utils_1.err)('Provide at least two PSBTs to combine.');
2746
+ }
2747
+ const network = this.getBitcoinNetwork();
2748
+ const parsed = psbts.map((psbtBase64) => bitcoin.Psbt.fromBase64(psbtBase64, { network }));
2749
+ const [base, ...rest] = parsed;
2750
+ base.combine(...rest);
2751
+ return (0, utils_1.ok)(base.toBase64());
2752
+ }
2753
+ catch (e) {
2754
+ return (0, utils_1.err)(e);
2755
+ }
2756
+ }
2757
+ async broadcastTransaction(txHex) {
2758
+ if (!txHex)
2759
+ return (0, utils_1.err)('No transaction hex provided.');
2760
+ return await this.electrum.broadcastTransaction({
2761
+ rawTx: txHex,
2762
+ subscribeToOutputAddress: false
2763
+ });
2764
+ }
2012
2765
  getAddressFromScriptHash(scriptHash) {
2013
2766
  const addresses = this.data.addresses;
2014
2767
  const changeAddresses = this.data.changeAddresses;
@@ -2436,7 +3189,7 @@ class Wallet {
2436
3189
  await this.saveWalletData('feeEstimates', feeEstimates);
2437
3190
  return (0, utils_1.ok)(feeEstimates);
2438
3191
  }
2439
- getAddressesFromPrivateKey(privateKey, _addressTypes = (0, shapes_1.getAddressTypes)()) {
3192
+ getAddressesFromPrivateKey(privateKey, _addressTypes = (0, shapes_1.getAddressTypes)().filter((t) => t !== types_1.EAddressType.p2wsh)) {
2440
3193
  try {
2441
3194
  const network = this.getBitcoinNetwork();
2442
3195
  return (0, utils_1.getAddressesFromPrivateKey)({
@@ -2481,6 +3234,10 @@ class Wallet {
2481
3234
  return (0, utils_1.ok)({ balance, utxos, keyPair, addresses });
2482
3235
  }
2483
3236
  async sweepPrivateKey({ privateKey, toAddress, satsPerByte = this.feeEstimates.normal, broadcast = true, combineWithWalletUtxos = false }) {
3237
+ if (this._multisig)
3238
+ return (0, utils_1.err)(new utils_1.MultisigSpendError());
3239
+ if (this.isWatchOnly)
3240
+ return (0, utils_1.err)(new utils_1.WatchOnlySigningError());
2484
3241
  const privateKeyInfo = await this.getPrivateKeyInfo(privateKey);
2485
3242
  if (privateKeyInfo.isErr()) {
2486
3243
  return (0, utils_1.err)(privateKeyInfo.error.message);