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.
- package/README.md +284 -1
- package/dist/cli/beignet-node.js +1019 -47
- package/dist/cli/beignet-node.js.map +1 -1
- package/dist/cli/cli.js +1201 -16
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/config.js +21 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/daemon.js +342 -16
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/errors.js +2 -0
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/openapi.js +1207 -14
- package/dist/cli/openapi.js.map +1 -1
- package/dist/cli/restore.js +89 -0
- package/dist/cli/restore.js.map +1 -0
- package/dist/cli/wallet-storage.js +34 -0
- package/dist/cli/wallet-storage.js.map +1 -0
- package/dist/electrum/index.js +105 -10
- package/dist/electrum/index.js.map +1 -1
- package/dist/lightning/advisor/fee-tuner.js +42 -0
- package/dist/lightning/advisor/fee-tuner.js.map +1 -0
- package/dist/lightning/advisor/index.js +9 -1
- package/dist/lightning/advisor/index.js.map +1 -1
- package/dist/lightning/advisor/rebalance-planner.js +55 -0
- package/dist/lightning/advisor/rebalance-planner.js.map +1 -0
- package/dist/lightning/backup/index.js +18 -0
- package/dist/lightning/backup/index.js.map +1 -0
- package/dist/lightning/backup/scb.js +42 -0
- package/dist/lightning/backup/scb.js.map +1 -0
- package/dist/lightning/chain/chain-monitor.js +27 -0
- package/dist/lightning/chain/chain-monitor.js.map +1 -1
- package/dist/lightning/chain/output-resolver.js +55 -6
- package/dist/lightning/chain/output-resolver.js.map +1 -1
- package/dist/lightning/chain/types.js +1 -0
- package/dist/lightning/chain/types.js.map +1 -1
- package/dist/lightning/channel/channel-manager.js +64 -0
- package/dist/lightning/channel/channel-manager.js.map +1 -1
- package/dist/lightning/channel/channel-state.js.map +1 -1
- package/dist/lightning/channel/channel.js +124 -4
- package/dist/lightning/channel/channel.js.map +1 -1
- package/dist/lightning/channel/dual-funding.js +6 -5
- package/dist/lightning/channel/dual-funding.js.map +1 -1
- package/dist/lightning/channel/types.js +3 -1
- package/dist/lightning/channel/types.js.map +1 -1
- package/dist/lightning/channel/validation.js +3 -3
- package/dist/lightning/channel/validation.js.map +1 -1
- package/dist/lightning/crypto/index.js +1 -0
- package/dist/lightning/crypto/index.js.map +1 -1
- package/dist/lightning/crypto/message-signing.js +116 -0
- package/dist/lightning/crypto/message-signing.js.map +1 -0
- package/dist/lightning/crypto/xchacha20poly1305.js +77 -0
- package/dist/lightning/crypto/xchacha20poly1305.js.map +1 -0
- package/dist/lightning/features/flags.js +1 -0
- package/dist/lightning/features/flags.js.map +1 -1
- package/dist/lightning/gossip/messages.js +37 -1
- package/dist/lightning/gossip/messages.js.map +1 -1
- package/dist/lightning/index.js +2 -1
- package/dist/lightning/index.js.map +1 -1
- package/dist/lightning/keys/wallet-keys.js +1 -0
- package/dist/lightning/keys/wallet-keys.js.map +1 -1
- package/dist/lightning/message/index.js +1 -0
- package/dist/lightning/message/index.js.map +1 -1
- package/dist/lightning/message/peer-storage.js +43 -0
- package/dist/lightning/message/peer-storage.js.map +1 -0
- package/dist/lightning/message/types.js +2 -0
- package/dist/lightning/message/types.js.map +1 -1
- package/dist/lightning/node/lightning-node.js +1583 -235
- package/dist/lightning/node/lightning-node.js.map +1 -1
- package/dist/lightning/node/types.js +18 -1
- package/dist/lightning/node/types.js.map +1 -1
- package/dist/lightning/offer/offer-manager.js +2 -5
- package/dist/lightning/offer/offer-manager.js.map +1 -1
- package/dist/lightning/offer/tlv.js +3 -1
- package/dist/lightning/offer/tlv.js.map +1 -1
- package/dist/lightning/storage/encryption.js +67 -0
- package/dist/lightning/storage/encryption.js.map +1 -0
- package/dist/lightning/storage/index.js +1 -0
- package/dist/lightning/storage/index.js.map +1 -1
- package/dist/lightning/storage/serialization.js +6 -2
- package/dist/lightning/storage/serialization.js.map +1 -1
- package/dist/lightning/storage/sqlite-storage.js +387 -33
- package/dist/lightning/storage/sqlite-storage.js.map +1 -1
- package/dist/lightning/watchtower/blob.js +135 -0
- package/dist/lightning/watchtower/blob.js.map +1 -0
- package/dist/lightning/watchtower/index.js +55 -0
- package/dist/lightning/watchtower/index.js.map +1 -0
- package/dist/lightning/watchtower/justice.js +182 -0
- package/dist/lightning/watchtower/justice.js.map +1 -0
- package/dist/lightning/watchtower/tower-connection.js +208 -0
- package/dist/lightning/watchtower/tower-connection.js.map +1 -0
- package/dist/lightning/watchtower/types.js +3 -0
- package/dist/lightning/watchtower/types.js.map +1 -0
- package/dist/lightning/watchtower/watchtower-client.js +402 -0
- package/dist/lightning/watchtower/watchtower-client.js.map +1 -0
- package/dist/lightning/watchtower/wtwire.js +201 -0
- package/dist/lightning/watchtower/wtwire.js.map +1 -0
- package/dist/shapes/electrum.js +32 -1
- package/dist/shapes/electrum.js.map +1 -1
- package/dist/shapes/wallet.js +11 -1
- package/dist/shapes/wallet.js.map +1 -1
- package/dist/transaction/index.js +163 -15
- package/dist/transaction/index.js.map +1 -1
- package/dist/types/cli/beignet-node.d.ts +144 -3
- package/dist/types/cli/daemon.d.ts +2 -0
- package/dist/types/cli/errors.d.ts +2 -0
- package/dist/types/cli/restore.d.ts +10 -0
- package/dist/types/cli/types.d.ts +228 -1
- package/dist/types/cli/wallet-storage.d.ts +3 -0
- package/dist/types/electrum/index.d.ts +12 -0
- package/dist/types/electrum.js +1 -0
- package/dist/types/electrum.js.map +1 -1
- package/dist/types/lightning/advisor/fee-tuner.d.ts +20 -0
- package/dist/types/lightning/advisor/index.d.ts +4 -0
- package/dist/types/lightning/advisor/rebalance-planner.d.ts +12 -0
- package/dist/types/lightning/backup/index.d.ts +1 -0
- package/dist/types/lightning/backup/scb.d.ts +24 -0
- package/dist/types/lightning/chain/chain-monitor.d.ts +1 -0
- package/dist/types/lightning/chain/types.d.ts +1 -0
- package/dist/types/lightning/channel/channel-manager.d.ts +7 -0
- package/dist/types/lightning/channel/channel-state.d.ts +2 -0
- package/dist/types/lightning/channel/channel.d.ts +6 -0
- package/dist/types/lightning/channel/dual-funding.d.ts +2 -1
- package/dist/types/lightning/channel/types.d.ts +2 -0
- package/dist/types/lightning/channel/validation.d.ts +1 -1
- package/dist/types/lightning/crypto/index.d.ts +1 -0
- package/dist/types/lightning/crypto/message-signing.d.ts +9 -0
- package/dist/types/lightning/crypto/xchacha20poly1305.d.ts +3 -0
- package/dist/types/lightning/features/flags.d.ts +1 -0
- package/dist/types/lightning/gossip/messages.d.ts +4 -0
- package/dist/types/lightning/index.d.ts +1 -0
- package/dist/types/lightning/keys/wallet-keys.d.ts +2 -1
- package/dist/types/lightning/message/index.d.ts +1 -0
- package/dist/types/lightning/message/peer-storage.d.ts +9 -0
- package/dist/types/lightning/message/types.d.ts +2 -0
- package/dist/types/lightning/node/lightning-node.d.ts +116 -4
- package/dist/types/lightning/node/types.d.ts +62 -1
- package/dist/types/lightning/storage/encryption.d.ts +11 -0
- package/dist/types/lightning/storage/index.d.ts +1 -0
- package/dist/types/lightning/storage/serialization.d.ts +2 -0
- package/dist/types/lightning/storage/sqlite-storage.d.ts +43 -3
- package/dist/types/lightning/storage/types.d.ts +62 -0
- package/dist/types/lightning/watchtower/blob.d.ts +32 -0
- package/dist/types/lightning/watchtower/index.d.ts +9 -0
- package/dist/types/lightning/watchtower/justice.d.ts +29 -0
- package/dist/types/lightning/watchtower/tower-connection.d.ts +34 -0
- package/dist/types/lightning/watchtower/types.d.ts +44 -0
- package/dist/types/lightning/watchtower/watchtower-client.d.ts +67 -0
- package/dist/types/lightning/watchtower/wtwire.d.ts +90 -0
- package/dist/types/shapes/electrum.d.ts +3 -1
- package/dist/types/transaction/index.d.ts +6 -0
- package/dist/types/transaction.js.map +1 -1
- package/dist/types/types/electrum.d.ts +3 -2
- package/dist/types/types/transaction.d.ts +1 -1
- package/dist/types/types/wallet.d.ts +78 -4
- package/dist/types/utils/descriptors.d.ts +2 -0
- package/dist/types/utils/fees.d.ts +3 -0
- package/dist/types/utils/helpers.d.ts +3 -1
- package/dist/types/utils/index.d.ts +5 -0
- package/dist/types/utils/multisig.d.ts +29 -0
- package/dist/types/utils/transaction.d.ts +7 -0
- package/dist/types/utils/wallet-storage-encryption.d.ts +3 -0
- package/dist/types/utils/wallet.d.ts +1 -1
- package/dist/types/utils/watch-only.d.ts +12 -0
- package/dist/types/wallet/constants.d.ts +1 -0
- package/dist/types/wallet/index.d.ts +61 -4
- package/dist/types/wallet.js +3 -0
- package/dist/types/wallet.js.map +1 -1
- package/dist/utils/derivation-path.js +8 -7
- package/dist/utils/derivation-path.js.map +1 -1
- package/dist/utils/descriptors.js +56 -0
- package/dist/utils/descriptors.js.map +1 -0
- package/dist/utils/electrum.js +10 -2
- package/dist/utils/electrum.js.map +1 -1
- package/dist/utils/fees.js +19 -0
- package/dist/utils/fees.js.map +1 -0
- package/dist/utils/helpers.js +22 -5
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/multisig.js +90 -0
- package/dist/utils/multisig.js.map +1 -0
- package/dist/utils/transaction.js +30 -3
- package/dist/utils/transaction.js.map +1 -1
- package/dist/utils/wallet-storage-encryption.js +45 -0
- package/dist/utils/wallet-storage-encryption.js.map +1 -0
- package/dist/utils/wallet.js +3 -1
- package/dist/utils/wallet.js.map +1 -1
- package/dist/utils/watch-only.js +102 -0
- package/dist/utils/watch-only.js.map +1 -0
- package/dist/wallet/constants.js +2 -1
- package/dist/wallet/constants.js.map +1 -1
- package/dist/wallet/index.js +787 -30
- package/dist/wallet/index.js.map +1 -1
- package/docs/ROADMAP.md +292 -0
- package/package.json +1 -1
package/dist/wallet/index.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
117
|
-
|
|
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
|
|
661
|
+
return (0, utils_1.getBitcoinJsNetwork)(network);
|
|
432
662
|
}
|
|
433
663
|
isValid(mnemonic) {
|
|
434
|
-
return
|
|
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.
|
|
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
|
-
|
|
1855
|
-
|
|
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
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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
|
|
1868
|
-
normal
|
|
1869
|
-
slow
|
|
1870
|
-
minimum
|
|
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
|
|
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
|
|
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);
|