UNPKG

@atomiqlabs/sdk-lib

Version:

Basic SDK functionality library for atomiq

276 lines (275 loc) 9.52 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MempoolApi = void 0; const buffer_1 = require("buffer"); const Utils_1 = require("../../utils/Utils"); const RequestError_1 = require("../../errors/RequestError"); class MempoolApi { /** * Returns api url that should be operational * * @private */ getOperationalApi() { return this.backends.find(e => e.operational === true); } /** * Returns api urls that are maybe operational, in case none is considered operational returns all of the price * apis such that they can be tested again whether they are operational * * @private */ getMaybeOperationalApis() { let operational = this.backends.filter(e => e.operational === true || e.operational === null); if (operational.length === 0) { this.backends.forEach(e => e.operational = null); operational = this.backends; } return operational; } /** * Sends a GET or POST request to the mempool api, handling the non-200 responses as errors & throwing * * @param url * @param path * @param responseType * @param type * @param body */ async _request(url, path, responseType, type = "GET", body) { const response = await (0, Utils_1.fetchWithTimeout)(url + path, { method: type, timeout: this.timeout, body: typeof (body) === "string" ? body : JSON.stringify(body) }); if (response.status !== 200) { let resp; try { resp = await response.text(); } catch (e) { throw new RequestError_1.RequestError(response.statusText, response.status); } throw RequestError_1.RequestError.parse(resp, response.status); } if (responseType === "str") return await response.text(); return await response.json(); } /** * Sends request in parallel to multiple maybe operational api urls * * @param path * @param responseType * @param type * @param body * @private */ async requestFromMaybeOperationalUrls(path, responseType, type = "GET", body) { try { return await (0, Utils_1.promiseAny)(this.getMaybeOperationalApis().map(obj => (async () => { try { const result = await this._request(obj.url, path, responseType, type, body); obj.operational = true; return result; } catch (e) { //Only mark as non operational on 5xx server errors! if (e instanceof RequestError_1.RequestError && Math.floor(e.httpCode / 100) !== 5) { obj.operational = true; throw e; } else { obj.operational = false; throw e; } } })())); } catch (e) { throw e.find(err => err instanceof RequestError_1.RequestError && Math.floor(err.httpCode / 100) !== 5) || e[0]; } } /** * Sends a request to mempool API, first tries to use the operational API (if any) and if that fails it falls back * to using maybe operational price APIs * * @param path * @param responseType * @param type * @param body * @private */ async request(path, responseType, type = "GET", body) { return (0, Utils_1.tryWithRetries)(() => { const operationalPriceApi = this.getOperationalApi(); if (operationalPriceApi != null) { return this._request(operationalPriceApi.url, path, responseType, type, body).catch(err => { //Only retry on 5xx server errors! if (err instanceof RequestError_1.RequestError && Math.floor(err.httpCode / 100) !== 5) throw err; operationalPriceApi.operational = false; return this.requestFromMaybeOperationalUrls(path, responseType, type, body); }); } return this.requestFromMaybeOperationalUrls(path, responseType, type, body); }, null, (err) => err instanceof RequestError_1.RequestError && Math.floor(err.httpCode / 100) !== 5); } constructor(url, timeout) { url = url ?? "https://mempool.space/testnet/api/"; if (Array.isArray(url)) { this.backends = url.map(val => { return { url: val, operational: null }; }); } else { this.backends = [ { url: url, operational: null } ]; } this.timeout = timeout; } /** * Returns information about a specific lightning network node as identified by the public key (in hex encoding) * * @param pubkey */ getLNNodeInfo(pubkey) { return this.request("v1/lightning/nodes/" + pubkey, "obj").catch((e) => { if (e.message === "This node does not exist, or our node is not seeing it yet") return null; throw e; }); } /** * Returns on-chain transaction as identified by its txId * * @param txId */ getTransaction(txId) { return this.request("tx/" + txId, "obj").catch((e) => { if (e.message === "Transaction not found") return null; throw e; }); } /** * Returns raw binary encoded bitcoin transaction, also strips the witness data from the transaction * * @param txId */ async getRawTransaction(txId) { const rawTransaction = await this.request("tx/" + txId + "/hex", "str"); return buffer_1.Buffer.from(rawTransaction, "hex"); } /** * Returns confirmed & unconfirmed balance of the specific bitcoin address * * @param address */ async getAddressBalances(address) { const jsonBody = await this.request("address/" + address, "obj"); const confirmedInput = BigInt(jsonBody.chain_stats.funded_txo_sum); const confirmedOutput = BigInt(jsonBody.chain_stats.spent_txo_sum); const unconfirmedInput = BigInt(jsonBody.mempool_stats.funded_txo_sum); const unconfirmedOutput = BigInt(jsonBody.mempool_stats.spent_txo_sum); return { confirmedBalance: confirmedInput - confirmedOutput, unconfirmedBalance: unconfirmedInput - unconfirmedOutput }; } /** * Returns CPFP (children pays for parent) data for a given transaction * * @param txId */ getCPFPData(txId) { return this.request("v1/cpfp/" + txId, "obj"); } /** * Returns UTXOs (unspent transaction outputs) for a given address * * @param address */ async getAddressUTXOs(address) { let jsonBody = await this.request("address/" + address + "/utxo", "obj"); jsonBody.forEach(e => e.value = BigInt(e.value)); return jsonBody; } /** * Returns current on-chain bitcoin fees */ getFees() { return this.request("v1/fees/recommended", "obj"); } /** * Returns all transactions for a given address * * @param address */ getAddressTransactions(address) { return this.request("address/" + address + "/txs", "obj"); } /** * Returns expected pending (mempool) blocks */ getPendingBlocks() { return this.request("v1/fees/mempool-blocks", "obj"); } /** * Returns the blockheight of the current bitcoin blockchain's tip */ async getTipBlockHeight() { const response = await this.request("blocks/tip/height", "str"); return parseInt(response); } /** * Returns the bitcoin blockheader as identified by its blockhash * * @param blockhash */ getBlockHeader(blockhash) { return this.request("block/" + blockhash, "obj"); } /** * Returns the block status * * @param blockhash */ getBlockStatus(blockhash) { return this.request("block/" + blockhash + "/status", "obj"); } /** * Returns the transaction's proof (merkle proof) * * @param txId */ getTransactionProof(txId) { return this.request("tx/" + txId + "/merkle-proof", "obj"); } /** * Returns blockhash of a block at a specific blockheight * * @param height */ getBlockHash(height) { return this.request("block-height/" + height, "str"); } /** * Returns past 15 blockheaders before (and including) the specified height * * @param endHeight */ getPast15BlockHeaders(endHeight) { return this.request("v1/blocks/" + endHeight, "obj"); } /** * Sends raw hex encoded bitcoin transaction * * @param transactionHex */ sendTransaction(transactionHex) { return this.request("tx", "str", "POST", transactionHex); } } exports.MempoolApi = MempoolApi;