@atomicfinance/bitcoin-utils 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @atomicfinance/bitcoin-utils
2
+
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - a06082e: Create unified standalone `bitcoin-abstraction-layer` package
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [a06082e]
12
+ - @atomicfinance/types@3.0.0
13
+ - @atomicfinance/crypto@3.0.0
14
+ - @atomicfinance/errors@3.0.0
15
+ - @atomicfinance/utils@3.0.0
@@ -0,0 +1,37 @@
1
+ /// <reference types="node" />
2
+ import { Address, bitcoin as bT, Transaction } from '@atomicfinance/types';
3
+ import { BitcoinNetwork } from 'bitcoin-networks';
4
+ declare const AddressTypes: string[];
5
+ declare function calculateFee(numInputs: number, numOutputs: number, feePerByte: number): number;
6
+ /**
7
+ * Get compressed pubKey from pubKey.
8
+ * @param {!string} pubKey - 65 byte string with prefix, x, y.
9
+ * @return {string} Returns the compressed pubKey of uncompressed pubKey.
10
+ */
11
+ declare function compressPubKey(pubKey: string): string;
12
+ /**
13
+ * Get a network object from an address
14
+ * @param {string} address The bitcoin address
15
+ * @return {Network}
16
+ */
17
+ declare function getAddressNetwork(address: string): BitcoinNetwork;
18
+ declare type CoinSelectTarget = {
19
+ value: number;
20
+ script?: Buffer;
21
+ id?: string;
22
+ };
23
+ declare function selectCoins(utxos: bT.UTXO[], targets: CoinSelectTarget[], feePerByte: number, fixedInputs?: bT.UTXO[]): {
24
+ inputs: bT.UTXO[];
25
+ outputs: CoinSelectTarget[];
26
+ fee: number;
27
+ change: any;
28
+ };
29
+ declare function decodeRawTransaction(hex: string, network: BitcoinNetwork): bT.Transaction;
30
+ declare function normalizeTransactionObject(tx: bT.Transaction, fee: number, block?: {
31
+ number: number;
32
+ hash: string;
33
+ }): Transaction<bT.Transaction>;
34
+ declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer;
35
+ declare function getPubKeyHash(address: string, network: BitcoinNetwork): Buffer;
36
+ declare function validateAddress(_address: Address | string, network: BitcoinNetwork): void;
37
+ export { calculateFee, compressPubKey, getAddressNetwork, CoinSelectTarget, selectCoins, decodeRawTransaction, normalizeTransactionObject, witnessStackToScriptWitness, AddressTypes, getPubKeyHash, validateAddress, };
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.validateAddress = exports.getPubKeyHash = exports.AddressTypes = exports.witnessStackToScriptWitness = exports.normalizeTransactionObject = exports.decodeRawTransaction = exports.selectCoins = exports.getAddressNetwork = exports.compressPubKey = exports.calculateFee = void 0;
26
+ const crypto_1 = require("@atomicfinance/crypto");
27
+ const errors_1 = require("@atomicfinance/errors");
28
+ const types_1 = require("@atomicfinance/types");
29
+ const utils_1 = require("@atomicfinance/utils");
30
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
31
+ const varuint = __importStar(require("bip174/src/lib/converter/varint"));
32
+ const bitcoin_networks_1 = require("bitcoin-networks");
33
+ const bitcoin = __importStar(require("bitcoinjs-lib"));
34
+ const classify = __importStar(require("bitcoinjs-lib/src/classify"));
35
+ const coinselect_1 = __importDefault(require("coinselect"));
36
+ const accumulative_1 = __importDefault(require("coinselect/accumulative"));
37
+ const lodash_1 = require("lodash");
38
+ const AddressTypes = ['legacy', 'p2sh-segwit', 'bech32'];
39
+ exports.AddressTypes = AddressTypes;
40
+ function calculateFee(numInputs, numOutputs, feePerByte) {
41
+ return (numInputs * 148 + numOutputs * 34 + 10) * feePerByte;
42
+ }
43
+ exports.calculateFee = calculateFee;
44
+ /**
45
+ * Get compressed pubKey from pubKey.
46
+ * @param {!string} pubKey - 65 byte string with prefix, x, y.
47
+ * @return {string} Returns the compressed pubKey of uncompressed pubKey.
48
+ */
49
+ function compressPubKey(pubKey) {
50
+ const x = pubKey.substring(2, 66);
51
+ const y = pubKey.substring(66, 130);
52
+ const even = parseInt(y.substring(62, 64), 16) % 2 === 0;
53
+ const prefix = even ? '02' : '03';
54
+ return prefix + x;
55
+ }
56
+ exports.compressPubKey = compressPubKey;
57
+ /**
58
+ * Get a network object from an address
59
+ * @param {string} address The bitcoin address
60
+ * @return {Network}
61
+ */
62
+ function getAddressNetwork(address) {
63
+ // TODO: can this be simplified using just bitcoinjs-lib??
64
+ let networkKey;
65
+ // bech32
66
+ networkKey = lodash_1.findKey(bitcoin_networks_1.BitcoinNetworks, (network) => address.startsWith(network.bech32));
67
+ // base58
68
+ if (!networkKey) {
69
+ const prefix = crypto_1.base58.decode(address).toString('hex').substring(0, 2);
70
+ networkKey = lodash_1.findKey(bitcoin_networks_1.BitcoinNetworks, (network) => {
71
+ const pubKeyHashPrefix = crypto_1.padHexStart(network.pubKeyHash.toString(16), 1);
72
+ const scriptHashPrefix = crypto_1.padHexStart(network.scriptHash.toString(16), 1);
73
+ return [pubKeyHashPrefix, scriptHashPrefix].includes(prefix);
74
+ });
75
+ }
76
+ return bitcoin_networks_1.BitcoinNetworks[networkKey];
77
+ }
78
+ exports.getAddressNetwork = getAddressNetwork;
79
+ function selectCoins(utxos, targets, feePerByte, fixedInputs = []) {
80
+ let selectUtxos = utxos;
81
+ // Default coinselect won't accumulate some inputs
82
+ // TODO: does coinselect need to be modified to ABSOLUTELY not skip an input?
83
+ const coinselectStrat = fixedInputs.length
84
+ ? accumulative_1.default
85
+ : coinselect_1.default;
86
+ if (fixedInputs.length) {
87
+ selectUtxos = [
88
+ // Order fixed inputs to the start of the list so they are used
89
+ ...fixedInputs,
90
+ ...utxos.filter((utxo) => !fixedInputs.find((input) => input.vout === utxo.vout && input.txid === utxo.txid)),
91
+ ];
92
+ }
93
+ const { inputs, outputs, fee } = coinselectStrat(selectUtxos, targets, Math.ceil(feePerByte));
94
+ let change;
95
+ if (inputs && outputs) {
96
+ change = outputs.find((output) => output.id !== 'main');
97
+ }
98
+ return { inputs, outputs, fee, change };
99
+ }
100
+ exports.selectCoins = selectCoins;
101
+ const OUTPUT_TYPES_MAP = {
102
+ [classify.types.P2WPKH]: 'witness_v0_keyhash',
103
+ [classify.types.P2WSH]: 'witness_v0_scripthash',
104
+ };
105
+ function decodeRawTransaction(hex, network) {
106
+ const bjsTx = bitcoin.Transaction.fromHex(hex);
107
+ const vin = bjsTx.ins.map((input) => {
108
+ return {
109
+ txid: Buffer.from(input.hash).reverse().toString('hex'),
110
+ vout: input.index,
111
+ scriptSig: {
112
+ asm: bitcoin.script.toASM(input.script),
113
+ hex: input.script.toString('hex'),
114
+ },
115
+ txinwitness: input.witness.map((w) => w.toString('hex')),
116
+ sequence: input.sequence,
117
+ };
118
+ });
119
+ const vout = bjsTx.outs.map((output, n) => {
120
+ const type = classify.output(output.script);
121
+ const vout = {
122
+ value: output.value / 1e8,
123
+ n,
124
+ scriptPubKey: {
125
+ asm: bitcoin.script.toASM(output.script),
126
+ hex: output.script.toString('hex'),
127
+ reqSigs: 1,
128
+ type: OUTPUT_TYPES_MAP[type] || type,
129
+ addresses: [],
130
+ },
131
+ };
132
+ try {
133
+ const address = bitcoin.address.fromOutputScript(output.script, network);
134
+ vout.scriptPubKey.addresses.push(address);
135
+ }
136
+ catch (e) {
137
+ /** If output script is not parasable, we just skip it */
138
+ }
139
+ return vout;
140
+ });
141
+ return {
142
+ txid: bjsTx.getHash(false).reverse().toString('hex'),
143
+ hash: bjsTx.getHash(true).reverse().toString('hex'),
144
+ version: bjsTx.version,
145
+ locktime: bjsTx.locktime,
146
+ size: bjsTx.byteLength(),
147
+ vsize: bjsTx.virtualSize(),
148
+ weight: bjsTx.weight(),
149
+ vin,
150
+ vout,
151
+ hex,
152
+ };
153
+ }
154
+ exports.decodeRawTransaction = decodeRawTransaction;
155
+ function normalizeTransactionObject(tx, fee, block) {
156
+ const value = tx.vout.reduce((p, n) => p.plus(new bignumber_js_1.default(n.value).times(1e8)), new bignumber_js_1.default(0));
157
+ const result = {
158
+ hash: tx.txid,
159
+ value: value.toNumber(),
160
+ _raw: tx,
161
+ confirmations: 0,
162
+ status: tx.confirmations > 0 ? types_1.TxStatus.Success : types_1.TxStatus.Pending,
163
+ };
164
+ if (fee) {
165
+ const feePrice = Math.round(fee / tx.vsize);
166
+ Object.assign(result, {
167
+ fee,
168
+ feePrice,
169
+ });
170
+ }
171
+ if (block) {
172
+ Object.assign(result, {
173
+ blockHash: block.hash,
174
+ blockNumber: block.number,
175
+ confirmations: tx.confirmations,
176
+ });
177
+ }
178
+ return result;
179
+ }
180
+ exports.normalizeTransactionObject = normalizeTransactionObject;
181
+ // TODO: This is copy pasta because it's not exported from bitcoinjs-lib
182
+ // https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.spec.ts#L477
183
+ function witnessStackToScriptWitness(witness) {
184
+ let buffer = Buffer.allocUnsafe(0);
185
+ function writeSlice(slice) {
186
+ buffer = Buffer.concat([buffer, Buffer.from(slice)]);
187
+ }
188
+ function writeVarInt(i) {
189
+ const currentLen = buffer.length;
190
+ const varintLen = varuint.encodingLength(i);
191
+ buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
192
+ varuint.encode(i, buffer, currentLen);
193
+ }
194
+ function writeVarSlice(slice) {
195
+ writeVarInt(slice.length);
196
+ writeSlice(slice);
197
+ }
198
+ function writeVector(vector) {
199
+ writeVarInt(vector.length);
200
+ vector.forEach(writeVarSlice);
201
+ }
202
+ writeVector(witness);
203
+ return buffer;
204
+ }
205
+ exports.witnessStackToScriptWitness = witnessStackToScriptWitness;
206
+ function getPubKeyHash(address, network) {
207
+ const outputScript = bitcoin.address.toOutputScript(address, network);
208
+ const type = classify.output(outputScript);
209
+ if (![classify.types.P2PKH, classify.types.P2WPKH].includes(type)) {
210
+ throw new Error(`Bitcoin swap doesn't support the address ${address} type of ${type}. Not possible to derive public key hash.`);
211
+ }
212
+ try {
213
+ const bech32 = bitcoin.address.fromBech32(address);
214
+ return bech32.data;
215
+ }
216
+ catch (e) {
217
+ const base58 = bitcoin.address.fromBase58Check(address);
218
+ return base58.hash;
219
+ }
220
+ }
221
+ exports.getPubKeyHash = getPubKeyHash;
222
+ function validateAddress(_address, network) {
223
+ const address = utils_1.addressToString(_address);
224
+ if (typeof address !== 'string') {
225
+ throw new errors_1.InvalidAddressError(`Invalid address: ${address}`);
226
+ }
227
+ let pubKeyHash;
228
+ try {
229
+ pubKeyHash = getPubKeyHash(address, network);
230
+ }
231
+ catch (e) {
232
+ throw new errors_1.InvalidAddressError(`Invalid Address. Failed to parse: ${address}`);
233
+ }
234
+ if (!pubKeyHash) {
235
+ throw new errors_1.InvalidAddressError(`Invalid Address: ${address}`);
236
+ }
237
+ }
238
+ exports.validateAddress = validateAddress;
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ declare module 'bitcoinjs-lib/src/classify'
@@ -0,0 +1 @@
1
+ declare module 'coinselect/accumulative'
@@ -0,0 +1 @@
1
+ declare module 'coinselect'
package/lib/index.ts ADDED
@@ -0,0 +1,308 @@
1
+ import { base58, padHexStart } from '@atomicfinance/crypto';
2
+ import { InvalidAddressError } from '@atomicfinance/errors';
3
+ import {
4
+ Address,
5
+ bitcoin as bT,
6
+ Transaction,
7
+ TxStatus,
8
+ } from '@atomicfinance/types';
9
+ import { addressToString } from '@atomicfinance/utils';
10
+ import BigNumber from 'bignumber.js';
11
+ import * as varuint from 'bip174/src/lib/converter/varint';
12
+ import { BitcoinNetwork, BitcoinNetworks } from 'bitcoin-networks';
13
+ import * as bitcoin from 'bitcoinjs-lib';
14
+ import * as classify from 'bitcoinjs-lib/src/classify';
15
+ import coinselect from 'coinselect';
16
+ import coinselectAccumulative from 'coinselect/accumulative';
17
+ import { findKey } from 'lodash';
18
+
19
+ const AddressTypes = ['legacy', 'p2sh-segwit', 'bech32'];
20
+
21
+ function calculateFee(
22
+ numInputs: number,
23
+ numOutputs: number,
24
+ feePerByte: number,
25
+ ) {
26
+ return (numInputs * 148 + numOutputs * 34 + 10) * feePerByte;
27
+ }
28
+
29
+ /**
30
+ * Get compressed pubKey from pubKey.
31
+ * @param {!string} pubKey - 65 byte string with prefix, x, y.
32
+ * @return {string} Returns the compressed pubKey of uncompressed pubKey.
33
+ */
34
+ function compressPubKey(pubKey: string) {
35
+ const x = pubKey.substring(2, 66);
36
+ const y = pubKey.substring(66, 130);
37
+ const even = parseInt(y.substring(62, 64), 16) % 2 === 0;
38
+ const prefix = even ? '02' : '03';
39
+
40
+ return prefix + x;
41
+ }
42
+
43
+ /**
44
+ * Get a network object from an address
45
+ * @param {string} address The bitcoin address
46
+ * @return {Network}
47
+ */
48
+ function getAddressNetwork(address: string) {
49
+ // TODO: can this be simplified using just bitcoinjs-lib??
50
+ let networkKey;
51
+ // bech32
52
+ networkKey = findKey(BitcoinNetworks, (network) =>
53
+ address.startsWith(network.bech32),
54
+ );
55
+ // base58
56
+ if (!networkKey) {
57
+ const prefix = base58.decode(address).toString('hex').substring(0, 2);
58
+ networkKey = findKey(BitcoinNetworks, (network) => {
59
+ const pubKeyHashPrefix = padHexStart(network.pubKeyHash.toString(16), 1);
60
+ const scriptHashPrefix = padHexStart(network.scriptHash.toString(16), 1);
61
+ return [pubKeyHashPrefix, scriptHashPrefix].includes(prefix);
62
+ });
63
+ }
64
+ return (BitcoinNetworks as { [key: string]: BitcoinNetwork })[networkKey];
65
+ }
66
+
67
+ type CoinSelectTarget = {
68
+ value: number;
69
+ script?: Buffer;
70
+ id?: string;
71
+ };
72
+
73
+ type CoinSelectResponse = {
74
+ inputs: bT.UTXO[];
75
+ outputs: CoinSelectTarget[];
76
+ change: CoinSelectTarget;
77
+ fee: number;
78
+ };
79
+
80
+ type CoinSelectFunction = (
81
+ utxos: bT.UTXO[],
82
+ targets: CoinSelectTarget[],
83
+ feePerByte: number,
84
+ ) => CoinSelectResponse;
85
+
86
+ function selectCoins(
87
+ utxos: bT.UTXO[],
88
+ targets: CoinSelectTarget[],
89
+ feePerByte: number,
90
+ fixedInputs: bT.UTXO[] = [],
91
+ ) {
92
+ let selectUtxos = utxos;
93
+
94
+ // Default coinselect won't accumulate some inputs
95
+ // TODO: does coinselect need to be modified to ABSOLUTELY not skip an input?
96
+ const coinselectStrat: CoinSelectFunction = fixedInputs.length
97
+ ? coinselectAccumulative
98
+ : coinselect;
99
+ if (fixedInputs.length) {
100
+ selectUtxos = [
101
+ // Order fixed inputs to the start of the list so they are used
102
+ ...fixedInputs,
103
+ ...utxos.filter(
104
+ (utxo) =>
105
+ !fixedInputs.find(
106
+ (input) => input.vout === utxo.vout && input.txid === utxo.txid,
107
+ ),
108
+ ),
109
+ ];
110
+ }
111
+
112
+ const { inputs, outputs, fee } = coinselectStrat(
113
+ selectUtxos,
114
+ targets,
115
+ Math.ceil(feePerByte),
116
+ );
117
+
118
+ let change;
119
+ if (inputs && outputs) {
120
+ change = outputs.find((output) => output.id !== 'main');
121
+ }
122
+
123
+ return { inputs, outputs, fee, change };
124
+ }
125
+
126
+ const OUTPUT_TYPES_MAP = {
127
+ [classify.types.P2WPKH]: 'witness_v0_keyhash',
128
+ [classify.types.P2WSH]: 'witness_v0_scripthash',
129
+ };
130
+
131
+ function decodeRawTransaction(
132
+ hex: string,
133
+ network: BitcoinNetwork,
134
+ ): bT.Transaction {
135
+ const bjsTx = bitcoin.Transaction.fromHex(hex);
136
+
137
+ const vin = bjsTx.ins.map((input) => {
138
+ return <bT.Input>{
139
+ txid: Buffer.from(input.hash).reverse().toString('hex'),
140
+ vout: input.index,
141
+ scriptSig: {
142
+ asm: bitcoin.script.toASM(input.script),
143
+ hex: input.script.toString('hex'),
144
+ },
145
+ txinwitness: input.witness.map((w) => w.toString('hex')),
146
+ sequence: input.sequence,
147
+ };
148
+ });
149
+
150
+ const vout = bjsTx.outs.map((output, n) => {
151
+ const type = classify.output(output.script);
152
+
153
+ const vout: bT.Output = {
154
+ value: output.value / 1e8,
155
+ n,
156
+ scriptPubKey: {
157
+ asm: bitcoin.script.toASM(output.script),
158
+ hex: output.script.toString('hex'),
159
+ reqSigs: 1, // TODO: not sure how to derive this
160
+ type: OUTPUT_TYPES_MAP[type] || type,
161
+ addresses: [],
162
+ },
163
+ };
164
+
165
+ try {
166
+ const address = bitcoin.address.fromOutputScript(output.script, network);
167
+ vout.scriptPubKey.addresses.push(address);
168
+ } catch (e) {
169
+ /** If output script is not parasable, we just skip it */
170
+ }
171
+
172
+ return vout;
173
+ });
174
+
175
+ return {
176
+ txid: bjsTx.getHash(false).reverse().toString('hex'),
177
+ hash: bjsTx.getHash(true).reverse().toString('hex'),
178
+ version: bjsTx.version,
179
+ locktime: bjsTx.locktime,
180
+ size: bjsTx.byteLength(),
181
+ vsize: bjsTx.virtualSize(),
182
+ weight: bjsTx.weight(),
183
+ vin,
184
+ vout,
185
+ hex,
186
+ };
187
+ }
188
+
189
+ function normalizeTransactionObject(
190
+ tx: bT.Transaction,
191
+ fee: number,
192
+ block?: { number: number; hash: string },
193
+ ): Transaction<bT.Transaction> {
194
+ const value = tx.vout.reduce(
195
+ (p, n) => p.plus(new BigNumber(n.value).times(1e8)),
196
+ new BigNumber(0),
197
+ );
198
+ const result = {
199
+ hash: tx.txid,
200
+ value: value.toNumber(),
201
+ _raw: tx,
202
+ confirmations: 0,
203
+ status: tx.confirmations > 0 ? TxStatus.Success : TxStatus.Pending,
204
+ };
205
+
206
+ if (fee) {
207
+ const feePrice = Math.round(fee / tx.vsize);
208
+ Object.assign(result, {
209
+ fee,
210
+ feePrice,
211
+ });
212
+ }
213
+
214
+ if (block) {
215
+ Object.assign(result, {
216
+ blockHash: block.hash,
217
+ blockNumber: block.number,
218
+ confirmations: tx.confirmations,
219
+ });
220
+ }
221
+
222
+ return result;
223
+ }
224
+
225
+ // TODO: This is copy pasta because it's not exported from bitcoinjs-lib
226
+ // https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.spec.ts#L477
227
+ function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
228
+ let buffer = Buffer.allocUnsafe(0);
229
+
230
+ function writeSlice(slice: Buffer): void {
231
+ buffer = Buffer.concat([buffer, Buffer.from(slice)]);
232
+ }
233
+
234
+ function writeVarInt(i: number): void {
235
+ const currentLen = buffer.length;
236
+ const varintLen = varuint.encodingLength(i);
237
+
238
+ buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
239
+ varuint.encode(i, buffer, currentLen);
240
+ }
241
+
242
+ function writeVarSlice(slice: Buffer): void {
243
+ writeVarInt(slice.length);
244
+ writeSlice(slice);
245
+ }
246
+
247
+ function writeVector(vector: Buffer[]): void {
248
+ writeVarInt(vector.length);
249
+ vector.forEach(writeVarSlice);
250
+ }
251
+
252
+ writeVector(witness);
253
+
254
+ return buffer;
255
+ }
256
+
257
+ function getPubKeyHash(address: string, network: BitcoinNetwork) {
258
+ const outputScript = bitcoin.address.toOutputScript(address, network);
259
+ const type = classify.output(outputScript);
260
+ if (![classify.types.P2PKH, classify.types.P2WPKH].includes(type)) {
261
+ throw new Error(
262
+ `Bitcoin swap doesn't support the address ${address} type of ${type}. Not possible to derive public key hash.`,
263
+ );
264
+ }
265
+
266
+ try {
267
+ const bech32 = bitcoin.address.fromBech32(address);
268
+ return bech32.data;
269
+ } catch (e) {
270
+ const base58 = bitcoin.address.fromBase58Check(address);
271
+ return base58.hash;
272
+ }
273
+ }
274
+
275
+ function validateAddress(_address: Address | string, network: BitcoinNetwork) {
276
+ const address = addressToString(_address);
277
+
278
+ if (typeof address !== 'string') {
279
+ throw new InvalidAddressError(`Invalid address: ${address}`);
280
+ }
281
+
282
+ let pubKeyHash;
283
+ try {
284
+ pubKeyHash = getPubKeyHash(address, network);
285
+ } catch (e) {
286
+ throw new InvalidAddressError(
287
+ `Invalid Address. Failed to parse: ${address}`,
288
+ );
289
+ }
290
+
291
+ if (!pubKeyHash) {
292
+ throw new InvalidAddressError(`Invalid Address: ${address}`);
293
+ }
294
+ }
295
+
296
+ export {
297
+ calculateFee,
298
+ compressPubKey,
299
+ getAddressNetwork,
300
+ CoinSelectTarget,
301
+ selectCoins,
302
+ decodeRawTransaction,
303
+ normalizeTransactionObject,
304
+ witnessStackToScriptWitness,
305
+ AddressTypes,
306
+ getPubKeyHash,
307
+ validateAddress,
308
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@atomicfinance/bitcoin-utils",
3
+ "version": "3.0.0",
4
+ "description": "",
5
+ "module": "dist/index.js",
6
+ "main": "dist/index.js",
7
+ "directories": {
8
+ "dist": "dist",
9
+ "src": "lib"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "lib"
14
+ ],
15
+ "scripts": {
16
+ "build": "../../node_modules/.bin/tsc --project tsconfig.json",
17
+ "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .",
18
+ "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .",
19
+ "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --recursive \"tests/**/*.test.*\""
20
+ },
21
+ "author": "Atomic Finance <info@atomic.finance>",
22
+ "license": "MIT",
23
+ "engines": {
24
+ "node": ">=14"
25
+ },
26
+ "dependencies": {
27
+ "@atomicfinance/crypto": "^3.0.0",
28
+ "@atomicfinance/errors": "^3.0.0",
29
+ "@atomicfinance/types": "^3.0.0",
30
+ "@atomicfinance/utils": "^3.0.0",
31
+ "@babel/runtime": "^7.12.1",
32
+ "bignumber.js": "^9.0.0",
33
+ "bip174": "^2.0.1",
34
+ "bitcoin-networks": "^1.0.0",
35
+ "bitcoinjs-lib": "^5.2.0",
36
+ "coinselect": "^3.1.11",
37
+ "lodash": "^4.17.20"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "devDependencies": {
43
+ "@types/bitcoinjs-lib": "^5.0.0",
44
+ "@types/lodash": "^4.14.168",
45
+ "@types/node": "16.10.3"
46
+ },
47
+ "sideEffects": false
48
+ }