@leather.io/bitcoin 0.35.4 → 0.35.6
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/.turbo/turbo-build.log +13 -14
- package/CHANGELOG.md +35 -0
- package/dist/index.d.ts +474 -357
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1188 -1504
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
- package/src/utils/lookup-derivation-by-address.spec.ts +1 -1
- package/tsconfig.json +3 -3
- package/tsdown.config.ts +7 -0
- package/vitest.config.js +1 -1
- package/tsup.config.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -1,1727 +1,1411 @@
|
|
|
1
|
-
// src/bip21/bip21.ts
|
|
2
1
|
import { decode, encode } from "bip21";
|
|
3
|
-
import { isError } from "@leather.io/utils";
|
|
4
|
-
var bip21 = {
|
|
5
|
-
decode: (uri, urnScheme) => {
|
|
6
|
-
try {
|
|
7
|
-
const { address: address2, options } = decode(uri, urnScheme);
|
|
8
|
-
return {
|
|
9
|
-
success: true,
|
|
10
|
-
data: { address: address2, ...options }
|
|
11
|
-
};
|
|
12
|
-
} catch (error) {
|
|
13
|
-
return {
|
|
14
|
-
success: false,
|
|
15
|
-
error: isError(error) ? error.message : "invalid input"
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
encode: (address2, options, urnScheme) => {
|
|
20
|
-
try {
|
|
21
|
-
return encode(address2, options, urnScheme);
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// src/bip322/bip322-utils.ts
|
|
2
|
+
import { assertUnreachable, createCounter, createMoney, defaultWalletKeyId, hexToNumber, isDefined, isEmptyString, isError, isString, isUndefined, satToBtc, subtractMoney, sumMoney, sumNumbers, toHexString, whenNetwork } from "@leather.io/utils";
|
|
29
3
|
import ecc from "@bitcoinerlab/secp256k1";
|
|
30
4
|
import { sha256 } from "@noble/hashes/sha256";
|
|
31
|
-
import { hexToBytes
|
|
32
|
-
import * as
|
|
5
|
+
import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils";
|
|
6
|
+
import * as bitcoinJs from "bitcoinjs-lib";
|
|
33
7
|
import { ECPairFactory } from "ecpair";
|
|
34
|
-
import { encode as
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
// src/utils/bitcoin.utils.ts
|
|
38
|
-
import { hexToBytes } from "@noble/hashes/utils";
|
|
39
|
-
import { HDKey } from "@scure/bip32";
|
|
8
|
+
import { encode as encode$1 } from "varuint-bitcoin";
|
|
9
|
+
import { HARDENED_OFFSET, HDKey } from "@scure/bip32";
|
|
40
10
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
41
|
-
import * as btc3 from "@scure/btc-signer";
|
|
42
|
-
import {
|
|
43
|
-
DerivationPathDepth as DerivationPathDepth3,
|
|
44
|
-
extractAccountIndexFromPath,
|
|
45
|
-
extractPurposeFromPath
|
|
46
|
-
} from "@leather.io/crypto";
|
|
47
|
-
import { defaultWalletKeyId, isDefined, whenNetwork } from "@leather.io/utils";
|
|
48
|
-
|
|
49
|
-
// src/payments/p2tr-address-gen.ts
|
|
50
11
|
import * as btc from "@scure/btc-signer";
|
|
51
|
-
import { DerivationPathDepth } from "@leather.io/crypto";
|
|
12
|
+
import { DerivationPathDepth, appendAddressIndexToPath, decomposeDescriptor, deriveBip39SeedFromMnemonic, deriveKeychainFromXpub, deriveRootBip32Keychain, extractAccountIndexFromPath, extractAddressIndexFromPath, extractChangeIndexFromPath, extractPurposeFromPath, keyOriginToDerivationPath } from "@leather.io/crypto";
|
|
13
|
+
import validate$1, { AddressType, Network, getAddressInfo, validate } from "bitcoin-address-validation";
|
|
14
|
+
import { base58check, base64 } from "@scure/base";
|
|
15
|
+
import BigNumber from "bignumber.js";
|
|
16
|
+
import { BITCOIN_MINIMUM_SPEND_IN_SATS, BTC_P2WPKH_DUST_AMOUNT } from "@leather.io/constants";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { ripemd160 } from "@noble/hashes/ripemd160";
|
|
19
|
+
import { RawPSBTV0, RawPSBTV2 } from "@scure/btc-signer/psbt";
|
|
52
20
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
21
|
+
//#region src/bip21/bip21.ts
|
|
22
|
+
const bip21 = {
|
|
23
|
+
decode: (uri, urnScheme) => {
|
|
24
|
+
try {
|
|
25
|
+
const { address, options } = decode(uri, urnScheme);
|
|
26
|
+
return {
|
|
27
|
+
success: true,
|
|
28
|
+
data: {
|
|
29
|
+
address,
|
|
30
|
+
...options
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: isError(error) ? error.message : "invalid input"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
encode: (address, options, urnScheme) => {
|
|
41
|
+
try {
|
|
42
|
+
return encode(address, options, urnScheme);
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
60
47
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/utils/bitcoin.network.ts
|
|
51
|
+
const bitcoinMainnet = {
|
|
52
|
+
bech32: "bc",
|
|
53
|
+
pubKeyHash: 0,
|
|
54
|
+
scriptHash: 5,
|
|
55
|
+
wif: 128
|
|
66
56
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
const bitcoinTestnet = {
|
|
58
|
+
bech32: "tb",
|
|
59
|
+
pubKeyHash: 111,
|
|
60
|
+
scriptHash: 196,
|
|
61
|
+
wif: 239
|
|
72
62
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
const btcSignerLibNetworks = {
|
|
64
|
+
mainnet: bitcoinMainnet,
|
|
65
|
+
testnet: bitcoinTestnet,
|
|
66
|
+
regtest: {
|
|
67
|
+
bech32: "bcrt",
|
|
68
|
+
pubKeyHash: 111,
|
|
69
|
+
scriptHash: 196,
|
|
70
|
+
wif: 239
|
|
71
|
+
},
|
|
72
|
+
signet: bitcoinTestnet
|
|
80
73
|
};
|
|
81
74
|
function getBtcSignerLibNetworkConfigByMode(network) {
|
|
82
|
-
|
|
75
|
+
return btcSignerLibNetworks[network];
|
|
83
76
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
const bitcoinJsLibNetworks = {
|
|
78
|
+
mainnet: bitcoinJs.networks.bitcoin,
|
|
79
|
+
testnet: bitcoinJs.networks.testnet,
|
|
80
|
+
regtest: bitcoinJs.networks.regtest,
|
|
81
|
+
signet: bitcoinJs.networks.testnet
|
|
89
82
|
};
|
|
90
83
|
function getBitcoinJsLibNetworkConfigByMode(network) {
|
|
91
|
-
|
|
84
|
+
return bitcoinJsLibNetworks[network];
|
|
92
85
|
}
|
|
93
86
|
|
|
94
|
-
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/payments/p2tr-address-gen.ts
|
|
95
89
|
function makeTaprootAccountDerivationPath(network, accountIndex) {
|
|
96
|
-
|
|
90
|
+
return `m/86'/${getBitcoinCoinTypeIndexByNetwork(network)}'/${accountIndex}'`;
|
|
97
91
|
}
|
|
98
|
-
|
|
92
|
+
/** @deprecated Use makeTaprootAccountDerivationPath */
|
|
93
|
+
const getTaprootAccountDerivationPath = makeTaprootAccountDerivationPath;
|
|
99
94
|
function makeTaprootAddressIndexDerivationPath(network, accountIndex, addressIndex) {
|
|
100
|
-
|
|
95
|
+
return makeTaprootAccountDerivationPath(network, accountIndex) + `/0/${addressIndex}`;
|
|
101
96
|
}
|
|
102
|
-
|
|
97
|
+
/** @deprecated Use makeTaprootAddressIndexDerivationPath */
|
|
98
|
+
const getTaprootAddressIndexDerivationPath = makeTaprootAddressIndexDerivationPath;
|
|
103
99
|
function deriveTaprootAccount(keychain, network) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
100
|
+
if (keychain.depth !== DerivationPathDepth.Root) throw new Error("Keychain passed is not an account");
|
|
101
|
+
return (accountIndex) => ({
|
|
102
|
+
type: "p2tr",
|
|
103
|
+
network,
|
|
104
|
+
accountIndex,
|
|
105
|
+
derivationPath: makeTaprootAccountDerivationPath(network, accountIndex),
|
|
106
|
+
keychain: keychain.derive(makeTaprootAccountDerivationPath(network, accountIndex))
|
|
107
|
+
});
|
|
113
108
|
}
|
|
114
109
|
function getTaprootPayment(publicKey, network) {
|
|
115
|
-
|
|
116
|
-
ecdsaPublicKeyToSchnorr(publicKey),
|
|
117
|
-
void 0,
|
|
118
|
-
getBtcSignerLibNetworkConfigByMode(network),
|
|
119
|
-
true
|
|
120
|
-
// allow unknown outputs
|
|
121
|
-
);
|
|
110
|
+
return btc.p2tr(ecdsaPublicKeyToSchnorr(publicKey), void 0, getBtcSignerLibNetworkConfigByMode(network), true);
|
|
122
111
|
}
|
|
123
112
|
function getTaprootPaymentFromAddressIndex(keychain, network) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
keychain: zeroAddressIndex,
|
|
136
|
-
payment: getTaprootPaymentFromAddressIndex(zeroAddressIndex, network)
|
|
137
|
-
};
|
|
113
|
+
if (keychain.depth !== DerivationPathDepth.AddressIndex) throw new Error("Keychain passed is not an address index");
|
|
114
|
+
if (!keychain.publicKey) throw new Error("Keychain has no public key");
|
|
115
|
+
return getTaprootPayment(keychain.publicKey, network);
|
|
116
|
+
}
|
|
117
|
+
function deriveTaprootReceiveAddressIndexZero({ keychain, network }) {
|
|
118
|
+
const zeroAddressIndex = deriveAddressIndexZeroFromAccount(keychain);
|
|
119
|
+
return {
|
|
120
|
+
keychain: zeroAddressIndex,
|
|
121
|
+
payment: getTaprootPaymentFromAddressIndex(zeroAddressIndex, network)
|
|
122
|
+
};
|
|
138
123
|
}
|
|
139
124
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
import { DerivationPathDepth as DerivationPathDepth2 } from "@leather.io/crypto";
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/payments/p2wpkh-address-gen.ts
|
|
143
127
|
function makeNativeSegwitAccountDerivationPath(network, accountIndex) {
|
|
144
|
-
|
|
128
|
+
return `m/84'/${getBitcoinCoinTypeIndexByNetwork(network)}'/${accountIndex}'`;
|
|
145
129
|
}
|
|
146
|
-
|
|
130
|
+
/** @deprecated Use makeNativeSegwitAccountDerivationPath */
|
|
131
|
+
const getNativeSegwitAccountDerivationPath = makeNativeSegwitAccountDerivationPath;
|
|
147
132
|
function makeNativeSegwitAddressIndexDerivationPath(network, accountIndex, addressIndex) {
|
|
148
|
-
|
|
133
|
+
return makeNativeSegwitAccountDerivationPath(network, accountIndex) + `/0/${addressIndex}`;
|
|
149
134
|
}
|
|
150
|
-
|
|
135
|
+
/** @deprecated Use makeNativeSegwitAddressIndexDerivationPath */
|
|
136
|
+
const getNativeSegwitAddressIndexDerivationPath = makeNativeSegwitAddressIndexDerivationPath;
|
|
151
137
|
function deriveNativeSegwitAccountFromRootKeychain(keychain, network) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
138
|
+
if (keychain.depth !== DerivationPathDepth.Root) throw new Error("Keychain passed is not a root");
|
|
139
|
+
return (accountIndex) => ({
|
|
140
|
+
type: "p2wpkh",
|
|
141
|
+
network,
|
|
142
|
+
accountIndex,
|
|
143
|
+
derivationPath: makeNativeSegwitAccountDerivationPath(network, accountIndex),
|
|
144
|
+
keychain: keychain.derive(makeNativeSegwitAccountDerivationPath(network, accountIndex))
|
|
145
|
+
});
|
|
160
146
|
}
|
|
161
147
|
function getNativeSegwitPaymentFromAddressIndex(keychain, network) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
keychain: zeroAddressIndex,
|
|
174
|
-
payment: getNativeSegwitPaymentFromAddressIndex(zeroAddressIndex, network)
|
|
175
|
-
};
|
|
148
|
+
if (keychain.depth !== DerivationPathDepth.AddressIndex) throw new Error("Keychain passed is not an address index");
|
|
149
|
+
if (!keychain.publicKey) throw new Error("Keychain does not have a public key");
|
|
150
|
+
return btc.p2wpkh(keychain.publicKey, getBtcSignerLibNetworkConfigByMode(network));
|
|
151
|
+
}
|
|
152
|
+
function deriveNativeSegwitReceiveAddressIndexZero({ keychain, network }) {
|
|
153
|
+
const zeroAddressIndex = deriveAddressIndexZeroFromAccount(keychain);
|
|
154
|
+
return {
|
|
155
|
+
keychain: zeroAddressIndex,
|
|
156
|
+
payment: getNativeSegwitPaymentFromAddressIndex(zeroAddressIndex, network)
|
|
157
|
+
};
|
|
176
158
|
}
|
|
177
159
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
import { isEmptyString, isUndefined } from "@leather.io/utils";
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/validation/address-validation.ts
|
|
181
162
|
function getBitcoinAddressNetworkType(network) {
|
|
182
|
-
|
|
183
|
-
|
|
163
|
+
if (network === "signet") return Network.testnet;
|
|
164
|
+
return network;
|
|
184
165
|
}
|
|
185
|
-
function isValidBitcoinAddress(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
return validate(address2);
|
|
166
|
+
function isValidBitcoinAddress(address) {
|
|
167
|
+
if (isUndefined(address) || isEmptyString(address)) return false;
|
|
168
|
+
return validate(address);
|
|
190
169
|
}
|
|
191
|
-
function isValidBitcoinNetworkAddress(
|
|
192
|
-
|
|
170
|
+
function isValidBitcoinNetworkAddress(address, network) {
|
|
171
|
+
return validate(address, getBitcoinAddressNetworkType(network));
|
|
193
172
|
}
|
|
194
173
|
|
|
195
|
-
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/validation/bitcoin-error.ts
|
|
196
176
|
var BitcoinError = class extends Error {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
177
|
+
message;
|
|
178
|
+
constructor(message) {
|
|
179
|
+
super(message);
|
|
180
|
+
this.name = "BitcoinError";
|
|
181
|
+
this.message = message;
|
|
182
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
183
|
+
}
|
|
204
184
|
};
|
|
205
185
|
|
|
206
|
-
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/validation/bitcoin-address.ts
|
|
207
188
|
function isBitcoinAddress(value) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
189
|
+
try {
|
|
190
|
+
isValidBitcoinAddress(value);
|
|
191
|
+
return true;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
214
195
|
}
|
|
215
196
|
function createBitcoinAddress(value) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
return value;
|
|
197
|
+
if (!isBitcoinAddress(value)) throw new BitcoinError("InvalidAddress");
|
|
198
|
+
return value;
|
|
220
199
|
}
|
|
221
200
|
|
|
222
|
-
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/utils/bitcoin.utils.ts
|
|
223
203
|
function initBitcoinAccount(derivationPath, policy) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
204
|
+
const xpub = extractExtendedPublicKeyFromPolicy(policy);
|
|
205
|
+
const network = inferNetworkFromPath(derivationPath);
|
|
206
|
+
return {
|
|
207
|
+
keychain: HDKey.fromExtendedKey(xpub, getHdKeyVersionsFromNetwork(network)),
|
|
208
|
+
network,
|
|
209
|
+
derivationPath,
|
|
210
|
+
type: inferPaymentTypeFromPath(derivationPath),
|
|
211
|
+
accountIndex: extractAccountIndexFromPath(derivationPath)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Represents a map of `BitcoinNetworkModes` to `NetworkModes`. While Bitcoin
|
|
216
|
+
* has a number of networks, its often only necessary to consider the higher
|
|
217
|
+
* level concept of mainnet and testnet
|
|
218
|
+
*/
|
|
219
|
+
const bitcoinNetworkToCoreNetworkMap = {
|
|
220
|
+
mainnet: "mainnet",
|
|
221
|
+
testnet: "testnet",
|
|
222
|
+
regtest: "testnet",
|
|
223
|
+
signet: "testnet"
|
|
239
224
|
};
|
|
240
225
|
function bitcoinNetworkModeToCoreNetworkMode(mode) {
|
|
241
|
-
|
|
226
|
+
return bitcoinNetworkToCoreNetworkMap[mode];
|
|
242
227
|
}
|
|
243
228
|
function whenBitcoinNetwork(mode) {
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
229
|
+
return (networkMap) => networkMap[mode];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Map representing the "Coin Type" section of a derivation path.
|
|
233
|
+
* Consider example below, Coin type is one, thus testnet
|
|
234
|
+
* @example
|
|
235
|
+
* `m/86'/1'/0'/0/0`
|
|
236
|
+
*/
|
|
237
|
+
const coinTypeMap = {
|
|
238
|
+
mainnet: 0,
|
|
239
|
+
testnet: 1
|
|
249
240
|
};
|
|
250
241
|
function getBitcoinCoinTypeIndexByNetwork(network) {
|
|
251
|
-
|
|
242
|
+
return coinTypeMap[bitcoinNetworkModeToCoreNetworkMode(network)];
|
|
252
243
|
}
|
|
253
244
|
function deriveAddressIndexKeychainFromAccount(keychain) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return (index) => keychain.deriveChild(0).deriveChild(index);
|
|
245
|
+
if (keychain.depth !== DerivationPathDepth.Account) throw new Error("Keychain passed is not an account");
|
|
246
|
+
return (index) => keychain.deriveChild(0).deriveChild(index);
|
|
257
247
|
}
|
|
258
248
|
function deriveAddressIndexZeroFromAccount(keychain) {
|
|
259
|
-
|
|
249
|
+
return deriveAddressIndexKeychainFromAccount(keychain)(0);
|
|
260
250
|
}
|
|
261
|
-
|
|
251
|
+
const ecdsaPublicKeyLength = 33;
|
|
262
252
|
function ecdsaPublicKeyToSchnorr(pubKey) {
|
|
263
|
-
|
|
264
|
-
|
|
253
|
+
if (pubKey.byteLength !== ecdsaPublicKeyLength) throw new Error("Invalid public key length");
|
|
254
|
+
return pubKey.slice(1);
|
|
265
255
|
}
|
|
266
256
|
function toXOnly(pubKey) {
|
|
267
|
-
|
|
257
|
+
return pubKey.length === 32 ? pubKey : pubKey.subarray(1, 33);
|
|
268
258
|
}
|
|
269
259
|
function decodeBitcoinTx(tx) {
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
function getAddressFromOutScript(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
var paymentTypeMap = {
|
|
305
|
-
wpkh: "p2wpkh",
|
|
306
|
-
wsh: "p2wpkh-p2sh",
|
|
307
|
-
tr: "p2tr",
|
|
308
|
-
pkh: "p2pkh",
|
|
309
|
-
sh: "p2sh"
|
|
260
|
+
return btc.RawTx.decode(hexToBytes(tx));
|
|
261
|
+
}
|
|
262
|
+
function getAddressFromOutScript(script, bitcoinNetwork) {
|
|
263
|
+
const outputScript = btc.OutScript.decode(script);
|
|
264
|
+
switch (outputScript.type) {
|
|
265
|
+
case "pkh":
|
|
266
|
+
case "sh":
|
|
267
|
+
case "wpkh":
|
|
268
|
+
case "wsh": return createBitcoinAddress(btc.Address(bitcoinNetwork).encode({
|
|
269
|
+
type: outputScript.type,
|
|
270
|
+
hash: outputScript.hash
|
|
271
|
+
}));
|
|
272
|
+
case "tr": return createBitcoinAddress(btc.Address(bitcoinNetwork).encode({
|
|
273
|
+
type: outputScript.type,
|
|
274
|
+
pubkey: outputScript.pubkey
|
|
275
|
+
}));
|
|
276
|
+
case "ms": return createBitcoinAddress(btc.p2ms(outputScript.m, outputScript.pubkeys).address ?? "");
|
|
277
|
+
case "pk": return createBitcoinAddress(btc.p2pk(outputScript.pubkey, bitcoinNetwork).address ?? "");
|
|
278
|
+
case "unknown":
|
|
279
|
+
case "tr_ms":
|
|
280
|
+
case "tr_ns":
|
|
281
|
+
case "p2a":
|
|
282
|
+
default: return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const paymentTypeMap = {
|
|
286
|
+
wpkh: "p2wpkh",
|
|
287
|
+
wsh: "p2wpkh-p2sh",
|
|
288
|
+
tr: "p2tr",
|
|
289
|
+
pkh: "p2pkh",
|
|
290
|
+
sh: "p2sh"
|
|
310
291
|
};
|
|
311
292
|
function btcSignerLibPaymentTypeToPaymentTypeMap(payment) {
|
|
312
|
-
|
|
293
|
+
return paymentTypeMap[payment];
|
|
313
294
|
}
|
|
314
295
|
function isBtcSignerLibPaymentType(payment) {
|
|
315
|
-
|
|
296
|
+
return payment in paymentTypeMap;
|
|
316
297
|
}
|
|
317
298
|
function parseKnownPaymentType(payment) {
|
|
318
|
-
|
|
299
|
+
return isBtcSignerLibPaymentType(payment) ? btcSignerLibPaymentTypeToPaymentTypeMap(payment) : payment;
|
|
319
300
|
}
|
|
320
301
|
function whenPaymentType(mode) {
|
|
321
|
-
|
|
302
|
+
return (paymentMap) => paymentMap[parseKnownPaymentType(mode)];
|
|
322
303
|
}
|
|
323
304
|
function whenSupportedPaymentType(mode) {
|
|
324
|
-
|
|
325
|
-
}
|
|
305
|
+
return (paymentMap) => paymentMap[mode];
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Infers the Bitcoin payment type from the derivation path.
|
|
309
|
+
* Below we see path has 86 in it, per convention, this refers to taproot payments
|
|
310
|
+
* @example
|
|
311
|
+
* `m/86'/1'/0'/0/0`
|
|
312
|
+
*/
|
|
326
313
|
function inferPaymentTypeFromPath(path) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return "p2pkh";
|
|
335
|
-
default:
|
|
336
|
-
throw new Error(`Unable to infer payment type from purpose=${purpose}`);
|
|
337
|
-
}
|
|
314
|
+
const purpose = extractPurposeFromPath(path);
|
|
315
|
+
switch (purpose) {
|
|
316
|
+
case 84: return "p2wpkh";
|
|
317
|
+
case 86: return "p2tr";
|
|
318
|
+
case 44: return "p2pkh";
|
|
319
|
+
default: throw new Error(`Unable to infer payment type from purpose=${purpose}`);
|
|
320
|
+
}
|
|
338
321
|
}
|
|
339
322
|
function inferNetworkFromPath(path) {
|
|
340
|
-
|
|
323
|
+
return path.split("/")[2].startsWith("0") ? "mainnet" : "testnet";
|
|
341
324
|
}
|
|
342
325
|
function extractExtendedPublicKeyFromPolicy(policy) {
|
|
343
|
-
|
|
326
|
+
return policy.split("]")[1];
|
|
344
327
|
}
|
|
345
328
|
function createWalletIdDecoratedPath(policy, walletId) {
|
|
346
|
-
|
|
329
|
+
return policy.split("]")[0].replace("[", "").replace("m", walletId);
|
|
347
330
|
}
|
|
348
331
|
function getHdKeyVersionsFromNetwork(network) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
332
|
+
return whenNetwork(network)({
|
|
333
|
+
mainnet: void 0,
|
|
334
|
+
testnet: {
|
|
335
|
+
private: 0,
|
|
336
|
+
public: 70617039
|
|
337
|
+
}
|
|
338
|
+
});
|
|
356
339
|
}
|
|
357
340
|
function getBitcoinInputAddress(input, bitcoinNetwork) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return getAddressFromOutScript(
|
|
362
|
-
input.nonWitnessUtxo.outputs[input.index]?.script,
|
|
363
|
-
bitcoinNetwork
|
|
364
|
-
);
|
|
365
|
-
return null;
|
|
341
|
+
if (isDefined(input.witnessUtxo)) return getAddressFromOutScript(input.witnessUtxo.script, bitcoinNetwork);
|
|
342
|
+
if (isDefined(input.nonWitnessUtxo) && isDefined(input.index)) return getAddressFromOutScript(input.nonWitnessUtxo.outputs[input.index]?.script, bitcoinNetwork);
|
|
343
|
+
return null;
|
|
366
344
|
}
|
|
367
345
|
function getInputPaymentType(input, network) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return "p2wpkh";
|
|
374
|
-
throw new Error("Unable to infer payment type from input address");
|
|
346
|
+
const address = getBitcoinInputAddress(input, getBtcSignerLibNetworkConfigByMode(network));
|
|
347
|
+
if (address === null) throw new Error("Input address cannot be empty");
|
|
348
|
+
if (address.startsWith("bc1p") || address.startsWith("tb1p") || address.startsWith("bcrt1p")) return "p2tr";
|
|
349
|
+
if (address.startsWith("bc1q") || address.startsWith("tb1q") || address.startsWith("bcrt1q")) return "p2wpkh";
|
|
350
|
+
throw new Error("Unable to infer payment type from input address");
|
|
375
351
|
}
|
|
376
352
|
function lookUpLedgerKeysByPath(getDerivationPath) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
353
|
+
return (ledgerKeyMap, network) => (accountIndex) => {
|
|
354
|
+
const path = getDerivationPath(network, accountIndex);
|
|
355
|
+
const account = ledgerKeyMap[path.replace("m", defaultWalletKeyId)];
|
|
356
|
+
if (!account) return;
|
|
357
|
+
return initBitcoinAccount(path, account.policy);
|
|
358
|
+
};
|
|
383
359
|
}
|
|
384
360
|
function getTaprootAddress({ index, keychain, network }) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return payment.address;
|
|
361
|
+
if (!keychain) throw new Error("Expected keychain to be provided");
|
|
362
|
+
if (keychain.depth !== DerivationPathDepth.Account) throw new Error("Expects keychain to be on the account index");
|
|
363
|
+
const addressIndex = deriveAddressIndexKeychainFromAccount(keychain)(index);
|
|
364
|
+
if (!addressIndex.publicKey) throw new Error("Expected publicKey to be defined");
|
|
365
|
+
const payment = getTaprootPayment(addressIndex.publicKey, network);
|
|
366
|
+
if (!payment.address) throw new Error("Expected address to be defined");
|
|
367
|
+
return payment.address;
|
|
393
368
|
}
|
|
394
369
|
function getNativeSegwitAddress({ index, keychain, network }) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
370
|
+
if (!keychain) throw new Error("Expected keychain to be provided");
|
|
371
|
+
if (keychain.depth !== DerivationPathDepth.Account) throw new Error("Expects keychain to be on the account index");
|
|
372
|
+
const addressIndex = deriveAddressIndexKeychainFromAccount(keychain)(index);
|
|
373
|
+
if (!addressIndex.publicKey) throw new Error("Expected publicKey to be defined");
|
|
374
|
+
const payment = getNativeSegwitPaymentFromAddressIndex(addressIndex, network);
|
|
375
|
+
if (!payment.address) throw new Error("Expected address to be defined");
|
|
376
|
+
return payment.address;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* @deprecated
|
|
380
|
+
* Use `deriveRootBip32Keychain` in `@leather.io/crypto` instead
|
|
381
|
+
*/
|
|
404
382
|
function mnemonicToRootNode(secretKey) {
|
|
405
|
-
|
|
406
|
-
|
|
383
|
+
const seed = mnemonicToSeedSync(secretKey);
|
|
384
|
+
return HDKey.fromMasterSeed(seed);
|
|
407
385
|
}
|
|
408
386
|
function getPsbtTxInputs(psbtTx) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
387
|
+
const inputsLength = psbtTx.inputsLength;
|
|
388
|
+
const inputs = [];
|
|
389
|
+
for (let i = 0; i < inputsLength; i++) inputs.push(psbtTx.getInput(i));
|
|
390
|
+
return inputs;
|
|
413
391
|
}
|
|
414
392
|
function getPsbtTxOutputs(psbtTx) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
function inferNetworkFromAddress(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
function inferPaymentTypeFromAddress(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return "p2tr";
|
|
435
|
-
throw new Error("Unable to infer payment type from address");
|
|
393
|
+
const outputsLength = psbtTx.outputsLength;
|
|
394
|
+
const outputs = [];
|
|
395
|
+
for (let i = 0; i < outputsLength; i++) outputs.push(psbtTx.getOutput(i));
|
|
396
|
+
return outputs;
|
|
397
|
+
}
|
|
398
|
+
function inferNetworkFromAddress(address) {
|
|
399
|
+
if (address.startsWith("bc1")) return "mainnet";
|
|
400
|
+
if (address.startsWith("tb1")) return "testnet";
|
|
401
|
+
if (address.startsWith("bcrt1")) return "regtest";
|
|
402
|
+
const firstChar = address[0];
|
|
403
|
+
if (firstChar === "1" || firstChar === "3") return "mainnet";
|
|
404
|
+
if (firstChar === "m" || firstChar === "n") return "testnet";
|
|
405
|
+
if (firstChar === "2") return "testnet";
|
|
406
|
+
throw new Error("Invalid or unsupported Bitcoin address format");
|
|
407
|
+
}
|
|
408
|
+
function inferPaymentTypeFromAddress(address) {
|
|
409
|
+
if (address.startsWith("bc1q") || address.startsWith("tb1q") || address.startsWith("bcrt1q")) return "p2wpkh";
|
|
410
|
+
if (address.startsWith("bc1p") || address.startsWith("tb1p") || address.startsWith("bcrt1p")) return "p2tr";
|
|
411
|
+
throw new Error("Unable to infer payment type from address");
|
|
436
412
|
}
|
|
437
413
|
function getBitcoinInputValue(input) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return 0;
|
|
414
|
+
if (isDefined(input.witnessUtxo)) return Number(input.witnessUtxo.amount);
|
|
415
|
+
if (isDefined(input.nonWitnessUtxo) && isDefined(input.index)) return Number(input.nonWitnessUtxo.outputs[input.index]?.amount);
|
|
416
|
+
return 0;
|
|
442
417
|
}
|
|
443
418
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/bip322/bip322-utils.ts
|
|
421
|
+
const bip322MessageTag = "BIP0322-signed-message";
|
|
422
|
+
const ECPair = ECPairFactory(ecc);
|
|
423
|
+
bitcoinJs.initEccLib(ecc);
|
|
448
424
|
function ecPairFromPrivateKey(key) {
|
|
449
|
-
|
|
425
|
+
return ECPair.fromPrivateKey(Buffer.from(key));
|
|
450
426
|
}
|
|
451
|
-
|
|
452
|
-
...sha256(utf8ToBytes(bip322MessageTag)),
|
|
453
|
-
...sha256(utf8ToBytes(bip322MessageTag))
|
|
454
|
-
]);
|
|
427
|
+
const messageTagHash = Uint8Array.from([...sha256(utf8ToBytes(bip322MessageTag)), ...sha256(utf8ToBytes(bip322MessageTag))]);
|
|
455
428
|
function hashBip322Message(message) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
prevoutIndex: 4294967295,
|
|
463
|
-
sequence: 0
|
|
429
|
+
return sha256(Uint8Array.from([...messageTagHash, ...isString(message) ? utf8ToBytes(message) : message]));
|
|
430
|
+
}
|
|
431
|
+
const bip322TransactionToSignValues = {
|
|
432
|
+
prevoutHash: hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"),
|
|
433
|
+
prevoutIndex: 4294967295,
|
|
434
|
+
sequence: 0
|
|
464
435
|
};
|
|
465
436
|
function encodeVarString(b) {
|
|
466
|
-
|
|
437
|
+
return Buffer.concat([encode$1(b.byteLength), b]);
|
|
467
438
|
}
|
|
468
|
-
|
|
439
|
+
const supportedMessageSigningPaymentTypes = ["p2wpkh", "p2tr"];
|
|
469
440
|
function isSupportedMessageSigningPaymentType(paymentType) {
|
|
470
|
-
|
|
441
|
+
return supportedMessageSigningPaymentTypes.includes(paymentType);
|
|
471
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Encode witness data for a BIP322 message
|
|
445
|
+
* TODO: Refactor to remove `Buffer` use
|
|
446
|
+
*/
|
|
472
447
|
function encodeMessageWitnessData(witnessArray) {
|
|
473
|
-
|
|
474
|
-
|
|
448
|
+
const len = encode$1(witnessArray.length);
|
|
449
|
+
return Buffer.concat([len, ...witnessArray.map((witness) => encodeVarString(witness))]);
|
|
475
450
|
}
|
|
476
451
|
function tapTweakHash(pubKey, h) {
|
|
477
|
-
|
|
452
|
+
return bitcoinJs.crypto.taggedHash("TapTweak", Buffer.concat(h ? [pubKey, h] : [pubKey]));
|
|
478
453
|
}
|
|
479
454
|
function tweakSigner(signer, opts = {}) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
const tweakedPrivateKey = ecc.privateAdd(
|
|
488
|
-
privateKey,
|
|
489
|
-
tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)
|
|
490
|
-
);
|
|
491
|
-
if (!tweakedPrivateKey) {
|
|
492
|
-
throw new Error("Invalid tweaked private key!");
|
|
493
|
-
}
|
|
494
|
-
return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
|
|
495
|
-
network: opts.network
|
|
496
|
-
});
|
|
455
|
+
let privateKey = signer.privateKey;
|
|
456
|
+
if (!privateKey) throw new Error("Private key is required for tweaking signer!");
|
|
457
|
+
if (signer.publicKey[0] === 3) privateKey = ecc.privateNegate(privateKey);
|
|
458
|
+
const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash));
|
|
459
|
+
if (!tweakedPrivateKey) throw new Error("Invalid tweaked private key!");
|
|
460
|
+
return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { network: opts.network });
|
|
497
461
|
}
|
|
498
462
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
import * as bitcoin2 from "bitcoinjs-lib";
|
|
463
|
+
//#endregion
|
|
464
|
+
//#region src/bip322/sign-message-bip322-bitcoinjs.ts
|
|
502
465
|
function createNativeSegwitBitcoinJsSigner(privateKey) {
|
|
503
|
-
|
|
466
|
+
return ecPairFromPrivateKey(privateKey);
|
|
504
467
|
}
|
|
505
468
|
function createTaprootBitcoinJsSigner(privateKey) {
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
function createToSpendTx(
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
function createToSignTx(toSpendTxHex,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
469
|
+
return tweakSigner(ecPairFromPrivateKey(privateKey));
|
|
470
|
+
}
|
|
471
|
+
function createToSpendTx(address, message, network) {
|
|
472
|
+
const { prevoutHash, prevoutIndex, sequence } = bip322TransactionToSignValues;
|
|
473
|
+
const script = bitcoinJs.address.toOutputScript(address, getBitcoinJsLibNetworkConfigByMode(network));
|
|
474
|
+
const hash = hashBip322Message(message);
|
|
475
|
+
const commands = [0, Buffer.from(hash)];
|
|
476
|
+
const scriptSig = bitcoinJs.script.compile(commands);
|
|
477
|
+
const virtualToSpend = new bitcoinJs.Transaction();
|
|
478
|
+
virtualToSpend.version = 0;
|
|
479
|
+
virtualToSpend.addInput(Buffer.from(prevoutHash), prevoutIndex, sequence, scriptSig);
|
|
480
|
+
virtualToSpend.addOutput(script, 0);
|
|
481
|
+
return {
|
|
482
|
+
virtualToSpend,
|
|
483
|
+
script
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function createToSignTx(toSpendTxHex, script, network) {
|
|
487
|
+
const virtualToSign = new bitcoinJs.Psbt({ network: getBitcoinJsLibNetworkConfigByMode(network) });
|
|
488
|
+
virtualToSign.setVersion(0);
|
|
489
|
+
const prevTxHash = toSpendTxHex;
|
|
490
|
+
const prevOutIndex = 0;
|
|
491
|
+
const toSignScriptSig = bitcoinJs.script.compile([bitcoinJs.script.OPS.OP_RETURN]);
|
|
492
|
+
virtualToSign.addInput({
|
|
493
|
+
hash: prevTxHash,
|
|
494
|
+
index: prevOutIndex,
|
|
495
|
+
sequence: 0,
|
|
496
|
+
witnessUtxo: {
|
|
497
|
+
script,
|
|
498
|
+
value: 0
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
virtualToSign.addOutput({
|
|
502
|
+
script: toSignScriptSig,
|
|
503
|
+
value: 0
|
|
504
|
+
});
|
|
505
|
+
return virtualToSign;
|
|
537
506
|
}
|
|
538
507
|
async function signBip322MessageSimple(args) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
};
|
|
508
|
+
const { address, message, network, signPsbt } = args;
|
|
509
|
+
const { virtualToSpend, script } = createToSpendTx(address, message, network);
|
|
510
|
+
const signedTx = await signPsbt(createToSignTx(virtualToSpend.getHash(), script, network));
|
|
511
|
+
const asBitcoinJsTransaction = bitcoinJs.Psbt.fromBuffer(Buffer.from(signedTx.toPSBT()));
|
|
512
|
+
asBitcoinJsTransaction.finalizeInput(0);
|
|
513
|
+
const toSignTx = asBitcoinJsTransaction.extractTransaction();
|
|
514
|
+
const result = encodeMessageWitnessData(toSignTx.ins[0].witness);
|
|
515
|
+
return {
|
|
516
|
+
virtualToSpend,
|
|
517
|
+
virtualToSign: toSignTx,
|
|
518
|
+
unencodedSig: result,
|
|
519
|
+
signature: base64.encode(result)
|
|
520
|
+
};
|
|
553
521
|
}
|
|
554
522
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
import { createMoney, satToBtc } from "@leather.io/utils";
|
|
558
|
-
|
|
559
|
-
// src/coin-selection/coin-selection.utils.ts
|
|
560
|
-
import BigNumber2 from "bignumber.js";
|
|
561
|
-
import validate2, { AddressType, getAddressInfo } from "bitcoin-address-validation";
|
|
562
|
-
import { BTC_P2WPKH_DUST_AMOUNT } from "@leather.io/constants";
|
|
563
|
-
import { sumNumbers } from "@leather.io/utils";
|
|
564
|
-
|
|
565
|
-
// src/fees/btc-size-fee-estimator.ts
|
|
566
|
-
import BigNumber from "bignumber.js";
|
|
567
|
-
import { assertUnreachable } from "@leather.io/utils";
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/fees/btc-size-fee-estimator.ts
|
|
568
525
|
var BtcSizeFeeEstimator = class {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
case "p2wpkh":
|
|
734
|
-
inputSize = this.P2WPKH_IN_SIZE;
|
|
735
|
-
inputWitnessSize = 107;
|
|
736
|
-
break;
|
|
737
|
-
case "p2tr":
|
|
738
|
-
inputSize = this.P2TR_IN_SIZE;
|
|
739
|
-
inputWitnessSize = 65;
|
|
740
|
-
break;
|
|
741
|
-
case "p2sh":
|
|
742
|
-
redeemScriptSize = 1 + // OP_M
|
|
743
|
-
this.params.input_n * (1 + this.PUBKEY_SIZE) + // OP_PUSH33 <pubkey>
|
|
744
|
-
1 + // OP_N
|
|
745
|
-
1;
|
|
746
|
-
const scriptSigSize = 1 + // size(0)
|
|
747
|
-
this.params.input_m * (1 + this.SIGNATURE_SIZE) + // size(SIGNATURE_SIZE) + signature
|
|
748
|
-
this.getSizeOfScriptLengthElement(redeemScriptSize) + redeemScriptSize;
|
|
749
|
-
inputSize = 32 + 4 + this.getSizeOfletInt(scriptSigSize) + scriptSigSize + 4;
|
|
750
|
-
break;
|
|
751
|
-
case "p2sh-p2wsh":
|
|
752
|
-
case "p2wsh":
|
|
753
|
-
redeemScriptSize = 1 + // OP_M
|
|
754
|
-
this.params.input_n * (1 + this.PUBKEY_SIZE) + // OP_PUSH33 <pubkey>
|
|
755
|
-
1 + // OP_N
|
|
756
|
-
1;
|
|
757
|
-
inputWitnessSize = 1 + // size(0)
|
|
758
|
-
this.params.input_m * (1 + this.SIGNATURE_SIZE) + // size(SIGNATURE_SIZE) + signature
|
|
759
|
-
this.getSizeOfScriptLengthElement(redeemScriptSize) + redeemScriptSize;
|
|
760
|
-
inputSize = 36 + // outpoint (spent UTXO ID)
|
|
761
|
-
inputWitnessSize / 4 + // witness program
|
|
762
|
-
4;
|
|
763
|
-
if (this.params.input_script === "p2sh-p2wsh") {
|
|
764
|
-
inputSize += 32 + 3;
|
|
765
|
-
}
|
|
766
|
-
break;
|
|
767
|
-
default:
|
|
768
|
-
assertUnreachable(this.params.input_script);
|
|
769
|
-
}
|
|
770
|
-
return {
|
|
771
|
-
inputSize,
|
|
772
|
-
inputWitnessSize
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
calcTxSize(opts) {
|
|
776
|
-
this.prepareParams(opts);
|
|
777
|
-
const output_count = this.getOutputCount();
|
|
778
|
-
const { inputSize, inputWitnessSize } = this.getSizeBasedOnInputType();
|
|
779
|
-
const txVBytes = this.getTxOverheadVBytes(this.params.input_script, this.params.input_count, output_count) + inputSize * this.params.input_count + this.P2PKH_OUT_SIZE * this.params.p2pkh_output_count + this.P2SH_OUT_SIZE * this.params.p2sh_output_count + this.P2SH_P2WPKH_OUT_SIZE * this.params.p2sh_p2wpkh_output_count + this.P2SH_P2WSH_OUT_SIZE * this.params.p2sh_p2wsh_output_count + this.P2WPKH_OUT_SIZE * this.params.p2wpkh_output_count + this.P2WSH_OUT_SIZE * this.params.p2wsh_output_count + this.P2TR_OUT_SIZE * this.params.p2tr_output_count;
|
|
780
|
-
const txBytes = this.getTxOverheadExtraRawBytes(this.params.input_script, this.params.input_count) + txVBytes + inputWitnessSize * this.params.input_count;
|
|
781
|
-
const txWeight = txVBytes * 4;
|
|
782
|
-
return { txVBytes, txBytes, txWeight };
|
|
783
|
-
}
|
|
784
|
-
estimateFee(vbyte, satVb) {
|
|
785
|
-
if (isNaN(vbyte) || isNaN(satVb)) {
|
|
786
|
-
throw new Error("Parameters should be numbers");
|
|
787
|
-
}
|
|
788
|
-
return vbyte * satVb;
|
|
789
|
-
}
|
|
790
|
-
formatFeeRange(fee, multiplier) {
|
|
791
|
-
if (isNaN(fee) || isNaN(multiplier)) {
|
|
792
|
-
throw new Error("Parameters should be numbers");
|
|
793
|
-
}
|
|
794
|
-
if (multiplier < 0) {
|
|
795
|
-
throw new Error("Multiplier cant be negative");
|
|
796
|
-
}
|
|
797
|
-
const multipliedFee = fee * multiplier;
|
|
798
|
-
return fee - multipliedFee + " - " + (fee + multipliedFee);
|
|
799
|
-
}
|
|
526
|
+
P2PKH_IN_SIZE = 148;
|
|
527
|
+
P2PKH_OUT_SIZE = 34;
|
|
528
|
+
P2SH_OUT_SIZE = 32;
|
|
529
|
+
P2SH_P2WPKH_OUT_SIZE = 32;
|
|
530
|
+
P2SH_P2WSH_OUT_SIZE = 32;
|
|
531
|
+
P2SH_P2WPKH_IN_SIZE = 91;
|
|
532
|
+
P2WPKH_IN_SIZE = 67.75;
|
|
533
|
+
P2WPKH_OUT_SIZE = 31;
|
|
534
|
+
P2WSH_OUT_SIZE = 43;
|
|
535
|
+
P2TR_OUT_SIZE = 43;
|
|
536
|
+
P2TR_IN_SIZE = 57.25;
|
|
537
|
+
PUBKEY_SIZE = 33;
|
|
538
|
+
SIGNATURE_SIZE = 72;
|
|
539
|
+
SUPPORTED_INPUT_SCRIPT_TYPES = [
|
|
540
|
+
"p2pkh",
|
|
541
|
+
"p2sh",
|
|
542
|
+
"p2sh-p2wpkh",
|
|
543
|
+
"p2sh-p2wsh",
|
|
544
|
+
"p2wpkh",
|
|
545
|
+
"p2wsh",
|
|
546
|
+
"p2tr"
|
|
547
|
+
];
|
|
548
|
+
defaultParams = {
|
|
549
|
+
input_count: 0,
|
|
550
|
+
input_script: "p2wpkh",
|
|
551
|
+
input_m: 0,
|
|
552
|
+
input_n: 0,
|
|
553
|
+
p2pkh_output_count: 0,
|
|
554
|
+
p2sh_output_count: 0,
|
|
555
|
+
p2sh_p2wpkh_output_count: 0,
|
|
556
|
+
p2sh_p2wsh_output_count: 0,
|
|
557
|
+
p2wpkh_output_count: 0,
|
|
558
|
+
p2wsh_output_count: 0,
|
|
559
|
+
p2tr_output_count: 0
|
|
560
|
+
};
|
|
561
|
+
params = { ...this.defaultParams };
|
|
562
|
+
getSizeOfScriptLengthElement(length) {
|
|
563
|
+
if (length < 75) return 1;
|
|
564
|
+
else if (length <= 255) return 2;
|
|
565
|
+
else if (length <= 65535) return 3;
|
|
566
|
+
else if (length <= 4294967295) return 5;
|
|
567
|
+
else throw new Error("Size of redeem script is too large");
|
|
568
|
+
}
|
|
569
|
+
getSizeOfletInt(length) {
|
|
570
|
+
if (length < 253) return 1;
|
|
571
|
+
else if (length < 65535) return 3;
|
|
572
|
+
else if (length < 4294967295) return 5;
|
|
573
|
+
else if (new BigNumber(length).isLessThan("18446744073709551615")) return 9;
|
|
574
|
+
else throw new Error("Invalid let int");
|
|
575
|
+
}
|
|
576
|
+
getTxOverheadVBytes(input_script, input_count, output_count) {
|
|
577
|
+
let witness_vbytes;
|
|
578
|
+
if (input_script === "p2pkh" || input_script === "p2sh") witness_vbytes = 0;
|
|
579
|
+
else witness_vbytes = .5 + this.getSizeOfletInt(input_count) / 4;
|
|
580
|
+
return 4 + this.getSizeOfletInt(input_count) + this.getSizeOfletInt(output_count) + 4 + witness_vbytes;
|
|
581
|
+
}
|
|
582
|
+
getTxOverheadExtraRawBytes(input_script, input_count) {
|
|
583
|
+
let witness_vbytes;
|
|
584
|
+
if (input_script === "p2pkh" || input_script === "p2sh") witness_vbytes = 0;
|
|
585
|
+
else witness_vbytes = .5 + this.getSizeOfletInt(input_count) / 4;
|
|
586
|
+
return witness_vbytes * 3;
|
|
587
|
+
}
|
|
588
|
+
prepareParams(opts) {
|
|
589
|
+
opts = opts || Object.assign(this.defaultParams);
|
|
590
|
+
const input_count = opts.input_count || this.defaultParams.input_count;
|
|
591
|
+
if (!Number.isInteger(input_count) || input_count < 0) throw new Error("expecting positive input count, got: " + input_count);
|
|
592
|
+
const input_script = opts.input_script || this.defaultParams.input_script;
|
|
593
|
+
if (this.SUPPORTED_INPUT_SCRIPT_TYPES.indexOf(input_script) === -1) throw new Error("Not supported input script type");
|
|
594
|
+
const input_m = opts.input_m || this.defaultParams.input_m;
|
|
595
|
+
if (!Number.isInteger(input_m) || input_m < 0) throw new Error("expecting positive signature count");
|
|
596
|
+
const input_n = opts.input_n || this.defaultParams.input_n;
|
|
597
|
+
if (!Number.isInteger(input_n) || input_n < 0) throw new Error("expecting positive pubkey count");
|
|
598
|
+
const p2pkh_output_count = opts.p2pkh_output_count || this.defaultParams.p2pkh_output_count;
|
|
599
|
+
if (!Number.isInteger(p2pkh_output_count) || p2pkh_output_count < 0) throw new Error("expecting positive p2pkh output count");
|
|
600
|
+
const p2sh_output_count = opts.p2sh_output_count || this.defaultParams.p2sh_output_count;
|
|
601
|
+
if (!Number.isInteger(p2sh_output_count) || p2sh_output_count < 0) throw new Error("expecting positive p2sh output count");
|
|
602
|
+
const p2sh_p2wpkh_output_count = opts.p2sh_p2wpkh_output_count || this.defaultParams.p2sh_p2wpkh_output_count;
|
|
603
|
+
if (!Number.isInteger(p2sh_p2wpkh_output_count) || p2sh_p2wpkh_output_count < 0) throw new Error("expecting positive p2sh-p2wpkh output count");
|
|
604
|
+
const p2sh_p2wsh_output_count = opts.p2sh_p2wsh_output_count || this.defaultParams.p2sh_p2wsh_output_count;
|
|
605
|
+
if (!Number.isInteger(p2sh_p2wsh_output_count) || p2sh_p2wsh_output_count < 0) throw new Error("expecting positive p2sh-p2wsh output count");
|
|
606
|
+
const p2wpkh_output_count = opts.p2wpkh_output_count || this.defaultParams.p2wpkh_output_count;
|
|
607
|
+
if (!Number.isInteger(p2wpkh_output_count) || p2wpkh_output_count < 0) throw new Error("expecting positive p2wpkh output count");
|
|
608
|
+
const p2wsh_output_count = opts.p2wsh_output_count || this.defaultParams.p2wsh_output_count;
|
|
609
|
+
if (!Number.isInteger(p2wsh_output_count) || p2wsh_output_count < 0) throw new Error("expecting positive p2wsh output count");
|
|
610
|
+
const p2tr_output_count = opts.p2tr_output_count || this.defaultParams.p2tr_output_count;
|
|
611
|
+
if (!Number.isInteger(p2tr_output_count) || p2tr_output_count < 0) throw new Error("expecting positive p2tr output count");
|
|
612
|
+
this.params = {
|
|
613
|
+
input_count,
|
|
614
|
+
input_script,
|
|
615
|
+
input_m,
|
|
616
|
+
input_n,
|
|
617
|
+
p2pkh_output_count,
|
|
618
|
+
p2sh_output_count,
|
|
619
|
+
p2sh_p2wpkh_output_count,
|
|
620
|
+
p2sh_p2wsh_output_count,
|
|
621
|
+
p2wpkh_output_count,
|
|
622
|
+
p2wsh_output_count,
|
|
623
|
+
p2tr_output_count
|
|
624
|
+
};
|
|
625
|
+
return this.params;
|
|
626
|
+
}
|
|
627
|
+
getOutputCount() {
|
|
628
|
+
return this.params.p2pkh_output_count + this.params.p2sh_output_count + this.params.p2sh_p2wpkh_output_count + this.params.p2sh_p2wsh_output_count + this.params.p2wpkh_output_count + this.params.p2wsh_output_count + this.params.p2tr_output_count;
|
|
629
|
+
}
|
|
630
|
+
getSizeBasedOnInputType() {
|
|
631
|
+
let inputSize = 0;
|
|
632
|
+
let inputWitnessSize = 0;
|
|
633
|
+
let redeemScriptSize;
|
|
634
|
+
switch (this.params.input_script) {
|
|
635
|
+
case "p2pkh":
|
|
636
|
+
inputSize = this.P2PKH_IN_SIZE;
|
|
637
|
+
break;
|
|
638
|
+
case "p2sh-p2wpkh":
|
|
639
|
+
inputSize = this.P2SH_P2WPKH_IN_SIZE;
|
|
640
|
+
inputWitnessSize = 107;
|
|
641
|
+
break;
|
|
642
|
+
case "p2wpkh":
|
|
643
|
+
inputSize = this.P2WPKH_IN_SIZE;
|
|
644
|
+
inputWitnessSize = 107;
|
|
645
|
+
break;
|
|
646
|
+
case "p2tr":
|
|
647
|
+
inputSize = this.P2TR_IN_SIZE;
|
|
648
|
+
inputWitnessSize = 65;
|
|
649
|
+
break;
|
|
650
|
+
case "p2sh":
|
|
651
|
+
redeemScriptSize = 1 + this.params.input_n * (1 + this.PUBKEY_SIZE) + 1 + 1;
|
|
652
|
+
const scriptSigSize = 1 + this.params.input_m * (1 + this.SIGNATURE_SIZE) + this.getSizeOfScriptLengthElement(redeemScriptSize) + redeemScriptSize;
|
|
653
|
+
inputSize = 36 + this.getSizeOfletInt(scriptSigSize) + scriptSigSize + 4;
|
|
654
|
+
break;
|
|
655
|
+
case "p2sh-p2wsh":
|
|
656
|
+
case "p2wsh":
|
|
657
|
+
redeemScriptSize = 1 + this.params.input_n * (1 + this.PUBKEY_SIZE) + 1 + 1;
|
|
658
|
+
inputWitnessSize = 1 + this.params.input_m * (1 + this.SIGNATURE_SIZE) + this.getSizeOfScriptLengthElement(redeemScriptSize) + redeemScriptSize;
|
|
659
|
+
inputSize = 36 + inputWitnessSize / 4 + 4;
|
|
660
|
+
if (this.params.input_script === "p2sh-p2wsh") inputSize += 35;
|
|
661
|
+
break;
|
|
662
|
+
default: assertUnreachable(this.params.input_script);
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
inputSize,
|
|
666
|
+
inputWitnessSize
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
calcTxSize(opts) {
|
|
670
|
+
this.prepareParams(opts);
|
|
671
|
+
const output_count = this.getOutputCount();
|
|
672
|
+
const { inputSize, inputWitnessSize } = this.getSizeBasedOnInputType();
|
|
673
|
+
const txVBytes = this.getTxOverheadVBytes(this.params.input_script, this.params.input_count, output_count) + inputSize * this.params.input_count + this.P2PKH_OUT_SIZE * this.params.p2pkh_output_count + this.P2SH_OUT_SIZE * this.params.p2sh_output_count + this.P2SH_P2WPKH_OUT_SIZE * this.params.p2sh_p2wpkh_output_count + this.P2SH_P2WSH_OUT_SIZE * this.params.p2sh_p2wsh_output_count + this.P2WPKH_OUT_SIZE * this.params.p2wpkh_output_count + this.P2WSH_OUT_SIZE * this.params.p2wsh_output_count + this.P2TR_OUT_SIZE * this.params.p2tr_output_count;
|
|
674
|
+
return {
|
|
675
|
+
txVBytes,
|
|
676
|
+
txBytes: this.getTxOverheadExtraRawBytes(this.params.input_script, this.params.input_count) + txVBytes + inputWitnessSize * this.params.input_count,
|
|
677
|
+
txWeight: txVBytes * 4
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
estimateFee(vbyte, satVb) {
|
|
681
|
+
if (isNaN(vbyte) || isNaN(satVb)) throw new Error("Parameters should be numbers");
|
|
682
|
+
return vbyte * satVb;
|
|
683
|
+
}
|
|
684
|
+
formatFeeRange(fee, multiplier) {
|
|
685
|
+
if (isNaN(fee) || isNaN(multiplier)) throw new Error("Parameters should be numbers");
|
|
686
|
+
if (multiplier < 0) throw new Error("Multiplier cant be negative");
|
|
687
|
+
const multipliedFee = fee * multiplier;
|
|
688
|
+
return fee - multipliedFee + " - " + (fee + multipliedFee);
|
|
689
|
+
}
|
|
800
690
|
};
|
|
801
691
|
|
|
802
|
-
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region src/coin-selection/coin-selection.utils.ts
|
|
803
694
|
function getUtxoTotal(utxos) {
|
|
804
|
-
|
|
695
|
+
return sumNumbers(utxos.map((utxo) => utxo.value));
|
|
805
696
|
}
|
|
806
697
|
function getSizeInfo(payload) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
function filterUneconomicalUtxos({
|
|
855
|
-
utxos,
|
|
856
|
-
feeRate,
|
|
857
|
-
recipients
|
|
858
|
-
}) {
|
|
859
|
-
const { spendableAmount: fullSpendableAmount } = getSpendableAmount({
|
|
860
|
-
utxos,
|
|
861
|
-
feeRate,
|
|
862
|
-
recipients
|
|
863
|
-
});
|
|
864
|
-
const filteredUtxos = utxos.filter((utxo) => Number(utxo.value) >= BTC_P2WPKH_DUST_AMOUNT).filter((utxo) => {
|
|
865
|
-
const { spendableAmount } = getSpendableAmount({
|
|
866
|
-
utxos: utxos.filter((u) => u.txid !== utxo.txid),
|
|
867
|
-
feeRate,
|
|
868
|
-
recipients
|
|
869
|
-
});
|
|
870
|
-
return spendableAmount.toNumber() < fullSpendableAmount.toNumber();
|
|
871
|
-
});
|
|
872
|
-
return filteredUtxos;
|
|
698
|
+
const { inputLength, recipients, isSendMax } = payload;
|
|
699
|
+
const validAddressesInfo = recipients.map((recipient) => validate$1(recipient.address) && getAddressInfo(recipient.address)).filter(Boolean);
|
|
700
|
+
function getTxOutputsLengthByPaymentType() {
|
|
701
|
+
return validAddressesInfo.reduce((acc, { type }) => {
|
|
702
|
+
acc[type] = (acc[type] || 0) + 1;
|
|
703
|
+
return acc;
|
|
704
|
+
}, {});
|
|
705
|
+
}
|
|
706
|
+
const outputTypesCount = getTxOutputsLengthByPaymentType();
|
|
707
|
+
if (!isSendMax) outputTypesCount[AddressType.p2wpkh] = (outputTypesCount[AddressType.p2wpkh] || 0) + 1;
|
|
708
|
+
const outputsData = Object.entries(outputTypesCount).reduce((acc, [type, count]) => {
|
|
709
|
+
acc[type + "_output_count"] = count;
|
|
710
|
+
return acc;
|
|
711
|
+
}, {});
|
|
712
|
+
return new BtcSizeFeeEstimator().calcTxSize({
|
|
713
|
+
input_script: "p2wpkh",
|
|
714
|
+
input_count: inputLength,
|
|
715
|
+
...outputsData
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
function getSpendableAmount({ utxos, feeRate, recipients }) {
|
|
719
|
+
const balance = utxos.map((utxo) => utxo.value).reduce((prevVal, curVal) => prevVal + curVal, 0);
|
|
720
|
+
const size = getSizeInfo({
|
|
721
|
+
inputLength: utxos.length,
|
|
722
|
+
recipients
|
|
723
|
+
});
|
|
724
|
+
const fee = Math.ceil(size.txVBytes * feeRate);
|
|
725
|
+
const bigNumberBalance = BigNumber(balance);
|
|
726
|
+
return {
|
|
727
|
+
spendableAmount: BigNumber.max(0, bigNumberBalance.minus(fee)),
|
|
728
|
+
fee
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function filterUneconomicalUtxos({ utxos, feeRate, recipients }) {
|
|
732
|
+
const { spendableAmount: fullSpendableAmount } = getSpendableAmount({
|
|
733
|
+
utxos,
|
|
734
|
+
feeRate,
|
|
735
|
+
recipients
|
|
736
|
+
});
|
|
737
|
+
return utxos.filter((utxo) => Number(utxo.value) >= BTC_P2WPKH_DUST_AMOUNT).filter((utxo) => {
|
|
738
|
+
const { spendableAmount } = getSpendableAmount({
|
|
739
|
+
utxos: utxos.filter((u) => u.txid !== utxo.txid),
|
|
740
|
+
feeRate,
|
|
741
|
+
recipients
|
|
742
|
+
});
|
|
743
|
+
return spendableAmount.toNumber() < fullSpendableAmount.toNumber();
|
|
744
|
+
});
|
|
873
745
|
}
|
|
874
746
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region src/coin-selection/calculate-max-spend.ts
|
|
749
|
+
function calculateMaxSpend({ recipient, utxos, feeRate, feeRates }) {
|
|
750
|
+
if (!utxos.length || !feeRates) return {
|
|
751
|
+
spendAllFee: 0,
|
|
752
|
+
amount: createMoney(0, "BTC"),
|
|
753
|
+
spendableBitcoin: new BigNumber(0)
|
|
754
|
+
};
|
|
755
|
+
const currentFeeRate = feeRate ?? feeRates.halfHourFee.toNumber();
|
|
756
|
+
const { spendableAmount, fee } = getSpendableAmount({
|
|
757
|
+
utxos: filterUneconomicalUtxos({
|
|
758
|
+
utxos,
|
|
759
|
+
feeRate: currentFeeRate,
|
|
760
|
+
recipients: [{
|
|
761
|
+
address: recipient,
|
|
762
|
+
amount: createMoney(0, "BTC")
|
|
763
|
+
}]
|
|
764
|
+
}),
|
|
765
|
+
feeRate: currentFeeRate,
|
|
766
|
+
recipients: [{
|
|
767
|
+
address: recipient,
|
|
768
|
+
amount: createMoney(0, "BTC")
|
|
769
|
+
}],
|
|
770
|
+
isSendMax: true
|
|
771
|
+
});
|
|
772
|
+
return {
|
|
773
|
+
spendAllFee: fee,
|
|
774
|
+
amount: createMoney(spendableAmount, "BTC"),
|
|
775
|
+
spendableBitcoin: satToBtc(spendableAmount)
|
|
776
|
+
};
|
|
905
777
|
}
|
|
906
778
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
value: changeAmount
|
|
980
|
-
}
|
|
981
|
-
] : [];
|
|
982
|
-
const outputs = [
|
|
983
|
-
...recipients.map(({ address: address2, amount: amount2 }) => ({
|
|
984
|
-
value: BigInt(amount2.amount.toNumber()),
|
|
985
|
-
address: address2
|
|
986
|
-
})),
|
|
987
|
-
...changeUtxos
|
|
988
|
-
];
|
|
989
|
-
return {
|
|
990
|
-
filteredUtxos,
|
|
991
|
-
inputs: neededUtxos,
|
|
992
|
-
outputs,
|
|
993
|
-
size: estimateTransactionSize().txVBytes,
|
|
994
|
-
fee: createMoney2(new BigNumber4(fee), "BTC"),
|
|
995
|
-
...estimateTransactionSize()
|
|
996
|
-
};
|
|
779
|
+
//#endregion
|
|
780
|
+
//#region src/coin-selection/coin-selection.ts
|
|
781
|
+
function determineUtxosForSpendAll({ feeRate, recipients, utxos }) {
|
|
782
|
+
recipients.forEach((recipient) => {
|
|
783
|
+
if (!validate(recipient.address)) throw new BitcoinError("InvalidAddress");
|
|
784
|
+
});
|
|
785
|
+
const filteredUtxos = filterUneconomicalUtxos({
|
|
786
|
+
utxos,
|
|
787
|
+
feeRate,
|
|
788
|
+
recipients
|
|
789
|
+
});
|
|
790
|
+
const sizeInfo = getSizeInfo({
|
|
791
|
+
inputLength: filteredUtxos.length,
|
|
792
|
+
isSendMax: true,
|
|
793
|
+
recipients
|
|
794
|
+
});
|
|
795
|
+
const outputs = recipients.map(({ address, amount }) => ({
|
|
796
|
+
value: BigInt(amount.amount.toNumber()),
|
|
797
|
+
address
|
|
798
|
+
}));
|
|
799
|
+
const fee = Math.ceil(sizeInfo.txVBytes * feeRate);
|
|
800
|
+
return {
|
|
801
|
+
inputs: filteredUtxos,
|
|
802
|
+
outputs,
|
|
803
|
+
size: sizeInfo.txVBytes,
|
|
804
|
+
fee: createMoney(new BigNumber(fee), "BTC")
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function determineUtxosForSpend({ feeRate, recipients, utxos }) {
|
|
808
|
+
recipients.forEach((recipient) => {
|
|
809
|
+
if (!validate(recipient.address)) throw new BitcoinError("InvalidAddress");
|
|
810
|
+
});
|
|
811
|
+
const filteredUtxos = filterUneconomicalUtxos({
|
|
812
|
+
utxos: utxos.sort((a, b) => b.value - a.value),
|
|
813
|
+
feeRate,
|
|
814
|
+
recipients
|
|
815
|
+
});
|
|
816
|
+
if (!filteredUtxos.length) throw new BitcoinError("InsufficientFunds");
|
|
817
|
+
const amount = sumMoney(recipients.map((recipient) => recipient.amount));
|
|
818
|
+
const neededUtxos = [filteredUtxos[0]];
|
|
819
|
+
function estimateTransactionSize() {
|
|
820
|
+
return getSizeInfo({
|
|
821
|
+
inputLength: neededUtxos.length,
|
|
822
|
+
recipients
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
function hasSufficientUtxosForTx() {
|
|
826
|
+
const neededAmount = new BigNumber(estimateTransactionSize().txVBytes * feeRate).plus(amount.amount);
|
|
827
|
+
return getUtxoTotal(neededUtxos).isGreaterThanOrEqualTo(neededAmount);
|
|
828
|
+
}
|
|
829
|
+
function getRemainingUnspentUtxos() {
|
|
830
|
+
return filteredUtxos.filter((utxo) => !neededUtxos.includes(utxo));
|
|
831
|
+
}
|
|
832
|
+
while (!hasSufficientUtxosForTx()) {
|
|
833
|
+
const [nextUtxo] = getRemainingUnspentUtxos();
|
|
834
|
+
if (!nextUtxo) throw new BitcoinError("InsufficientFunds");
|
|
835
|
+
neededUtxos.push(nextUtxo);
|
|
836
|
+
}
|
|
837
|
+
const fee = Math.ceil(new BigNumber(estimateTransactionSize().txVBytes).multipliedBy(feeRate).toNumber());
|
|
838
|
+
const changeAmount = BigInt(getUtxoTotal(neededUtxos).toString()) - BigInt(amount.amount.toNumber()) - BigInt(fee);
|
|
839
|
+
const changeUtxos = changeAmount > BTC_P2WPKH_DUST_AMOUNT ? [{ value: changeAmount }] : [];
|
|
840
|
+
return {
|
|
841
|
+
filteredUtxos,
|
|
842
|
+
inputs: neededUtxos,
|
|
843
|
+
outputs: [...recipients.map(({ address, amount: amount$1 }) => ({
|
|
844
|
+
value: BigInt(amount$1.amount.toNumber()),
|
|
845
|
+
address
|
|
846
|
+
})), ...changeUtxos],
|
|
847
|
+
size: estimateTransactionSize().txVBytes,
|
|
848
|
+
fee: createMoney(new BigNumber(fee), "BTC"),
|
|
849
|
+
...estimateTransactionSize()
|
|
850
|
+
};
|
|
997
851
|
}
|
|
998
852
|
|
|
999
|
-
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/fees/bitcoin-fees.ts
|
|
1000
855
|
function getBitcoinTransactionFee({ isSendingMax, ...props }) {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
856
|
+
try {
|
|
857
|
+
const { fee } = isSendingMax ? determineUtxosForSpendAll({ ...props }) : determineUtxosForSpend({ ...props });
|
|
858
|
+
return fee;
|
|
859
|
+
} catch {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
1007
862
|
}
|
|
1008
863
|
function getBitcoinFees({ feeRates, isSendingMax, recipients, utxos }) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
864
|
+
const defaultArgs = {
|
|
865
|
+
isSendingMax,
|
|
866
|
+
recipients,
|
|
867
|
+
utxos
|
|
868
|
+
};
|
|
869
|
+
const highFeeRate = feeRates.fastestFee.toNumber();
|
|
870
|
+
const standardFeeRate = feeRates.halfHourFee.toNumber();
|
|
871
|
+
const lowFeeRate = feeRates.hourFee.toNumber();
|
|
872
|
+
const highFeeValue = getBitcoinTransactionFee({
|
|
873
|
+
...defaultArgs,
|
|
874
|
+
feeRate: highFeeRate
|
|
875
|
+
});
|
|
876
|
+
const standardFeeValue = getBitcoinTransactionFee({
|
|
877
|
+
...defaultArgs,
|
|
878
|
+
feeRate: standardFeeRate
|
|
879
|
+
});
|
|
880
|
+
const lowFeeValue = getBitcoinTransactionFee({
|
|
881
|
+
...defaultArgs,
|
|
882
|
+
feeRate: lowFeeRate
|
|
883
|
+
});
|
|
884
|
+
return {
|
|
885
|
+
high: {
|
|
886
|
+
feeRate: highFeeRate,
|
|
887
|
+
fee: highFeeValue
|
|
888
|
+
},
|
|
889
|
+
standard: {
|
|
890
|
+
feeRate: standardFeeRate,
|
|
891
|
+
fee: standardFeeValue
|
|
892
|
+
},
|
|
893
|
+
low: {
|
|
894
|
+
feeRate: lowFeeRate,
|
|
895
|
+
fee: lowFeeValue
|
|
896
|
+
}
|
|
897
|
+
};
|
|
1034
898
|
}
|
|
1035
899
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
);
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
);
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
);
|
|
1052
|
-
var TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS = createBitcoinAddress(
|
|
1053
|
-
"tb1pve00jmp43whpqj2wpcxtc7m8wqhz0azq689y4r7h8tmj8ltaj87qj2nj6w"
|
|
1054
|
-
);
|
|
1055
|
-
var recipientAddress = createBitcoinAddress("tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m");
|
|
1056
|
-
var legacyAddress = createBitcoinAddress("15PyZveQd28E2SHZu2ugkWZBp6iER41vXj");
|
|
1057
|
-
var segwitAddress = createBitcoinAddress("33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH");
|
|
1058
|
-
var taprootAddress = createBitcoinAddress(
|
|
1059
|
-
"tb1parwmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd"
|
|
1060
|
-
);
|
|
1061
|
-
var invalidAddress = "whoop-de-da-boop-da-de-not-a-bitcoin-address";
|
|
1062
|
-
var inValidCharactersAddress = createBitcoinAddress(
|
|
1063
|
-
"tb1&*%wmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd"
|
|
1064
|
-
);
|
|
1065
|
-
var inValidLengthAddress = createBitcoinAddress("tb1parwmj7533de3k2fw2kntyqacspvhm67wd");
|
|
900
|
+
//#endregion
|
|
901
|
+
//#region src/mocks/mocks.ts
|
|
902
|
+
const TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS = createBitcoinAddress("bc1q530dz4h80kwlzywlhx2qn0k6vdtftd93c499yq");
|
|
903
|
+
const TEST_ACCOUNT_1_TAPROOT_ADDRESS = createBitcoinAddress("bc1putuzj9lyfcm8fef9jpy85nmh33cxuq9u6wyuk536t9kemdk37yjqmkc0pg");
|
|
904
|
+
const TEST_ACCOUNT_2_TAPROOT_ADDRESS = createBitcoinAddress("bc1pmk2sacpfyy4v5phl8tq6eggu4e8laztep7fsgkkx0nc6m9vydjesaw0g2r");
|
|
905
|
+
const TEST_TESNET_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS = createBitcoinAddress("tb1q4qgnjewwun2llgken94zqjrx5kpqqycaz5522d");
|
|
906
|
+
const TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS = createBitcoinAddress("tb1qr8me8t9gu9g6fu926ry5v44yp0wyljrespjtnz");
|
|
907
|
+
const TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS = createBitcoinAddress("tb1pve00jmp43whpqj2wpcxtc7m8wqhz0azq689y4r7h8tmj8ltaj87qj2nj6w");
|
|
908
|
+
const recipientAddress = createBitcoinAddress("tb1qt28eagxcl9gvhq2rpj5slg7dwgxae2dn2hk93m");
|
|
909
|
+
const legacyAddress = createBitcoinAddress("15PyZveQd28E2SHZu2ugkWZBp6iER41vXj");
|
|
910
|
+
const segwitAddress = createBitcoinAddress("33SVjoCHJovrXxjDKLFSXo1h3t5KgkPzfH");
|
|
911
|
+
const taprootAddress = createBitcoinAddress("tb1parwmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd");
|
|
912
|
+
const invalidAddress = "whoop-de-da-boop-da-de-not-a-bitcoin-address";
|
|
913
|
+
const inValidCharactersAddress = createBitcoinAddress("tb1&*%wmj7533de3k2fw2kntyqacspvhm67qnjcmpqnnpfvzu05l69nsczdywd");
|
|
914
|
+
const inValidLengthAddress = createBitcoinAddress("tb1parwmj7533de3k2fw2kntyqacspvhm67wd");
|
|
1066
915
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
import { z } from "zod";
|
|
1070
|
-
import { isEmptyString as isEmptyString2, isUndefined as isUndefined2 } from "@leather.io/utils";
|
|
916
|
+
//#endregion
|
|
917
|
+
//#region src/schemas/address-schema.ts
|
|
1071
918
|
function nonEmptyStringValidator(message = "") {
|
|
1072
|
-
|
|
919
|
+
return z.string().refine((value) => value !== void 0 && value.trim() !== "", { message });
|
|
1073
920
|
}
|
|
1074
921
|
function btcAddressValidator() {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
},
|
|
1080
|
-
{ message: "Bitcoin address is not valid" }
|
|
1081
|
-
);
|
|
922
|
+
return z.string().refine((value) => {
|
|
923
|
+
if (isUndefined(value) || isEmptyString(value)) return true;
|
|
924
|
+
return validate(value);
|
|
925
|
+
}, { message: "Bitcoin address is not valid" });
|
|
1082
926
|
}
|
|
1083
|
-
function getNetworkTypeFromAddress(
|
|
1084
|
-
|
|
927
|
+
function getNetworkTypeFromAddress(address) {
|
|
928
|
+
return getAddressInfo(address).network;
|
|
1085
929
|
}
|
|
1086
930
|
function btcAddressNetworkValidatorFactory(network) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
931
|
+
function getAddressNetworkType(network$1) {
|
|
932
|
+
if (network$1 === "signet") return Network.testnet;
|
|
933
|
+
return network$1;
|
|
934
|
+
}
|
|
935
|
+
return (value) => {
|
|
936
|
+
if (isUndefined(value) || isEmptyString(value)) return true;
|
|
937
|
+
return validate(value, getAddressNetworkType(network));
|
|
938
|
+
};
|
|
1095
939
|
}
|
|
1096
940
|
function btcAddressNetworkValidator(network) {
|
|
1097
|
-
|
|
1098
|
-
message: "Address is for incorrect network"
|
|
1099
|
-
});
|
|
941
|
+
return z.string().refine(btcAddressNetworkValidatorFactory(network), { message: "Address is for incorrect network" });
|
|
1100
942
|
}
|
|
1101
943
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
944
|
+
//#endregion
|
|
945
|
+
//#region src/payments/p2wsh-p2sh-address-gen.ts
|
|
946
|
+
/**
|
|
947
|
+
* @deprecated
|
|
948
|
+
* Use `deriveBip39MnemonicFromSeed` from `@leather.io/crypto`
|
|
949
|
+
*/
|
|
950
|
+
const deriveBtcBip49SeedFromMnemonic = deriveBip39SeedFromMnemonic;
|
|
951
|
+
/**
|
|
952
|
+
* @deprecated
|
|
953
|
+
* Use `deriveRootBip32Keychain` from `@leather.io/crypto`
|
|
954
|
+
*/
|
|
955
|
+
const deriveRootBtcKeychain = deriveRootBip32Keychain;
|
|
1109
956
|
function decodeCompressedWifPrivateKey(key) {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
957
|
+
const compressedWifFormatPrivateKey = base58check(sha256).decode(key);
|
|
958
|
+
return compressedWifFormatPrivateKey.slice(1, compressedWifFormatPrivateKey.length - 1);
|
|
959
|
+
}
|
|
960
|
+
const payToScriptHashMainnetPrefix = 5;
|
|
961
|
+
const payToScriptHashTestnetPrefix = 196;
|
|
962
|
+
const payToScriptHashPrefixMap = {
|
|
963
|
+
mainnet: payToScriptHashMainnetPrefix,
|
|
964
|
+
testnet: payToScriptHashTestnetPrefix
|
|
1118
965
|
};
|
|
1119
966
|
function hash160(input) {
|
|
1120
|
-
|
|
967
|
+
return ripemd160(sha256(input));
|
|
1121
968
|
}
|
|
1122
969
|
function makePayToScriptHashKeyHash(publicKey) {
|
|
1123
|
-
|
|
970
|
+
return hash160(publicKey);
|
|
1124
971
|
}
|
|
1125
972
|
function makePayToScriptHashAddressBytes(keyHash) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
return hash160(redeemScript);
|
|
973
|
+
return hash160(Uint8Array.from([
|
|
974
|
+
...Uint8Array.of(0),
|
|
975
|
+
...Uint8Array.of(keyHash.length),
|
|
976
|
+
...keyHash
|
|
977
|
+
]));
|
|
1132
978
|
}
|
|
1133
979
|
function makePayToScriptHashAddress(addressBytes, network) {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
980
|
+
const networkByte = payToScriptHashPrefixMap[network];
|
|
981
|
+
const addressWithPrefix = Uint8Array.from([networkByte, ...addressBytes]);
|
|
982
|
+
return base58check(sha256).encode(addressWithPrefix);
|
|
1137
983
|
}
|
|
1138
984
|
function publicKeyToPayToScriptHashAddress(publicKey, network) {
|
|
1139
|
-
|
|
1140
|
-
const addrBytes = makePayToScriptHashAddressBytes(hash);
|
|
1141
|
-
return makePayToScriptHashAddress(addrBytes, network);
|
|
985
|
+
return makePayToScriptHashAddress(makePayToScriptHashAddressBytes(makePayToScriptHashKeyHash(publicKey)), network);
|
|
1142
986
|
}
|
|
1143
987
|
|
|
1144
|
-
|
|
1145
|
-
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region src/psbt/psbt-totals.ts
|
|
1146
990
|
function calculateAddressInputsTotal(addresses, inputs) {
|
|
1147
|
-
|
|
1148
|
-
(address2) => inputs.filter((input) => input.address === address2).map((input) => input.value).reduce((acc, curVal) => acc + curVal, 0)
|
|
1149
|
-
);
|
|
1150
|
-
return createMoney3(sumNumbers2(sumsByAddress), "BTC");
|
|
991
|
+
return createMoney(sumNumbers(addresses.map((address) => inputs.filter((input) => input.address === address).map((input) => input.value).reduce((acc, curVal) => acc + curVal, 0))), "BTC");
|
|
1151
992
|
}
|
|
1152
993
|
function calculateAddressOutputsTotal(addresses, outputs) {
|
|
1153
|
-
|
|
1154
|
-
(address2) => outputs.filter((output) => output.address === address2).map((output) => Number(output.value)).reduce((acc, curVal) => acc + curVal, 0)
|
|
1155
|
-
);
|
|
1156
|
-
return createMoney3(sumNumbers2(sumsByAddress), "BTC");
|
|
994
|
+
return createMoney(sumNumbers(addresses.map((address) => outputs.filter((output) => output.address === address).map((output) => Number(output.value)).reduce((acc, curVal) => acc + curVal, 0))), "BTC");
|
|
1157
995
|
}
|
|
1158
996
|
function calculatePsbtInputsTotal(inputs) {
|
|
1159
|
-
|
|
997
|
+
return createMoney(sumNumbers(inputs.map((input) => input.value)), "BTC");
|
|
1160
998
|
}
|
|
1161
999
|
function calculatePsbtOutputsTotal(outputs) {
|
|
1162
|
-
|
|
1000
|
+
return createMoney(sumNumbers(outputs.map((output) => output.value)), "BTC");
|
|
1163
1001
|
}
|
|
1164
1002
|
function getPsbtTotals({ psbtAddresses, parsedInputs, parsedOutputs }) {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
outputsTotalTaproot: calculateAddressOutputsTotal(taprootAddresses, parsedOutputs),
|
|
1176
|
-
psbtInputsTotal: calculatePsbtInputsTotal(parsedInputs),
|
|
1177
|
-
psbtOutputsTotal: calculatePsbtOutputsTotal(parsedOutputs)
|
|
1178
|
-
};
|
|
1003
|
+
const nativeSegwitAddresses = psbtAddresses.filter((addr) => inferPaymentTypeFromAddress(addr) === "p2wpkh");
|
|
1004
|
+
const taprootAddresses = psbtAddresses.filter((addr) => inferPaymentTypeFromAddress(addr) === "p2tr");
|
|
1005
|
+
return {
|
|
1006
|
+
inputsTotalNativeSegwit: calculateAddressInputsTotal(nativeSegwitAddresses, parsedInputs),
|
|
1007
|
+
inputsTotalTaproot: calculateAddressInputsTotal(taprootAddresses, parsedInputs),
|
|
1008
|
+
outputsTotalNativeSegwit: calculateAddressOutputsTotal(nativeSegwitAddresses, parsedOutputs),
|
|
1009
|
+
outputsTotalTaproot: calculateAddressOutputsTotal(taprootAddresses, parsedOutputs),
|
|
1010
|
+
psbtInputsTotal: calculatePsbtInputsTotal(parsedInputs),
|
|
1011
|
+
psbtOutputsTotal: calculatePsbtOutputsTotal(parsedOutputs)
|
|
1012
|
+
};
|
|
1179
1013
|
}
|
|
1180
1014
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
toSign: toSignAll || toSignIndex,
|
|
1209
|
-
txid: input.txid ? bytesToHex(input.txid) : "",
|
|
1210
|
-
value: isDefined2(input.index) ? getBitcoinInputValue(input) : 0
|
|
1211
|
-
};
|
|
1212
|
-
});
|
|
1213
|
-
const isPsbtMutable = psbtInputs.some((input) => input.isMutable);
|
|
1214
|
-
return { isPsbtMutable, parsedInputs: psbtInputs };
|
|
1015
|
+
//#endregion
|
|
1016
|
+
//#region src/psbt/psbt-inputs.ts
|
|
1017
|
+
function getParsedInputs({ inputs, indexesToSign, networkMode, psbtAddresses }) {
|
|
1018
|
+
const bitcoinNetwork = getBtcSignerLibNetworkConfigByMode(networkMode);
|
|
1019
|
+
const signAll = isUndefined(indexesToSign);
|
|
1020
|
+
const psbtInputs = inputs.map((input, i) => {
|
|
1021
|
+
const bitcoinAddress = isDefined(input.index) ? getBitcoinInputAddress(input, bitcoinNetwork) : null;
|
|
1022
|
+
if (bitcoinAddress === null) throw new Error("PSBT input has unsupported bitcoin address");
|
|
1023
|
+
const isCurrentAddress = psbtAddresses.includes(bitcoinAddress);
|
|
1024
|
+
const canChange = isCurrentAddress && !(!input.sighashType || input.sighashType === 0 || input.sighashType === 1);
|
|
1025
|
+
const toSignAll = isCurrentAddress && signAll;
|
|
1026
|
+
const toSignIndex = isCurrentAddress && !signAll && indexesToSign.includes(i);
|
|
1027
|
+
return {
|
|
1028
|
+
address: bitcoinAddress,
|
|
1029
|
+
index: input.index,
|
|
1030
|
+
bip32Derivation: input.bip32Derivation,
|
|
1031
|
+
tapBip32Derivation: input.tapBip32Derivation,
|
|
1032
|
+
isMutable: canChange,
|
|
1033
|
+
toSign: toSignAll || toSignIndex,
|
|
1034
|
+
txid: input.txid ? bytesToHex(input.txid) : "",
|
|
1035
|
+
value: isDefined(input.index) ? getBitcoinInputValue(input) : 0
|
|
1036
|
+
};
|
|
1037
|
+
});
|
|
1038
|
+
return {
|
|
1039
|
+
isPsbtMutable: psbtInputs.some((input) => input.isMutable),
|
|
1040
|
+
parsedInputs: psbtInputs
|
|
1041
|
+
};
|
|
1215
1042
|
}
|
|
1216
1043
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
function getParsedOutputs({
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const isCurrentAddress = !!outputAddress && psbtAddresses.includes(outputAddress);
|
|
1232
|
-
return {
|
|
1233
|
-
address: outputAddress,
|
|
1234
|
-
isMutable: isPsbtMutable,
|
|
1235
|
-
toSign: isCurrentAddress,
|
|
1236
|
-
value: Number(output.amount)
|
|
1237
|
-
};
|
|
1238
|
-
}).filter(isDefined3);
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region src/psbt/psbt-outputs.ts
|
|
1046
|
+
function getParsedOutputs({ isPsbtMutable, outputs, networkMode, psbtAddresses }) {
|
|
1047
|
+
const bitcoinNetwork = getBtcSignerLibNetworkConfigByMode(networkMode);
|
|
1048
|
+
return outputs.map((output) => {
|
|
1049
|
+
if (isUndefined(output.script)) return;
|
|
1050
|
+
const outputAddress = getAddressFromOutScript(output.script, bitcoinNetwork);
|
|
1051
|
+
return {
|
|
1052
|
+
address: outputAddress,
|
|
1053
|
+
isMutable: isPsbtMutable,
|
|
1054
|
+
toSign: !!outputAddress && psbtAddresses.includes(outputAddress),
|
|
1055
|
+
value: Number(output.amount)
|
|
1056
|
+
};
|
|
1057
|
+
}).filter(isDefined);
|
|
1239
1058
|
}
|
|
1240
1059
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
// src/psbt/utils.ts
|
|
1245
|
-
import { hexToBytes as hexToBytes3 } from "@noble/hashes/utils";
|
|
1246
|
-
import * as btc4 from "@scure/btc-signer";
|
|
1247
|
-
import { RawPSBTV0, RawPSBTV2 } from "@scure/btc-signer/psbt";
|
|
1248
|
-
import { isString as isString2 } from "@leather.io/utils";
|
|
1060
|
+
//#endregion
|
|
1061
|
+
//#region src/psbt/utils.ts
|
|
1249
1062
|
function getPsbtAsTransaction(psbt) {
|
|
1250
|
-
|
|
1251
|
-
|
|
1063
|
+
const bytes = isString(psbt) ? hexToBytes(psbt) : psbt;
|
|
1064
|
+
return btc.Transaction.fromPSBT(bytes);
|
|
1252
1065
|
}
|
|
1253
1066
|
function getRawPsbt(psbt) {
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1067
|
+
const bytes = isString(psbt) ? hexToBytes(psbt) : psbt;
|
|
1068
|
+
try {
|
|
1069
|
+
return RawPSBTV0.decode(bytes);
|
|
1070
|
+
} catch (e1) {
|
|
1071
|
+
try {
|
|
1072
|
+
return RawPSBTV2.decode(bytes);
|
|
1073
|
+
} catch (e2) {
|
|
1074
|
+
throw new Error(`Unable to decode PSBT, ${e1 ?? e2}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1264
1077
|
}
|
|
1265
1078
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
addressNativeSegwitTotal: subtractMoney(inputsTotalNativeSegwit, outputsTotalNativeSegwit),
|
|
1302
|
-
addressTaprootTotal: subtractMoney(inputsTotalTaproot, outputsTotalTaproot),
|
|
1303
|
-
fee: getFee(),
|
|
1304
|
-
isPsbtMutable,
|
|
1305
|
-
psbtInputs: parsedInputs,
|
|
1306
|
-
psbtOutputs: parsedOutputs
|
|
1307
|
-
};
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region src/psbt/psbt-details.ts
|
|
1081
|
+
function getPsbtDetails({ psbtHex, networkMode, indexesToSign, psbtAddresses }) {
|
|
1082
|
+
const tx = getPsbtAsTransaction(psbtHex);
|
|
1083
|
+
const inputs = getPsbtTxInputs(tx);
|
|
1084
|
+
const outputs = getPsbtTxOutputs(tx);
|
|
1085
|
+
const { isPsbtMutable, parsedInputs } = getParsedInputs({
|
|
1086
|
+
inputs,
|
|
1087
|
+
indexesToSign,
|
|
1088
|
+
networkMode,
|
|
1089
|
+
psbtAddresses
|
|
1090
|
+
});
|
|
1091
|
+
const parsedOutputs = getParsedOutputs({
|
|
1092
|
+
isPsbtMutable,
|
|
1093
|
+
outputs,
|
|
1094
|
+
networkMode,
|
|
1095
|
+
psbtAddresses
|
|
1096
|
+
});
|
|
1097
|
+
const { inputsTotalNativeSegwit, inputsTotalTaproot, outputsTotalNativeSegwit, outputsTotalTaproot, psbtInputsTotal, psbtOutputsTotal } = getPsbtTotals({
|
|
1098
|
+
psbtAddresses,
|
|
1099
|
+
parsedInputs,
|
|
1100
|
+
parsedOutputs
|
|
1101
|
+
});
|
|
1102
|
+
function getFee() {
|
|
1103
|
+
if (psbtInputsTotal.amount.isGreaterThan(psbtOutputsTotal.amount)) return subtractMoney(psbtInputsTotal, psbtOutputsTotal);
|
|
1104
|
+
return createMoney(0, "BTC");
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
addressNativeSegwitTotal: subtractMoney(inputsTotalNativeSegwit, outputsTotalNativeSegwit),
|
|
1108
|
+
addressTaprootTotal: subtractMoney(inputsTotalTaproot, outputsTotalTaproot),
|
|
1109
|
+
fee: getFee(),
|
|
1110
|
+
isPsbtMutable,
|
|
1111
|
+
psbtInputs: parsedInputs,
|
|
1112
|
+
psbtOutputs: parsedOutputs
|
|
1113
|
+
};
|
|
1308
1114
|
}
|
|
1309
1115
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
import * as btc5 from "@scure/btc-signer";
|
|
1313
|
-
import {
|
|
1314
|
-
DerivationPathDepth as DerivationPathDepth4,
|
|
1315
|
-
appendAddressIndexToPath,
|
|
1316
|
-
decomposeDescriptor,
|
|
1317
|
-
deriveKeychainFromXpub,
|
|
1318
|
-
extractAddressIndexFromPath,
|
|
1319
|
-
extractChangeIndexFromPath,
|
|
1320
|
-
keyOriginToDerivationPath
|
|
1321
|
-
} from "@leather.io/crypto";
|
|
1322
|
-
import { hexToNumber, toHexString } from "@leather.io/utils";
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/signer/bitcoin-signer.ts
|
|
1323
1118
|
function initializeBitcoinAccountKeychainFromDescriptor(descriptor) {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1119
|
+
const { fingerprint, keyOrigin } = decomposeDescriptor(descriptor);
|
|
1120
|
+
return {
|
|
1121
|
+
descriptor,
|
|
1122
|
+
xpub: extractExtendedPublicKeyFromPolicy(descriptor),
|
|
1123
|
+
keyOrigin,
|
|
1124
|
+
masterKeyFingerprint: fingerprint,
|
|
1125
|
+
keychain: deriveKeychainFromXpub(extractExtendedPublicKeyFromPolicy(descriptor))
|
|
1126
|
+
};
|
|
1332
1127
|
}
|
|
1333
1128
|
function deriveBitcoinPayerFromAccount(descriptor, network) {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1129
|
+
const { fingerprint, keyOrigin } = decomposeDescriptor(descriptor);
|
|
1130
|
+
const accountKeychain = deriveKeychainFromXpub(extractExtendedPublicKeyFromPolicy(descriptor));
|
|
1131
|
+
const paymentType = inferPaymentTypeFromPath(keyOrigin);
|
|
1132
|
+
if (accountKeychain.depth !== DerivationPathDepth.Account) throw new Error("Keychain passed is not an account");
|
|
1133
|
+
return ({ change, addressIndex }) => {
|
|
1134
|
+
const childKeychain = accountKeychain.deriveChild(change).deriveChild(addressIndex);
|
|
1135
|
+
const payment = whenSupportedPaymentType(paymentType)({
|
|
1136
|
+
p2tr: getTaprootPaymentFromAddressIndex,
|
|
1137
|
+
p2wpkh: getNativeSegwitPaymentFromAddressIndex
|
|
1138
|
+
})(childKeychain, network);
|
|
1139
|
+
return {
|
|
1140
|
+
keyOrigin: appendAddressIndexToPath(keyOrigin, change, addressIndex),
|
|
1141
|
+
masterKeyFingerprint: fingerprint,
|
|
1142
|
+
paymentType,
|
|
1143
|
+
network,
|
|
1144
|
+
payment,
|
|
1145
|
+
get address() {
|
|
1146
|
+
if (!payment.address) throw new Error("Payment address could not be derived");
|
|
1147
|
+
return payment.address;
|
|
1148
|
+
},
|
|
1149
|
+
get publicKey() {
|
|
1150
|
+
if (!childKeychain.publicKey) throw new Error("Public key could not be derived");
|
|
1151
|
+
return childKeychain.publicKey;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* @example
|
|
1158
|
+
* ```ts
|
|
1159
|
+
* tx.addInput({
|
|
1160
|
+
* ...input,
|
|
1161
|
+
* bip32Derivation: [payerToBip32Derivation(payer)],
|
|
1162
|
+
* })
|
|
1163
|
+
* ```
|
|
1164
|
+
*/
|
|
1363
1165
|
function payerToBip32Derivation(args) {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1166
|
+
return [args.publicKey, {
|
|
1167
|
+
fingerprint: hexToNumber(args.masterKeyFingerprint),
|
|
1168
|
+
path: btc.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
|
|
1169
|
+
}];
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* @example
|
|
1173
|
+
* ```ts
|
|
1174
|
+
* tx.addInput({
|
|
1175
|
+
* ...input,
|
|
1176
|
+
* tapBip32Derivation: [payerToTapBip32Derivation(payer)],
|
|
1177
|
+
* })
|
|
1178
|
+
* ```
|
|
1179
|
+
*/
|
|
1372
1180
|
function payerToTapBip32Derivation(args) {
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
fingerprint: hexToNumber(args.masterKeyFingerprint),
|
|
1381
|
-
path: btc5.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
];
|
|
1181
|
+
return [ecdsaPublicKeyToSchnorr(args.publicKey), {
|
|
1182
|
+
hashes: [],
|
|
1183
|
+
der: {
|
|
1184
|
+
fingerprint: hexToNumber(args.masterKeyFingerprint),
|
|
1185
|
+
path: btc.bip32Path(keyOriginToDerivationPath(args.keyOrigin))
|
|
1186
|
+
}
|
|
1187
|
+
}];
|
|
1385
1188
|
}
|
|
1386
1189
|
function payerToTapBip32DerivationBitcoinJsLib(args) {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1190
|
+
return {
|
|
1191
|
+
masterFingerprint: Buffer.from(args.masterKeyFingerprint, "hex"),
|
|
1192
|
+
path: keyOriginToDerivationPath(args.keyOrigin),
|
|
1193
|
+
leafHashes: [],
|
|
1194
|
+
pubkey: Buffer.from(ecdsaPublicKeyToSchnorr(args.publicKey))
|
|
1195
|
+
};
|
|
1393
1196
|
}
|
|
1394
1197
|
function payerToBip32DerivationBitcoinJsLib(args) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1198
|
+
return {
|
|
1199
|
+
masterFingerprint: Buffer.from(args.masterKeyFingerprint, "hex"),
|
|
1200
|
+
path: keyOriginToDerivationPath(args.keyOrigin),
|
|
1201
|
+
pubkey: Buffer.from(args.publicKey)
|
|
1202
|
+
};
|
|
1400
1203
|
}
|
|
1401
1204
|
function extractPayerInfoFromDerivationPath(path) {
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
}
|
|
1205
|
+
return {
|
|
1206
|
+
change: extractChangeIndexFromPath(path),
|
|
1207
|
+
addressIndex: extractAddressIndexFromPath(path)
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* @description
|
|
1212
|
+
* Turns key format from @scure/btc-signer lib back into key origin string
|
|
1213
|
+
* @example
|
|
1214
|
+
* ```ts
|
|
1215
|
+
* const [inputOne] = getPsbtTxInputs(tx);
|
|
1216
|
+
* const keyOrigin = serializeKeyOrigin(inputOne.bip32Derivation[0][1]);
|
|
1217
|
+
* ```
|
|
1218
|
+
*/
|
|
1407
1219
|
function serializeKeyOrigin({ fingerprint, path }) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
}
|
|
1220
|
+
const values = path.map((num) => num >= HARDENED_OFFSET ? num - HARDENED_OFFSET + "'" : num);
|
|
1221
|
+
return `${toHexString(fingerprint)}/${values.join("/")}`;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* @description
|
|
1225
|
+
* Of a given set of a `tx.input`s bip32 derivation paths from
|
|
1226
|
+
* `@scure/btc-signer`, serialize the paths back to the string format used
|
|
1227
|
+
* internally
|
|
1228
|
+
*/
|
|
1411
1229
|
function extractRequiredKeyOrigins(derivation) {
|
|
1412
|
-
|
|
1413
|
-
([_pubkey, path]) => serializeKeyOrigin("hashes" in path ? path.der : path)
|
|
1414
|
-
);
|
|
1230
|
+
return derivation.map(([_pubkey, path]) => serializeKeyOrigin("hashes" in path ? path.der : path));
|
|
1415
1231
|
}
|
|
1416
1232
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
function generateBitcoinUnsignedTransaction({
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1233
|
+
//#endregion
|
|
1234
|
+
//#region src/transactions/generate-unsigned-transaction.ts
|
|
1235
|
+
function generateBitcoinUnsignedTransaction({ feeRate, isSendingMax, network, recipients, changeAddress, utxos, payerLookup }) {
|
|
1236
|
+
const determineUtxosArgs = {
|
|
1237
|
+
feeRate,
|
|
1238
|
+
recipients,
|
|
1239
|
+
utxos
|
|
1240
|
+
};
|
|
1241
|
+
const { inputs, outputs, fee } = isSendingMax ? determineUtxosForSpendAll(determineUtxosArgs) : determineUtxosForSpend(determineUtxosArgs);
|
|
1242
|
+
if (!inputs.length) throw new BitcoinError("NoInputsToSign");
|
|
1243
|
+
if (!outputs.length) throw new BitcoinError("NoOutputsToSign");
|
|
1244
|
+
const tx = new btc.Transaction();
|
|
1245
|
+
for (const input of inputs) {
|
|
1246
|
+
const payer = payerLookup(input.keyOrigin);
|
|
1247
|
+
if (!payer) {
|
|
1248
|
+
console.log(`No payer found for input with keyOrigin ${input.keyOrigin}`);
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const bip32Derivation = payer.paymentType === "p2tr" ? { tapBip32Derivation: [payerToTapBip32Derivation(payer)] } : { bip32Derivation: [payerToBip32Derivation(payer)] };
|
|
1252
|
+
const tapInternalKey = payer.paymentType === "p2tr" ? { tapInternalKey: payer.payment.tapInternalKey } : {};
|
|
1253
|
+
tx.addInput({
|
|
1254
|
+
txid: input.txid,
|
|
1255
|
+
index: input.vout,
|
|
1256
|
+
witnessUtxo: {
|
|
1257
|
+
script: payer.payment.script,
|
|
1258
|
+
amount: BigInt(input.value)
|
|
1259
|
+
},
|
|
1260
|
+
...bip32Derivation,
|
|
1261
|
+
...tapInternalKey
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
outputs.forEach((output) => {
|
|
1265
|
+
if (!output.address) {
|
|
1266
|
+
tx.addOutputAddress(changeAddress, BigInt(output.value), network);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
tx.addOutputAddress(output.address, BigInt(output.value), network);
|
|
1270
|
+
});
|
|
1271
|
+
return {
|
|
1272
|
+
tx,
|
|
1273
|
+
hex: tx.hex,
|
|
1274
|
+
psbt: tx.toPSBT(),
|
|
1275
|
+
inputs,
|
|
1276
|
+
fee
|
|
1277
|
+
};
|
|
1460
1278
|
}
|
|
1461
1279
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1280
|
+
//#endregion
|
|
1281
|
+
//#region src/validation/amount-validation.ts
|
|
1464
1282
|
function isBtcBalanceSufficient({ desiredSpend, maxSpend }) {
|
|
1465
|
-
|
|
1283
|
+
return !desiredSpend.amount.isGreaterThan(maxSpend.amount);
|
|
1466
1284
|
}
|
|
1467
1285
|
function isBtcMinimumSpend(desiredSpend) {
|
|
1468
|
-
|
|
1286
|
+
return !desiredSpend.amount.isLessThan(BITCOIN_MINIMUM_SPEND_IN_SATS);
|
|
1469
1287
|
}
|
|
1470
1288
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
}
|
|
1487
|
-
if (!isBtcMinimumSpend(amount)) {
|
|
1488
|
-
throw new BitcoinError("InsufficientAmount");
|
|
1489
|
-
}
|
|
1490
|
-
const maxSpend = calculateMaxSpend({ recipient, utxos, feeRate, feeRates });
|
|
1491
|
-
if (!isBtcBalanceSufficient({ desiredSpend: amount, maxSpend: maxSpend.amount })) {
|
|
1492
|
-
throw new BitcoinError("InsufficientFunds");
|
|
1493
|
-
}
|
|
1289
|
+
//#endregion
|
|
1290
|
+
//#region src/validation/transaction-validation.ts
|
|
1291
|
+
function isValidBitcoinTransaction({ amount, payer, recipient, network, utxos, feeRate, feeRates }) {
|
|
1292
|
+
if (!isValidBitcoinAddress(payer) || !isValidBitcoinAddress(recipient)) throw new BitcoinError("InvalidAddress");
|
|
1293
|
+
if (!isValidBitcoinNetworkAddress(payer, network) || !isValidBitcoinNetworkAddress(recipient, network)) throw new BitcoinError("InvalidNetworkAddress");
|
|
1294
|
+
if (!isBtcMinimumSpend(amount)) throw new BitcoinError("InsufficientAmount");
|
|
1295
|
+
if (!isBtcBalanceSufficient({
|
|
1296
|
+
desiredSpend: amount,
|
|
1297
|
+
maxSpend: calculateMaxSpend({
|
|
1298
|
+
recipient,
|
|
1299
|
+
utxos,
|
|
1300
|
+
feeRate,
|
|
1301
|
+
feeRates
|
|
1302
|
+
}).amount
|
|
1303
|
+
})) throw new BitcoinError("InsufficientFunds");
|
|
1494
1304
|
}
|
|
1495
1305
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1306
|
+
//#endregion
|
|
1307
|
+
//#region src/utils/bitcoin.descriptors.ts
|
|
1498
1308
|
function getDescriptorFromKeychain(accountKeychain) {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
default:
|
|
1505
|
-
return void 0;
|
|
1506
|
-
}
|
|
1309
|
+
switch (inferPaymentTypeFromPath(accountKeychain.keyOrigin)) {
|
|
1310
|
+
case "p2tr": return `tr(${accountKeychain.xpub})`;
|
|
1311
|
+
case "p2wpkh": return `wpkh(${accountKeychain.xpub})`;
|
|
1312
|
+
default: return;
|
|
1313
|
+
}
|
|
1507
1314
|
}
|
|
1508
1315
|
function inferPaymentTypeFromDescriptor(descriptor) {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
return "p2wpkh";
|
|
1515
|
-
default:
|
|
1516
|
-
throw new Error("Unrecognized descriptor");
|
|
1517
|
-
}
|
|
1316
|
+
switch (descriptor.split("(")[0]) {
|
|
1317
|
+
case "tr": return "p2tr";
|
|
1318
|
+
case "wpkh": return "p2wpkh";
|
|
1319
|
+
default: throw new Error("Unrecognized descriptor");
|
|
1320
|
+
}
|
|
1518
1321
|
}
|
|
1519
1322
|
function extractXpubFromDescriptor(descriptor) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
})
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1323
|
+
const match = descriptor.match(/\((xpub[0-9A-Za-z]+)\)/);
|
|
1324
|
+
if (match && match[1]) return match[1];
|
|
1325
|
+
throw new Error("Invalid descriptor format");
|
|
1326
|
+
}
|
|
1327
|
+
function deriveAddressesFromDescriptor({ accountDescriptor, network, limit = 1 }) {
|
|
1328
|
+
const accountKeychain = HDKey.fromExtendedKey(extractXpubFromDescriptor(accountDescriptor));
|
|
1329
|
+
const paymentType = inferPaymentTypeFromDescriptor(accountDescriptor);
|
|
1330
|
+
const derivationPathFn = whenSupportedPaymentType(paymentType)({
|
|
1331
|
+
p2tr: makeTaprootAddressIndexDerivationPath,
|
|
1332
|
+
p2wpkh: makeNativeSegwitAddressIndexDerivationPath
|
|
1333
|
+
});
|
|
1334
|
+
const results = [];
|
|
1335
|
+
for (let i = 0; i < limit; i++) {
|
|
1336
|
+
const address = whenSupportedPaymentType(paymentType)({
|
|
1337
|
+
p2tr: getTaprootAddress({
|
|
1338
|
+
index: i,
|
|
1339
|
+
keychain: accountKeychain,
|
|
1340
|
+
network
|
|
1341
|
+
}),
|
|
1342
|
+
p2wpkh: getNativeSegwitAddress({
|
|
1343
|
+
index: i,
|
|
1344
|
+
keychain: accountKeychain,
|
|
1345
|
+
network
|
|
1346
|
+
})
|
|
1347
|
+
});
|
|
1348
|
+
results.push({
|
|
1349
|
+
address,
|
|
1350
|
+
path: derivationPathFn(network, accountKeychain.index - HARDENED_OFFSET, i)
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
return results;
|
|
1549
1354
|
}
|
|
1550
1355
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
import { createCounter } from "@leather.io/utils";
|
|
1356
|
+
//#endregion
|
|
1357
|
+
//#region src/utils/lookup-derivation-by-address.ts
|
|
1554
1358
|
function lookupDerivationByAddress(args) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
bip322TransactionToSignValues,
|
|
1608
|
-
bitcoinNetworkModeToCoreNetworkMode,
|
|
1609
|
-
bitcoinNetworkToCoreNetworkMap,
|
|
1610
|
-
btcAddressNetworkValidator,
|
|
1611
|
-
btcAddressValidator,
|
|
1612
|
-
btcSignerLibPaymentTypeToPaymentTypeMap,
|
|
1613
|
-
calculateMaxSpend,
|
|
1614
|
-
coinTypeMap,
|
|
1615
|
-
createBitcoinAddress,
|
|
1616
|
-
createNativeSegwitBitcoinJsSigner,
|
|
1617
|
-
createTaprootBitcoinJsSigner,
|
|
1618
|
-
createToSpendTx,
|
|
1619
|
-
createWalletIdDecoratedPath,
|
|
1620
|
-
decodeBitcoinTx,
|
|
1621
|
-
decodeCompressedWifPrivateKey,
|
|
1622
|
-
deriveAddressIndexKeychainFromAccount,
|
|
1623
|
-
deriveAddressIndexZeroFromAccount,
|
|
1624
|
-
deriveAddressesFromDescriptor,
|
|
1625
|
-
deriveBitcoinPayerFromAccount,
|
|
1626
|
-
deriveBtcBip49SeedFromMnemonic,
|
|
1627
|
-
deriveNativeSegwitAccountFromRootKeychain,
|
|
1628
|
-
deriveNativeSegwitReceiveAddressIndexZero,
|
|
1629
|
-
deriveRootBtcKeychain,
|
|
1630
|
-
deriveTaprootAccount,
|
|
1631
|
-
deriveTaprootReceiveAddressIndexZero,
|
|
1632
|
-
determineUtxosForSpend,
|
|
1633
|
-
determineUtxosForSpendAll,
|
|
1634
|
-
ecPairFromPrivateKey,
|
|
1635
|
-
ecdsaPublicKeyLength,
|
|
1636
|
-
ecdsaPublicKeyToSchnorr,
|
|
1637
|
-
encodeMessageWitnessData,
|
|
1638
|
-
extractExtendedPublicKeyFromPolicy,
|
|
1639
|
-
extractPayerInfoFromDerivationPath,
|
|
1640
|
-
extractRequiredKeyOrigins,
|
|
1641
|
-
extractXpubFromDescriptor,
|
|
1642
|
-
filterUneconomicalUtxos,
|
|
1643
|
-
generateBitcoinUnsignedTransaction,
|
|
1644
|
-
getAddressFromOutScript,
|
|
1645
|
-
getBitcoinAddressNetworkType,
|
|
1646
|
-
getBitcoinCoinTypeIndexByNetwork,
|
|
1647
|
-
getBitcoinFees,
|
|
1648
|
-
getBitcoinInputAddress,
|
|
1649
|
-
getBitcoinInputValue,
|
|
1650
|
-
getBitcoinJsLibNetworkConfigByMode,
|
|
1651
|
-
getBitcoinTransactionFee,
|
|
1652
|
-
getBtcSignerLibNetworkConfigByMode,
|
|
1653
|
-
getDescriptorFromKeychain,
|
|
1654
|
-
getHdKeyVersionsFromNetwork,
|
|
1655
|
-
getInputPaymentType,
|
|
1656
|
-
getNativeSegwitAccountDerivationPath,
|
|
1657
|
-
getNativeSegwitAddress,
|
|
1658
|
-
getNativeSegwitAddressIndexDerivationPath,
|
|
1659
|
-
getNativeSegwitPaymentFromAddressIndex,
|
|
1660
|
-
getNetworkTypeFromAddress,
|
|
1661
|
-
getParsedInputs,
|
|
1662
|
-
getParsedOutputs,
|
|
1663
|
-
getPsbtAsTransaction,
|
|
1664
|
-
getPsbtDetails,
|
|
1665
|
-
getPsbtTotals,
|
|
1666
|
-
getPsbtTxInputs,
|
|
1667
|
-
getPsbtTxOutputs,
|
|
1668
|
-
getRawPsbt,
|
|
1669
|
-
getSizeInfo,
|
|
1670
|
-
getSpendableAmount,
|
|
1671
|
-
getTaprootAccountDerivationPath,
|
|
1672
|
-
getTaprootAddress,
|
|
1673
|
-
getTaprootAddressIndexDerivationPath,
|
|
1674
|
-
getTaprootPayment,
|
|
1675
|
-
getTaprootPaymentFromAddressIndex,
|
|
1676
|
-
getUtxoTotal,
|
|
1677
|
-
hashBip322Message,
|
|
1678
|
-
inValidCharactersAddress,
|
|
1679
|
-
inValidLengthAddress,
|
|
1680
|
-
inferNetworkFromAddress,
|
|
1681
|
-
inferNetworkFromPath,
|
|
1682
|
-
inferPaymentTypeFromAddress,
|
|
1683
|
-
inferPaymentTypeFromDescriptor,
|
|
1684
|
-
inferPaymentTypeFromPath,
|
|
1685
|
-
initBitcoinAccount,
|
|
1686
|
-
initializeBitcoinAccountKeychainFromDescriptor,
|
|
1687
|
-
invalidAddress,
|
|
1688
|
-
isBitcoinAddress,
|
|
1689
|
-
isBtcBalanceSufficient,
|
|
1690
|
-
isBtcMinimumSpend,
|
|
1691
|
-
isBtcSignerLibPaymentType,
|
|
1692
|
-
isSupportedMessageSigningPaymentType,
|
|
1693
|
-
isValidBitcoinAddress,
|
|
1694
|
-
isValidBitcoinNetworkAddress,
|
|
1695
|
-
isValidBitcoinTransaction,
|
|
1696
|
-
legacyAddress,
|
|
1697
|
-
lookUpLedgerKeysByPath,
|
|
1698
|
-
lookupDerivationByAddress,
|
|
1699
|
-
makeNativeSegwitAccountDerivationPath,
|
|
1700
|
-
makeNativeSegwitAddressIndexDerivationPath,
|
|
1701
|
-
makePayToScriptHashAddress,
|
|
1702
|
-
makePayToScriptHashAddressBytes,
|
|
1703
|
-
makePayToScriptHashKeyHash,
|
|
1704
|
-
makeTaprootAccountDerivationPath,
|
|
1705
|
-
makeTaprootAddressIndexDerivationPath,
|
|
1706
|
-
mnemonicToRootNode,
|
|
1707
|
-
nonEmptyStringValidator,
|
|
1708
|
-
parseKnownPaymentType,
|
|
1709
|
-
payToScriptHashTestnetPrefix,
|
|
1710
|
-
payerToBip32Derivation,
|
|
1711
|
-
payerToBip32DerivationBitcoinJsLib,
|
|
1712
|
-
payerToTapBip32Derivation,
|
|
1713
|
-
payerToTapBip32DerivationBitcoinJsLib,
|
|
1714
|
-
paymentTypeMap,
|
|
1715
|
-
publicKeyToPayToScriptHashAddress,
|
|
1716
|
-
recipientAddress,
|
|
1717
|
-
segwitAddress,
|
|
1718
|
-
serializeKeyOrigin,
|
|
1719
|
-
signBip322MessageSimple,
|
|
1720
|
-
taprootAddress,
|
|
1721
|
-
toXOnly,
|
|
1722
|
-
tweakSigner,
|
|
1723
|
-
whenBitcoinNetwork,
|
|
1724
|
-
whenPaymentType,
|
|
1725
|
-
whenSupportedPaymentType
|
|
1726
|
-
};
|
|
1359
|
+
const { taprootXpub, nativeSegwitXpub, iterationLimit } = args;
|
|
1360
|
+
const taprootKeychain = HDKey.fromExtendedKey(taprootXpub);
|
|
1361
|
+
const nativeSegwitKeychain = HDKey.fromExtendedKey(nativeSegwitXpub);
|
|
1362
|
+
return (address) => {
|
|
1363
|
+
const network = inferNetworkFromAddress(address);
|
|
1364
|
+
const paymentType = inferPaymentTypeFromAddress(address);
|
|
1365
|
+
const accountIndex = whenSupportedPaymentType(paymentType)({
|
|
1366
|
+
p2tr: taprootKeychain.index - HARDENED_OFFSET,
|
|
1367
|
+
p2wpkh: nativeSegwitKeychain.index - HARDENED_OFFSET
|
|
1368
|
+
});
|
|
1369
|
+
function getTaprootAddressAtIndex(index) {
|
|
1370
|
+
return getTaprootAddress({
|
|
1371
|
+
index,
|
|
1372
|
+
keychain: taprootKeychain,
|
|
1373
|
+
network
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
function getNativeSegwitAddressAtIndex(index) {
|
|
1377
|
+
return getNativeSegwitAddress({
|
|
1378
|
+
index,
|
|
1379
|
+
keychain: nativeSegwitKeychain,
|
|
1380
|
+
network
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
const paymentFn = whenSupportedPaymentType(paymentType)({
|
|
1384
|
+
p2tr: getTaprootAddressAtIndex,
|
|
1385
|
+
p2wpkh: getNativeSegwitAddressAtIndex
|
|
1386
|
+
});
|
|
1387
|
+
const derivationPathFn = whenSupportedPaymentType(paymentType)({
|
|
1388
|
+
p2tr: makeTaprootAddressIndexDerivationPath,
|
|
1389
|
+
p2wpkh: makeNativeSegwitAddressIndexDerivationPath
|
|
1390
|
+
});
|
|
1391
|
+
const count = createCounter();
|
|
1392
|
+
const t0 = performance.now();
|
|
1393
|
+
while (count.getValue() <= iterationLimit) {
|
|
1394
|
+
const currentIndex = count.getValue();
|
|
1395
|
+
if (paymentFn(currentIndex) !== address) {
|
|
1396
|
+
count.increment();
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
status: "success",
|
|
1401
|
+
duration: performance.now() - t0,
|
|
1402
|
+
path: derivationPathFn(network, accountIndex, currentIndex)
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
return { status: "failure" };
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
//#endregion
|
|
1410
|
+
export { BitcoinError, TEST_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS, TEST_ACCOUNT_1_TAPROOT_ADDRESS, TEST_ACCOUNT_2_TAPROOT_ADDRESS, TEST_TESNET_ACCOUNT_1_NATIVE_SEGWIT_ADDRESS, TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS, TEST_TESTNET_ACCOUNT_2_TAPROOT_ADDRESS, bip21, bip322TransactionToSignValues, bitcoinNetworkModeToCoreNetworkMode, bitcoinNetworkToCoreNetworkMap, btcAddressNetworkValidator, btcAddressValidator, btcSignerLibPaymentTypeToPaymentTypeMap, calculateMaxSpend, coinTypeMap, createBitcoinAddress, createNativeSegwitBitcoinJsSigner, createTaprootBitcoinJsSigner, createToSpendTx, createWalletIdDecoratedPath, decodeBitcoinTx, decodeCompressedWifPrivateKey, deriveAddressIndexKeychainFromAccount, deriveAddressIndexZeroFromAccount, deriveAddressesFromDescriptor, deriveBitcoinPayerFromAccount, deriveBtcBip49SeedFromMnemonic, deriveNativeSegwitAccountFromRootKeychain, deriveNativeSegwitReceiveAddressIndexZero, deriveRootBtcKeychain, deriveTaprootAccount, deriveTaprootReceiveAddressIndexZero, determineUtxosForSpend, determineUtxosForSpendAll, ecPairFromPrivateKey, ecdsaPublicKeyLength, ecdsaPublicKeyToSchnorr, encodeMessageWitnessData, extractExtendedPublicKeyFromPolicy, extractPayerInfoFromDerivationPath, extractRequiredKeyOrigins, extractXpubFromDescriptor, filterUneconomicalUtxos, generateBitcoinUnsignedTransaction, getAddressFromOutScript, getBitcoinAddressNetworkType, getBitcoinCoinTypeIndexByNetwork, getBitcoinFees, getBitcoinInputAddress, getBitcoinInputValue, getBitcoinJsLibNetworkConfigByMode, getBitcoinTransactionFee, getBtcSignerLibNetworkConfigByMode, getDescriptorFromKeychain, getHdKeyVersionsFromNetwork, getInputPaymentType, getNativeSegwitAccountDerivationPath, getNativeSegwitAddress, getNativeSegwitAddressIndexDerivationPath, getNativeSegwitPaymentFromAddressIndex, getNetworkTypeFromAddress, getParsedInputs, getParsedOutputs, getPsbtAsTransaction, getPsbtDetails, getPsbtTotals, getPsbtTxInputs, getPsbtTxOutputs, getRawPsbt, getSizeInfo, getSpendableAmount, getTaprootAccountDerivationPath, getTaprootAddress, getTaprootAddressIndexDerivationPath, getTaprootPayment, getTaprootPaymentFromAddressIndex, getUtxoTotal, hashBip322Message, inValidCharactersAddress, inValidLengthAddress, inferNetworkFromAddress, inferNetworkFromPath, inferPaymentTypeFromAddress, inferPaymentTypeFromDescriptor, inferPaymentTypeFromPath, initBitcoinAccount, initializeBitcoinAccountKeychainFromDescriptor, invalidAddress, isBitcoinAddress, isBtcBalanceSufficient, isBtcMinimumSpend, isBtcSignerLibPaymentType, isSupportedMessageSigningPaymentType, isValidBitcoinAddress, isValidBitcoinNetworkAddress, isValidBitcoinTransaction, legacyAddress, lookUpLedgerKeysByPath, lookupDerivationByAddress, makeNativeSegwitAccountDerivationPath, makeNativeSegwitAddressIndexDerivationPath, makePayToScriptHashAddress, makePayToScriptHashAddressBytes, makePayToScriptHashKeyHash, makeTaprootAccountDerivationPath, makeTaprootAddressIndexDerivationPath, mnemonicToRootNode, nonEmptyStringValidator, parseKnownPaymentType, payToScriptHashTestnetPrefix, payerToBip32Derivation, payerToBip32DerivationBitcoinJsLib, payerToTapBip32Derivation, payerToTapBip32DerivationBitcoinJsLib, paymentTypeMap, publicKeyToPayToScriptHashAddress, recipientAddress, segwitAddress, serializeKeyOrigin, signBip322MessageSimple, taprootAddress, toXOnly, tweakSigner, whenBitcoinNetwork, whenPaymentType, whenSupportedPaymentType };
|
|
1727
1411
|
//# sourceMappingURL=index.js.map
|