@atomiqlabs/btc-mempool 1.0.1

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.
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MempoolApi = void 0;
4
+ const buffer_1 = require("buffer");
5
+ const MempoolApiError_1 = require("../errors/MempoolApiError");
6
+ const base_1 = require("@atomiqlabs/base");
7
+ const MempoolApiEndpoints = {
8
+ [base_1.BitcoinNetwork.MAINNET]: [
9
+ "https://mempool.space/api/",
10
+ "https://mempool.fra.mempool.space/api/",
11
+ "https://mempool.va1.mempool.space/api/",
12
+ "https://mempool.tk7.mempool.space/api/"
13
+ ],
14
+ [base_1.BitcoinNetwork.TESTNET]: [
15
+ "https://mempool.space/testnet/api/",
16
+ "https://mempool.fra.mempool.space/testnet/api/",
17
+ "https://mempool.va1.mempool.space/testnet/api/",
18
+ "https://mempool.tk7.mempool.space/testnet/api/"
19
+ ],
20
+ [base_1.BitcoinNetwork.TESTNET4]: [
21
+ "https://mempool.space/testnet4/api/",
22
+ "https://mempool.fra.mempool.space/testnet4/api/",
23
+ "https://mempool.va1.mempool.space/testnet4/api/",
24
+ "https://mempool.tk7.mempool.space/testnet4/api/"
25
+ ]
26
+ };
27
+ /**
28
+ * Mempool.space REST API client for Bitcoin blockchain data
29
+ *
30
+ * @category Bitcoin
31
+ */
32
+ class MempoolApi {
33
+ /**
34
+ * Returns api url that should be operational
35
+ *
36
+ * @private
37
+ */
38
+ getOperationalApi() {
39
+ return this.backends.find(e => e.operational === true);
40
+ }
41
+ /**
42
+ * Returns api urls that are maybe operational, in case none is considered operational returns all of the price
43
+ * apis such that they can be tested again whether they are operational
44
+ *
45
+ * @private
46
+ */
47
+ getMaybeOperationalApis() {
48
+ let operational = this.backends.filter(e => e.operational === true || e.operational === null);
49
+ if (operational.length === 0) {
50
+ this.backends.forEach(e => e.operational = null);
51
+ operational = this.backends;
52
+ }
53
+ return operational;
54
+ }
55
+ /**
56
+ * Sends a GET or POST request to the mempool api, handling the non-200 responses as errors & throwing
57
+ *
58
+ * @param url
59
+ * @param path
60
+ * @param responseType
61
+ * @param type
62
+ * @param body
63
+ */
64
+ async _request(url, path, responseType, type = "GET", body) {
65
+ const response = await fetch(url + path, {
66
+ method: type,
67
+ signal: AbortSignal.timeout(this.timeout),
68
+ body: typeof (body) === "string" ? body : JSON.stringify(body)
69
+ });
70
+ if (response.status !== 200) {
71
+ let resp;
72
+ try {
73
+ resp = await response.text();
74
+ }
75
+ catch (e) {
76
+ throw new MempoolApiError_1.MempoolApiError(response.statusText, response.status);
77
+ }
78
+ throw new MempoolApiError_1.MempoolApiError(resp, response.status);
79
+ }
80
+ if (responseType === "str")
81
+ return await response.text();
82
+ return await response.json();
83
+ }
84
+ /**
85
+ * Sends request in parallel to multiple maybe operational api urls
86
+ *
87
+ * @param path
88
+ * @param responseType
89
+ * @param type
90
+ * @param body
91
+ * @private
92
+ */
93
+ async requestFromMaybeOperationalUrls(path, responseType, type = "GET", body) {
94
+ try {
95
+ return await Promise.any(this.getMaybeOperationalApis().map(obj => (async () => {
96
+ try {
97
+ const result = await this._request(obj.url, path, responseType, type, body);
98
+ obj.operational = true;
99
+ return result;
100
+ }
101
+ catch (e) {
102
+ //Only mark as non operational on 5xx server errors!
103
+ if (e instanceof MempoolApiError_1.MempoolApiError && Math.floor(e.httpCode / 100) !== 5) {
104
+ obj.operational = true;
105
+ throw e;
106
+ }
107
+ else {
108
+ obj.operational = false;
109
+ throw e;
110
+ }
111
+ }
112
+ })()));
113
+ }
114
+ catch (_e) {
115
+ const e = _e;
116
+ throw e.errors.find(err => err instanceof MempoolApiError_1.MempoolApiError && Math.floor(err.httpCode / 100) !== 5) || e.errors[0];
117
+ }
118
+ }
119
+ /**
120
+ * Sends a request to mempool API, first tries to use the operational API (if any) and if that fails it falls back
121
+ * to using maybe operational price APIs
122
+ *
123
+ * @param path
124
+ * @param responseType
125
+ * @param type
126
+ * @param body
127
+ * @private
128
+ */
129
+ async request(path, responseType, type = "GET", body) {
130
+ return (0, base_1.tryWithRetries)(() => {
131
+ const operationalPriceApi = this.getOperationalApi();
132
+ if (operationalPriceApi != null) {
133
+ return this._request(operationalPriceApi.url, path, responseType, type, body).catch(err => {
134
+ //Only retry on 5xx server errors!
135
+ if (err instanceof MempoolApiError_1.MempoolApiError && Math.floor(err.httpCode / 100) !== 5)
136
+ throw err;
137
+ operationalPriceApi.operational = false;
138
+ return this.requestFromMaybeOperationalUrls(path, responseType, type, body);
139
+ });
140
+ }
141
+ return this.requestFromMaybeOperationalUrls(path, responseType, type, body);
142
+ }, undefined, (err) => err instanceof MempoolApiError_1.MempoolApiError && Math.floor(err.httpCode / 100) !== 5);
143
+ }
144
+ constructor(urlOrNetwork, timeout) {
145
+ if (typeof (urlOrNetwork) === "number") {
146
+ const endpoints = MempoolApiEndpoints[urlOrNetwork];
147
+ if (endpoints == null)
148
+ throw new Error(`No default endpoints found for ${base_1.BitcoinNetwork[urlOrNetwork]} network, please pass the manually as string or string[]`);
149
+ this.backends = endpoints.map(val => ({ url: val, operational: null }));
150
+ }
151
+ else {
152
+ if (Array.isArray(urlOrNetwork)) {
153
+ this.backends = urlOrNetwork.map(val => ({ url: val, operational: null }));
154
+ }
155
+ else {
156
+ this.backends = [{ url: urlOrNetwork, operational: null }];
157
+ }
158
+ }
159
+ this.timeout = timeout ?? 15 * 1000;
160
+ }
161
+ /**
162
+ * Returns information about a specific lightning network node as identified by the public key (in hex encoding)
163
+ *
164
+ * @param pubkey
165
+ */
166
+ getLNNodeInfo(pubkey) {
167
+ //500, 200
168
+ return this.request("v1/lightning/nodes/" + pubkey, "obj").catch((e) => {
169
+ if (e.message === "This node does not exist, or our node is not seeing it yet")
170
+ return null;
171
+ throw e;
172
+ });
173
+ }
174
+ /**
175
+ * Returns on-chain transaction as identified by its txId
176
+ *
177
+ * @param txId
178
+ */
179
+ getTransaction(txId) {
180
+ //404 ("Transaction not found"), 200
181
+ return this.request("tx/" + txId, "obj").catch((e) => {
182
+ if (e.message === "Transaction not found")
183
+ return null;
184
+ throw e;
185
+ });
186
+ }
187
+ /**
188
+ * Returns raw binary encoded bitcoin transaction, also strips the witness data from the transaction
189
+ *
190
+ * @param txId
191
+ */
192
+ async getRawTransaction(txId) {
193
+ //404 ("Transaction not found"), 200
194
+ const rawTransaction = await this.request("tx/" + txId + "/hex", "str").catch((e) => {
195
+ if (e.message === "Transaction not found")
196
+ return null;
197
+ throw e;
198
+ });
199
+ return rawTransaction == null ? null : buffer_1.Buffer.from(rawTransaction, "hex");
200
+ }
201
+ /**
202
+ * Returns confirmed & unconfirmed balance of the specific bitcoin address
203
+ *
204
+ * @param address
205
+ */
206
+ async getAddressBalances(address) {
207
+ //400 ("Invalid Bitcoin address"), 200
208
+ const jsonBody = await this.request("address/" + address, "obj");
209
+ const confirmedInput = BigInt(jsonBody.chain_stats.funded_txo_sum);
210
+ const confirmedOutput = BigInt(jsonBody.chain_stats.spent_txo_sum);
211
+ const unconfirmedInput = BigInt(jsonBody.mempool_stats.funded_txo_sum);
212
+ const unconfirmedOutput = BigInt(jsonBody.mempool_stats.spent_txo_sum);
213
+ return {
214
+ confirmedBalance: confirmedInput - confirmedOutput,
215
+ unconfirmedBalance: unconfirmedInput - unconfirmedOutput
216
+ };
217
+ }
218
+ /**
219
+ * Returns CPFP (children pays for parent) data for a given transaction
220
+ *
221
+ * @param txId
222
+ */
223
+ getCPFPData(txId) {
224
+ //200
225
+ return this.request("v1/cpfp/" + txId, "obj");
226
+ }
227
+ /**
228
+ * Returns UTXOs (unspent transaction outputs) for a given address
229
+ *
230
+ * @param address
231
+ */
232
+ async getAddressUTXOs(address) {
233
+ //400 ("Invalid Bitcoin address"), 200
234
+ let jsonBody = await this.request("address/" + address + "/utxo", "obj");
235
+ jsonBody.forEach(e => e.value = BigInt(e.value));
236
+ return jsonBody;
237
+ }
238
+ /**
239
+ * Returns current on-chain bitcoin fees
240
+ */
241
+ getFees() {
242
+ //200
243
+ return this.request("v1/fees/recommended", "obj");
244
+ }
245
+ /**
246
+ * Returns all transactions for a given address
247
+ *
248
+ * @param address
249
+ */
250
+ getAddressTransactions(address) {
251
+ //400 ("Invalid Bitcoin address"), 200
252
+ return this.request("address/" + address + "/txs", "obj");
253
+ }
254
+ /**
255
+ * Returns expected pending (mempool) blocks
256
+ */
257
+ getPendingBlocks() {
258
+ //200
259
+ return this.request("v1/fees/mempool-blocks", "obj");
260
+ }
261
+ /**
262
+ * Returns the blockheight of the current bitcoin blockchain's tip
263
+ */
264
+ async getTipBlockHeight() {
265
+ //200
266
+ const response = await this.request("blocks/tip/height", "str");
267
+ return parseInt(response);
268
+ }
269
+ /**
270
+ * Returns the bitcoin blockheader as identified by its blockhash
271
+ *
272
+ * @param blockhash
273
+ */
274
+ getBlockHeader(blockhash) {
275
+ //404 ("Block not found"), 200
276
+ return this.request("block/" + blockhash, "obj");
277
+ }
278
+ /**
279
+ * Returns the block status
280
+ *
281
+ * @param blockhash
282
+ */
283
+ getBlockStatus(blockhash) {
284
+ //200
285
+ return this.request("block/" + blockhash + "/status", "obj");
286
+ }
287
+ /**
288
+ * Returns the transaction's proof (merkle proof)
289
+ *
290
+ * @param txId
291
+ */
292
+ getTransactionProof(txId) {
293
+ //404 ("Transaction not found or is unconfirmed"), 200
294
+ return this.request("tx/" + txId + "/merkle-proof", "obj");
295
+ }
296
+ /**
297
+ * Returns the transaction's proof (merkle proof)
298
+ *
299
+ * @param txId
300
+ */
301
+ getOutspends(txId) {
302
+ //404 ("Transaction not found"), 200
303
+ return this.request("tx/" + txId + "/outspends", "obj");
304
+ }
305
+ /**
306
+ * Returns blockhash of a block at a specific blockheight
307
+ *
308
+ * @param height
309
+ */
310
+ getBlockHash(height) {
311
+ //404 ("Block not found"), 200
312
+ return this.request("block-height/" + height, "str");
313
+ }
314
+ /**
315
+ * Returns past 15 blockheaders before (and including) the specified height
316
+ *
317
+ * @param endHeight
318
+ */
319
+ getPast15BlockHeaders(endHeight) {
320
+ //200
321
+ return this.request("v1/blocks/" + endHeight, "obj");
322
+ }
323
+ /**
324
+ * Sends raw hex encoded bitcoin transaction
325
+ *
326
+ * @param transactionHex
327
+ */
328
+ sendTransaction(transactionHex) {
329
+ //400??, 200
330
+ return this.request("tx", "str", "POST", transactionHex);
331
+ }
332
+ }
333
+ exports.MempoolApi = MempoolApi;
@@ -0,0 +1,44 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { BtcBlock } from "@atomiqlabs/base";
4
+ import { Buffer } from "buffer";
5
+ export type MempoolBitcoinBlockType = {
6
+ id: string;
7
+ height: number;
8
+ version: number;
9
+ timestamp: number;
10
+ tx_count: number;
11
+ size: number;
12
+ weight: number;
13
+ merkle_root: string;
14
+ previousblockhash: string;
15
+ mediantime: number;
16
+ nonce: number;
17
+ bits: number;
18
+ difficulty: number;
19
+ };
20
+ export declare class MempoolBitcoinBlock implements BtcBlock {
21
+ id: string;
22
+ height: number;
23
+ version: number;
24
+ timestamp: number;
25
+ tx_count: number;
26
+ size: number;
27
+ weight: number;
28
+ merkle_root: string;
29
+ previousblockhash: string;
30
+ mediantime: number;
31
+ nonce: number;
32
+ bits: number;
33
+ difficulty: number;
34
+ constructor(obj: MempoolBitcoinBlockType);
35
+ getHeight(): number;
36
+ getHash(): string;
37
+ getMerkleRoot(): string;
38
+ getNbits(): number;
39
+ getNonce(): number;
40
+ getPrevBlockhash(): string;
41
+ getTimestamp(): number;
42
+ getVersion(): number;
43
+ getChainWork(): Buffer;
44
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MempoolBitcoinBlock = void 0;
4
+ class MempoolBitcoinBlock {
5
+ constructor(obj) {
6
+ this.id = obj.id;
7
+ this.height = obj.height;
8
+ this.version = obj.version;
9
+ this.timestamp = obj.timestamp;
10
+ this.tx_count = obj.tx_count;
11
+ this.size = obj.size;
12
+ this.weight = obj.weight;
13
+ this.merkle_root = obj.merkle_root;
14
+ this.previousblockhash = obj.previousblockhash;
15
+ this.mediantime = obj.mediantime;
16
+ this.nonce = obj.nonce;
17
+ this.bits = obj.bits;
18
+ this.difficulty = obj.difficulty;
19
+ }
20
+ getHeight() {
21
+ return this.height;
22
+ }
23
+ getHash() {
24
+ return this.id;
25
+ }
26
+ getMerkleRoot() {
27
+ return this.merkle_root;
28
+ }
29
+ getNbits() {
30
+ return this.bits;
31
+ }
32
+ getNonce() {
33
+ return this.nonce;
34
+ }
35
+ getPrevBlockhash() {
36
+ return this.previousblockhash;
37
+ }
38
+ getTimestamp() {
39
+ return this.timestamp;
40
+ }
41
+ getVersion() {
42
+ return this.version;
43
+ }
44
+ getChainWork() {
45
+ throw new Error("Unsupported");
46
+ }
47
+ }
48
+ exports.MempoolBitcoinBlock = MempoolBitcoinBlock;
@@ -0,0 +1,167 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { BitcoinRpcWithAddressIndex, BtcBlockWithTxs, BtcSyncInfo, BtcTx, BtcTxWithBlockheight, LightningNetworkApi, LNNodeLiquidity } from "@atomiqlabs/base";
4
+ import { MempoolBitcoinBlock } from "./MempoolBitcoinBlock";
5
+ import { MempoolApi } from "./MempoolApi";
6
+ import { Buffer } from "buffer";
7
+ /**
8
+ * Bitcoin RPC implementation via Mempool.space API
9
+ *
10
+ * @category Bitcoin
11
+ */
12
+ export declare class MempoolBitcoinRpc implements BitcoinRpcWithAddressIndex<MempoolBitcoinBlock>, LightningNetworkApi {
13
+ api: MempoolApi;
14
+ constructor(urlOrMempoolApi: MempoolApi | string | string[]);
15
+ /**
16
+ * Returns a txo hash for a specific transaction vout
17
+ *
18
+ * @param vout
19
+ * @private
20
+ */
21
+ private static getTxoHash;
22
+ /**
23
+ * Returns delay in milliseconds till an unconfirmed transaction is expected to confirm, returns -1
24
+ * if the transaction won't confirm any time soon
25
+ *
26
+ * @param feeRate
27
+ * @private
28
+ */
29
+ private getTimeTillConfirmation;
30
+ /**
31
+ * @inheritDoc
32
+ */
33
+ getConfirmationDelay(tx: {
34
+ txid: string;
35
+ confirmations?: number;
36
+ }, requiredConfirmations: number): Promise<number | null>;
37
+ /**
38
+ * Converts mempool API's transaction to BtcTx object while fetching the raw tx separately
39
+ * @param tx Transaction to convert
40
+ * @private
41
+ */
42
+ private toBtcTx;
43
+ /**
44
+ * Converts mempool API's transaction to BtcTx object, doesn't populate raw and hex fields
45
+ * @param tx Transaction to convert
46
+ * @private
47
+ */
48
+ private toBtcTxWithoutRawData;
49
+ /**
50
+ * @inheritDoc
51
+ */
52
+ getTipHeight(): Promise<number>;
53
+ /**
54
+ * @inheritDoc
55
+ */
56
+ getBlockHeader(blockhash: string): Promise<MempoolBitcoinBlock>;
57
+ /**
58
+ * @inheritDoc
59
+ */
60
+ getMerkleProof(txId: string, blockhash: string): Promise<{
61
+ reversedTxId: Buffer;
62
+ pos: number;
63
+ merkle: Buffer[];
64
+ blockheight: number;
65
+ }>;
66
+ /**
67
+ * @inheritDoc
68
+ */
69
+ getTransaction(txId: string): Promise<BtcTxWithBlockheight | null>;
70
+ /**
71
+ * @inheritDoc
72
+ */
73
+ isInMainChain(blockhash: string): Promise<boolean>;
74
+ /**
75
+ * @inheritDoc
76
+ */
77
+ getBlockhash(height: number): Promise<string>;
78
+ /**
79
+ * @inheritDoc
80
+ */
81
+ getBlockWithTransactions(blockhash: string): Promise<BtcBlockWithTxs>;
82
+ /**
83
+ * @inheritDoc
84
+ */
85
+ getSyncInfo(): Promise<BtcSyncInfo>;
86
+ /**
87
+ * @private
88
+ */
89
+ getPast15Blocks(height: number): Promise<MempoolBitcoinBlock[]>;
90
+ /**
91
+ * @inheritDoc
92
+ */
93
+ checkAddressTxos(address: string, txoHash: Buffer): Promise<{
94
+ tx: Omit<BtcTxWithBlockheight, "hex" | "raw">;
95
+ vout: number;
96
+ } | null>;
97
+ /**
98
+ * @inheritDoc
99
+ */
100
+ waitForAddressTxo(address: string, txoHash: Buffer, requiredConfirmations: number, stateUpdateCbk: (btcTx?: Omit<BtcTxWithBlockheight, "hex" | "raw">, vout?: number, txEtaMS?: number) => void, abortSignal?: AbortSignal, intervalSeconds?: number): Promise<{
101
+ tx: Omit<BtcTxWithBlockheight, "hex" | "raw">;
102
+ vout: number;
103
+ }>;
104
+ /**
105
+ * @inheritDoc
106
+ */
107
+ waitForTransaction(txId: string, requiredConfirmations: number, stateUpdateCbk: (btcTx?: BtcTxWithBlockheight, txEtaMS?: number) => void, abortSignal?: AbortSignal, intervalSeconds?: number): Promise<BtcTxWithBlockheight>;
108
+ /**
109
+ * @inheritDoc
110
+ */
111
+ getLNNodeLiquidity(pubkey: string): Promise<LNNodeLiquidity | null>;
112
+ /**
113
+ * @inheritDoc
114
+ */
115
+ sendRawTransaction(rawTx: string): Promise<string>;
116
+ /**
117
+ * @inheritDoc
118
+ */
119
+ sendRawPackage(rawTx: string[]): Promise<string[]>;
120
+ /**
121
+ * @inheritDoc
122
+ */
123
+ isSpent(utxo: string, confirmed?: boolean): Promise<boolean>;
124
+ /**
125
+ * @inheritDoc
126
+ */
127
+ parseTransaction(rawTx: string): Promise<BtcTx>;
128
+ /**
129
+ * @inheritDoc
130
+ */
131
+ getEffectiveFeeRate(btcTx: BtcTx): Promise<{
132
+ vsize: number;
133
+ fee: number;
134
+ feeRate: number;
135
+ }>;
136
+ /**
137
+ * @inheritDoc
138
+ */
139
+ getFeeRate(): Promise<number>;
140
+ /**
141
+ * @inheritDoc
142
+ */
143
+ getAddressBalances(address: string): Promise<{
144
+ confirmedBalance: bigint;
145
+ unconfirmedBalance: bigint;
146
+ }>;
147
+ /**
148
+ * @inheritDoc
149
+ */
150
+ getAddressUTXOs(address: string): Promise<{
151
+ txid: string;
152
+ vout: number;
153
+ confirmed: boolean;
154
+ block_height: number;
155
+ block_hash: string;
156
+ block_time: number;
157
+ value: bigint;
158
+ }[]>;
159
+ /**
160
+ * @inheritDoc
161
+ */
162
+ getCPFPData(txId: string): Promise<{
163
+ effectiveFeePerVsize: number;
164
+ adjustedVsize: number;
165
+ } | null>;
166
+ outputScriptToAddress(outputScriptHex: string): Promise<string>;
167
+ }