UNPKG

blockbook-client

Version:

Client for interacting with Trezor's blockbook API

952 lines (939 loc) 37.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var t = require('io-ts'); var tsCommon = require('@bitaccess/ts-common'); var WebSocket = _interopDefault(require('ws')); var axios = _interopDefault(require('axios')); var qs = _interopDefault(require('qs')); const Paginated = t.type({ page: t.number, totalPages: t.number, itemsOnPage: t.number, }, 'Paginated'); function paginated(c) { return t.intersection([Paginated, c]); } const BlockbookConfig = tsCommon.requiredOptionalCodec({ nodes: t.array(t.string), }, { logger: tsCommon.nullable(tsCommon.Logger), disableTypeValidation: t.boolean, requestTimeoutMs: t.number, reconnectDelayMs: t.number, }, 'BlockbookConfig'); const BlockbookInfo = t.type({ coin: t.string, host: t.string, version: t.string, gitCommit: t.string, buildTime: t.string, syncMode: t.boolean, initialSync: t.boolean, inSync: t.boolean, bestHeight: t.number, lastBlockTime: t.string, inSyncMempool: t.boolean, lastMempoolTime: t.string, mempoolSize: t.number, decimals: t.number, dbSize: t.number, about: t.string, }, 'BlockbookInfo'); const BackendInfo = tsCommon.requiredOptionalCodec({ chain: t.string, blocks: t.number, bestBlockHash: t.string, difficulty: t.string, version: t.string, }, { protocolVersion: t.string, subversion: t.string, sizeOnDisk: t.number, headers: t.number, timeOffset: t.number, warnings: t.string, }, 'BackendInfo'); const SystemInfo = t.type({ blockbook: BlockbookInfo, backend: BackendInfo, }, 'ApiStatus'); const SystemInfoWs = t.type({ name: t.string, shortcut: t.string, decimals: t.number, version: t.string, bestHeight: t.number, bestHash: t.string, block0Hash: t.string, testnet: t.boolean, }, 'SystemInfoWs'); const NormalizedTxCommonVin = tsCommon.requiredOptionalCodec({ n: t.number, }, { txid: t.string, vout: t.number, sequence: t.number, addresses: t.array(t.string), value: t.string, hex: t.string, asm: t.string, coinbase: t.string, isAddress: t.boolean, }, 'NormalizedTxCommonVin'); const NormalizedTxCommonVout = tsCommon.requiredOptionalCodec({ n: t.number, addresses: tsCommon.nullable(t.array(t.string)), }, { value: t.string, spent: t.boolean, spentTxId: t.string, spentIndex: t.number, spentHeight: t.number, hex: t.string, asm: t.string, type: t.string, isAddress: t.boolean, }, 'NormalizedTxCommonVout'); const EthereumSpecific = t.type({ status: t.number, nonce: t.number, gasLimit: t.number, gasUsed: t.number, gasPrice: t.string, }, 'EthereumSpecific'); const TokenTransfer = t.type({ type: t.string, from: t.string, to: t.string, token: t.string, name: t.string, symbol: t.string, decimals: t.number, value: t.string, }, 'TokenTransfer'); const NormalizedTxCommon = tsCommon.requiredOptionalCodec({ txid: t.string, vin: t.array(NormalizedTxCommonVin), vout: t.array(NormalizedTxCommonVout), blockHeight: t.number, confirmations: t.number, blockTime: t.number, value: t.string, }, { version: t.number, lockTime: t.number, blockHash: t.string, size: t.number, vsize: t.number, valueIn: t.string, fees: t.string, hex: t.string, tokenTransfers: t.array(TokenTransfer), ethereumSpecific: EthereumSpecific, }, 'NormalizedTxCommon'); const BlockHashResponse = t.type({ blockHash: t.string, }, 'BlockHashResponse'); const BlockHashResponseWs = t.type({ hash: t.string, }, 'BlockHashResponseWs'); const SubscribeNewBlockEvent = t.type({ height: t.number, hash: t.string, }, 'SubscribeNewBlockEvent'); const SubscribeAddressesEvent = t.type({ address: t.string, tx: NormalizedTxCommon, }, 'SubscribeAddressesEvent'); const GetAddressDetailsLevels = t.keyof({ basic: null, tokens: null, tokenBalances: null, txids: null, txs: null, }); const GetAddressDetailsOptions = t.partial({ page: t.number, pageSize: t.number, from: t.number, to: t.number, details: GetAddressDetailsLevels, }); const TokenDetailsTypeERC20 = t.literal('ERC20'); const TokenDetailsTypeXpubAddress = t.literal('XPUBAddress'); const TokenDetailsType = t.union([ TokenDetailsTypeERC20, TokenDetailsTypeXpubAddress, ], 'TokenDetailsType'); const TokenDetailsCommon = tsCommon.requiredOptionalCodec({ type: TokenDetailsType, name: t.string, transfers: t.number, }, { path: t.string, contract: t.string, symbol: t.string, decimals: t.number, balance: t.string, totalReceived: t.string, totalSent: t.string, }, 'TokenDetailsCommon'); const TokenDetailsCommonBalance = tsCommon.extendCodec(TokenDetailsCommon, { balance: t.string, }, 'TokenDetailsCommonBalance'); const AddressDetailsCommonBasic = tsCommon.requiredOptionalCodec({ address: t.string, balance: t.string, unconfirmedBalance: t.string, unconfirmedTxs: t.number, txs: t.number, }, { totalReceived: t.string, totalSent: t.string, nonTokenTxs: t.number, nonce: t.string, usedTokens: t.number, erc20Contract: t.any, }, 'AddressDetailsCommonBasic'); const AddressDetailsCommonTokens = tsCommon.extendCodec(AddressDetailsCommonBasic, { tokens: t.array(TokenDetailsCommon), }, 'AddressDetailsCommonTokens'); const AddressDetailsCommonTokenBalances = tsCommon.extendCodec(AddressDetailsCommonBasic, {}, { tokens: t.array(TokenDetailsCommonBalance), }, 'AddressDetailsCommonTokenBalances'); const AddressDetailsCommonTxids = paginated(tsCommon.extendCodec(AddressDetailsCommonTokenBalances, {}, { txids: t.array(t.string), }, 'AddressDetailsCommonTxids')); const AddressDetailsCommonTxs = paginated(tsCommon.extendCodec(AddressDetailsCommonTokenBalances, {}, { txs: t.array(NormalizedTxCommon), }, 'AddressDetailsCommonTxs')); const GetUtxosOptions = t.partial({ confirmed: t.boolean, }, 'GetUtxosOptions'); const UtxoDetails = tsCommon.requiredOptionalCodec({ txid: t.string, vout: t.number, value: t.string, confirmations: t.number, }, { height: t.number, coinbase: t.boolean, lockTime: t.number, }, 'UtxoDetails'); const UtxoDetailsXpub = tsCommon.extendCodec(UtxoDetails, {}, { address: t.string, path: t.string, }, 'UtxoDetailsXpub'); const GetBlockOptions = t.partial({ page: t.number, }, 'GetBlockOptions'); const BlockInfoCommon = paginated(tsCommon.requiredOptionalCodec({ hash: t.string, height: t.number, confirmations: t.number, size: t.number, version: t.number, merkleRoot: t.string, nonce: t.string, bits: t.string, difficulty: t.string, txCount: t.number, }, { previousBlockHash: t.string, nextBlockHash: t.string, time: t.number, txs: t.array(NormalizedTxCommon), }, 'BlockInfoCommon')); const SendTxSuccess = t.type({ result: t.string, }, 'SendTransactionSuccess'); const SendTxError = t.type({ error: t.type({ message: t.string, }) }, 'SendTxFailed'); const EstimateFeeResponse = t.type({ result: t.string, }, 'EstimateFeeResponse'); const NormalizedTxBitcoinVinWithoutCoinbase = tsCommon.extendCodec(NormalizedTxCommonVin, { value: t.string, }, 'NormalizedTxBitcoinVinWithoutCoinbase'); const NormalizedTxBitcoinVinWithCoinbase = tsCommon.extendCodec(NormalizedTxCommonVin, { coinbase: t.string, }, 'NormalizedTxBitcoinVinWithCoinbase'); const NormalizedTxBitcoinVin = t.union([NormalizedTxBitcoinVinWithoutCoinbase, NormalizedTxBitcoinVinWithCoinbase], 'NormalizedTxBitcoinVin'); const NormalizedTxBitcoinVout = tsCommon.extendCodec(NormalizedTxCommonVout, { value: t.string, }, 'NormalizedTxBitcoinVout'); const NormalizedTxBitcoin = tsCommon.extendCodec(NormalizedTxCommon, { vin: t.array(NormalizedTxBitcoinVin), vout: t.array(NormalizedTxBitcoinVout), valueIn: t.string, fees: t.string, }, 'NormalizedTxBitcoin'); const SpecificTxBitcoinVinScriptSig = t.type({ asm: t.string, hex: t.string, }, 'SpecificTxBitcoinVinScriptSig'); const SpecificTxBitcoinVin = t.type({ txid: t.string, vout: t.number, scriptSig: SpecificTxBitcoinVinScriptSig, sequence: t.number, }, 'SpecificTxBitcoinVin'); const SpecificTxBitcoinVoutScriptPubKey = tsCommon.requiredOptionalCodec({ asm: t.string, hex: t.string, type: t.string, }, { reqSigs: t.number, addresses: t.array(t.string), address: t.string, }, 'SpecificTxBitcoinVoutScriptPubKey'); const SpecificTxBitcoinVout = t.type({ value: t.number, n: t.number, scriptPubKey: SpecificTxBitcoinVoutScriptPubKey, }, 'SpecificTxBitcoinVout'); const SpecificTxBitcoin = tsCommon.requiredOptionalCodec({ txid: t.string, hash: t.string, version: t.number, size: t.number, locktime: t.number, vin: t.array(SpecificTxBitcoinVin), vout: t.array(SpecificTxBitcoinVout), hex: t.string, }, { vsize: t.number, weight: t.number, blockhash: t.string, confirmations: t.number, time: t.number, blocktime: t.number, }, 'SpecificTxBitcoin'); const AddressDetailsBitcoinBasic = tsCommon.extendCodec(AddressDetailsCommonBasic, { totalReceived: t.string, totalSent: t.string, }, 'AddressDetailsBitcoinBasic'); const AddressDetailsBitcoinTokens = AddressDetailsBitcoinBasic; const AddressDetailsBitcoinTokenBalances = AddressDetailsBitcoinBasic; const AddressDetailsBitcoinTxids = paginated(tsCommon.extendCodec(AddressDetailsBitcoinTokenBalances, {}, { txids: t.array(t.string), }, 'AddressDetailsBitcoinTxids')); const AddressDetailsBitcoinTxs = paginated(tsCommon.extendCodec(AddressDetailsBitcoinTokenBalances, {}, { transactions: t.array(NormalizedTxBitcoin), }, 'AddressDetailsBitcoinTxs')); const GetXpubDetailsTokensOption = t.keyof({ nonzero: null, used: null, derived: null, }, 'GetXpubDetailsTokensOption'); const GetXpubDetailsOptions = tsCommon.extendCodec(GetAddressDetailsOptions, {}, { usedTokens: t.number, tokens: GetXpubDetailsTokensOption, }, 'GetXpubDetailsOptions'); const TokenDetailsXpubAddress = t.type({ type: TokenDetailsTypeXpubAddress, name: t.string, path: t.string, transfers: t.number, decimals: t.number, }, 'TokenDetailsXpubAddress'); const TokenDetailsXpubAddressBalance = tsCommon.extendCodec(TokenDetailsXpubAddress, {}, { balance: t.string, totalReceived: t.string, totalSent: t.string, }, 'TokenDetailsXpubAddressBalance'); const XpubDetailsBasic = AddressDetailsBitcoinBasic; const XpubDetailsTokens = tsCommon.extendCodec(XpubDetailsBasic, {}, { tokens: t.array(TokenDetailsXpubAddress), }, 'XpubDetailsTokens'); const XpubDetailsTokenBalances = tsCommon.extendCodec(XpubDetailsBasic, {}, { tokens: t.array(TokenDetailsXpubAddressBalance), }, 'XpubDetailsTokenBalances'); const XpubDetailsTxids = paginated(tsCommon.extendCodec(XpubDetailsTokenBalances, {}, { txids: t.array(t.string), }, 'XpubDetailsTxids')); const XpubDetailsTxs = paginated(tsCommon.extendCodec(XpubDetailsTokenBalances, {}, { transactions: t.array(NormalizedTxBitcoin), }, 'XpubDetailsTxs')); const BlockInfoBitcoin = tsCommon.extendCodec(BlockInfoCommon, {}, { txs: t.array(NormalizedTxBitcoin), }, 'BlockInfoBitcoin'); const NormalizedTxEthereumVin = tsCommon.extendCodec(NormalizedTxCommonVin, { addresses: t.array(t.string), }, 'NormalizedTxEthereumVin'); const NormalizedTxEthereumVout = tsCommon.extendCodec(NormalizedTxCommonVout, { value: t.string, }, 'NormalizedTxEthereumVout'); const NormalizedTxEthereum = tsCommon.extendCodec(NormalizedTxCommon, { vin: t.array(NormalizedTxEthereumVin), vout: t.array(NormalizedTxEthereumVout), fees: t.string, ethereumSpecific: EthereumSpecific, }, 'NormalizedTxEthereum'); const SpecificTxEthereumTx = t.type({ nonce: t.string, gasPrice: t.string, gas: t.string, to: t.string, value: t.string, input: t.string, hash: t.string, blockNumber: t.string, blockHash: t.string, from: t.string, transactionIndex: t.string, }, 'SpecificTxEthereumTx'); const SpecificTxEthereumReceipt = t.type({ gasUsed: t.string, status: t.string, logs: t.array(t.any), }, 'SpecificTxEthereumReceipt'); const SpecificTxEthereum = t.type({ tx: SpecificTxEthereumTx, receipt: SpecificTxEthereumReceipt, }, 'SpecificTxEthereum'); const TokenDetailsERC20 = t.type({ type: TokenDetailsTypeERC20, name: t.string, contract: t.string, transfers: t.number, symbol: tsCommon.optional(t.string), }, 'TokenDetailsERC20'); const TokenDetailsERC20Balance = tsCommon.extendCodec(TokenDetailsERC20, { balance: tsCommon.optional(t.string), }, 'TokenDetailsERC20Balance'); const AddressDetailsEthereumBasic = tsCommon.extendCodec(AddressDetailsCommonBasic, { nonTokenTxs: tsCommon.optional(t.number), nonce: t.string, }, 'AddressDetailsEthereumBasic'); const AddressDetailsEthereumTokens = tsCommon.extendCodec(AddressDetailsEthereumBasic, {}, { tokens: t.array(TokenDetailsERC20), }, 'AddressDetailsEthereumTokens'); const AddressDetailsEthereumTokenBalances = tsCommon.extendCodec(AddressDetailsEthereumBasic, {}, { tokens: t.array(TokenDetailsERC20Balance), }, 'AddressDetailsEthereumTokenBalances'); const AddressDetailsEthereumTxids = paginated(tsCommon.extendCodec(AddressDetailsEthereumTokenBalances, {}, { txids: t.array(t.string), }, 'AddressDetailsEthereumTxids')); const AddressDetailsEthereumTxs = paginated(tsCommon.extendCodec(AddressDetailsEthereumTokenBalances, {}, { transactions: t.array(NormalizedTxEthereum), }, 'AddressDetailsEthereumTxs')); const BlockInfoEthereum = tsCommon.extendCodec(BlockInfoCommon, {}, { txs: t.array(NormalizedTxEthereum), }, 'BlockInfoEthereum'); const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'; async function jsonRequest(host, method, path, params, body, options) { var _a, _b, _c; if (!host.startsWith('http')) { host = `https://${host}`; } const queryString = params ? qs.stringify(params, { addQueryPrefix: true }) : ''; const uri = `${host}${path}${queryString}`; const fullOptions = { url: uri, method, data: body, responseType: 'json', ...options, headers: { 'user-agent': USER_AGENT, }, }; try { let { data } = await axios.request(fullOptions); if ((_a = data === null || data === void 0 ? void 0 : data.error) === null || _a === void 0 ? void 0 : _a.message) { throw new Error(data.error.message); } return data; } catch (e) { if (axios.isAxiosError(e)) { const body = (_b = e.response) === null || _b === void 0 ? void 0 : _b.data; if (tsCommon.isString(body === null || body === void 0 ? void 0 : body.error)) { throw new Error(body.error); } else if (tsCommon.isString((_c = body === null || body === void 0 ? void 0 : body.error) === null || _c === void 0 ? void 0 : _c.message)) { throw new Error(body.error.message); } if (e.code === '522') { e.message = `StatusCodeError: 522 Origin Connection Time-out ${method} ${uri}`; } else if (e.code === '504') { e.message = `StatusCodeError: 504 Gateway Time-out ${method} ${uri}`; } } throw e; } } const xpubDetailsCodecs = { basic: XpubDetailsBasic, tokens: XpubDetailsTokens, tokenBalances: XpubDetailsTokenBalances, txids: XpubDetailsTxids, txs: XpubDetailsTxs, }; class BaseBlockbook { constructor(config, normalizedTxCodec, specificTxCodec, blockInfoCodec, addressDetailsCodecs) { var _a, _b, _c; this.normalizedTxCodec = normalizedTxCodec; this.specificTxCodec = specificTxCodec; this.blockInfoCodec = blockInfoCodec; this.addressDetailsCodecs = addressDetailsCodecs; this.wsConnected = false; this.requestCounter = 0; this.pendingWsRequests = {}; this.subscriptionIdToData = {}; this.subscribtionMethodToId = {}; config = tsCommon.assertType(BlockbookConfig, config); if (config.nodes.length === 0) { throw new Error('Blockbook node list must not be empty'); } this.nodes = config.nodes.map(node => node.trim().replace(/\/$/, '')); this.disableTypeValidation = config.disableTypeValidation || false; this.requestTimeoutMs = config.requestTimeoutMs || 5000; this.reconnectDelayMs = config.reconnectDelayMs || 2000; this.logger = new tsCommon.DelegateLogger((_a = config.logger) !== null && _a !== void 0 ? _a : null, 'blockbook-client'); this.debug = (_c = (_b = process.env.DEBUG) === null || _b === void 0 ? void 0 : _b.includes('blockbook-client')) !== null && _c !== void 0 ? _c : false; } doAssertType(codec, value, ...rest) { if (this.disableTypeValidation) { return value; } return tsCommon.assertType(codec, value, ...rest); } pickNode() { return this.nodes[this.requestCounter++ % this.nodes.length]; } async httpRequest(method, path, params, body, options) { const response = jsonRequest(this.pickNode(), method, path, params, body, { timeout: this.requestTimeoutMs, ...options, }); if (this.debug) { this.logger.debug(`http result ${method} ${path}`, response); } return response; } wsRequest(method, params, idOption) { const id = idOption !== null && idOption !== void 0 ? idOption : (this.requestCounter++).toString(); const req = { id, method, params, }; return new Promise((resolve, reject) => { setTimeout(() => { var _a; if (((_a = this.pendingWsRequests[id]) === null || _a === void 0 ? void 0 : _a.reject) === reject) { delete this.pendingWsRequests[id]; reject(new Error(`Timeout waiting for websocket ${method} response (id: ${id})`)); } }, this.requestTimeoutMs); this.pendingWsRequests[id] = { resolve, reject }; this.ws.send(JSON.stringify(req)); }); } async subscribe(method, params, callback) { const id = (this.requestCounter++).toString(); this.subscriptionIdToData[id] = { callback, method, params }; let result; try { result = await this.wsRequest(method, params, id); } catch (e) { delete this.subscriptionIdToData[id]; throw e; } const oldSubscriptionId = this.subscribtionMethodToId[method]; if (oldSubscriptionId) { delete this.subscriptionIdToData[oldSubscriptionId]; } this.subscribtionMethodToId[method] = id; return result; } async unsubscribe(method) { const subscriptionId = this.subscribtionMethodToId[method]; if (tsCommon.isUndefined(subscriptionId)) { return { subscribed: false }; } delete this.subscribtionMethodToId[method]; delete this.subscriptionIdToData[subscriptionId]; return this.wsRequest(`un${method}`, {}, subscriptionId); } reconnect(baseDelay, existingSubscriptions) { const reconnectMs = Math.round(baseDelay * (1 + Math.random())); this.logger.log(`socket reconnecting in ${reconnectMs / 1000}s to one of`, this.nodes); setTimeout(async () => { try { await this.connect(); for (let subscription of existingSubscriptions) { await this.subscribe(subscription.method, subscription.params, subscription.callback); } } catch (e) { this.reconnect(Math.max(60 * 1000, baseDelay * 2), existingSubscriptions); } }, reconnectMs); } rejectAllPendingRequests(reason) { for (let pendingRequestId of Object.keys(this.pendingWsRequests)) { const { reject } = this.pendingWsRequests[pendingRequestId]; delete this.pendingWsRequests[pendingRequestId]; reject(new Error(reason)); } } async connect() { if (this.wsPendingConnectPromise) { await this.wsPendingConnectPromise; } if (this.wsConnectedNode) { return this.wsConnectedNode; } this.pendingWsRequests = {}; this.subscriptionIdToData = {}; this.subscribtionMethodToId = {}; let node = this.pickNode(); if (node.startsWith('http')) { node = node.replace('http', 'ws'); } if (!node.startsWith('ws')) { node = `wss://${node}`; } if (!node.endsWith('/websocket')) { node += '/websocket'; } this.wsPendingConnectPromise = new Promise((resolve, reject) => { this.ws = new WebSocket(node, { headers: { 'user-agent': USER_AGENT } }); this.ws.once('open', () => { this.logger.log(`socket connected to ${node}`); this.wsConnected = true; this.wsConnectedNode = node; resolve(); }); this.ws.once('error', e => { this.logger.warn(`socket error connecting to ${node}`, e); this.ws.terminate(); reject(e); }); }); try { await this.wsPendingConnectPromise; } finally { delete this.wsPendingConnectPromise; } this.ws.on('close', code => { this.logger.warn(`socket connection to ${node} closed with code: ${code}`); this.wsConnected = false; this.wsConnectedNode = undefined; clearInterval(this.pingIntervalId); this.rejectAllPendingRequests('socket closed while waiting for response'); if (!BaseBlockbook.WS_NORMAL_CLOSURE_CODES.includes(code) && this.reconnectDelayMs > 0) { this.reconnect(this.reconnectDelayMs, Object.values(this.subscriptionIdToData)); } }); this.ws.on('error', e => { this.logger.warn(`socket error for ${node}`, e); }); this.ws.on('message', data => { var _a; if (this.debug) { this.logger.debug(`socket message from ${node}`, data); } if (!tsCommon.isString(data)) { this.logger.error(`Unrecognized websocket data type ${typeof data} received from ${node}`); return; } let response; try { response = JSON.parse(data); } catch (e) { this.logger.error(`Failed to parse websocket data received from ${node}`, e.toString()); return; } const id = response.id; if (!tsCommon.isString(id)) { this.logger.error(`Received websocket data without a valid ID from ${node}`, response); } const result = response.data; let errorMessage = ''; if (result === null || result === void 0 ? void 0 : result.error) { errorMessage = (_a = result.error.message) !== null && _a !== void 0 ? _a : data; } const pendingRequest = this.pendingWsRequests[id]; if (pendingRequest) { delete this.pendingWsRequests[id]; if (errorMessage) { return pendingRequest.reject(new Error(errorMessage)); } return pendingRequest.resolve(result); } const activeSubscription = this.subscriptionIdToData[id]; if (activeSubscription) { if (errorMessage) { this.logger.error(`Received error response for ${activeSubscription.method} subscription from ${node}`, errorMessage); } const maybePromise = activeSubscription.callback(result); if (maybePromise) { maybePromise === null || maybePromise === void 0 ? void 0 : maybePromise.catch(e => this.logger.error(`Error handling ${activeSubscription.method} subscription data (id: ${id})`, result, e)); } return; } this.logger.warn(`Unrecognized websocket data (id: ${id}) received from ${node}`, result); }); this.pingIntervalId = setInterval(async () => { try { await this.wsRequest('ping', {}); } catch (e) { this.ws.terminate(); } }, 25000); return node; } async disconnect() { if (!this.wsConnected) { return; } return new Promise((resolve, reject) => { this.ws.once('close', () => resolve()); this.ws.once('error', e => reject(e)); this.ws.close(); }); } assertWsConnected(msg) { if (!this.wsConnected) { throw new Error(`Websocket must be connected to ${msg !== null && msg !== void 0 ? msg : ''}`); } } async getInfo() { if (!this.wsConnected) { throw new Error('Websocket must be connected to call getInfo'); } const response = await this.wsRequest('getInfo'); return this.doAssertType(SystemInfoWs, response); } async getStatus() { const response = await this.httpRequest('GET', '/api/v2'); return this.doAssertType(SystemInfo, response); } async getBestBlock() { if (this.wsConnected) { const info = await this.getInfo(); return { height: info.bestHeight, hash: info.bestHash }; } const status = await this.getStatus(); return { height: status.blockbook.bestHeight, hash: status.backend.bestBlockHash }; } async getBlockHash(blockNumber) { if (this.wsConnected) { const response = await this.wsRequest('getBlockHash', { height: blockNumber }); const { hash } = this.doAssertType(BlockHashResponseWs, response); return hash; } const response = await this.httpRequest('GET', `/api/v2/block-index/${blockNumber}`); const { blockHash } = this.doAssertType(BlockHashResponse, response); return blockHash; } async getTx(txid) { const response = this.wsConnected ? await this.wsRequest('getTransaction', { txid }) : await this.httpRequest('GET', `/api/v2/tx/${txid}`); return this.doAssertType(this.normalizedTxCodec, response); } async getTxSpecific(txid) { const response = this.wsConnected ? await this.wsRequest('getTransactionSpecific', { txid }) : await this.httpRequest('GET', `/api/v2/tx-specific/${txid}`); return this.doAssertType(this.specificTxCodec, response); } async getAddressDetails(address, options = {}) { const detailsLevel = options.details || 'txids'; const response = this.wsConnected ? await this.wsRequest('getAccountInfo', { descriptor: address, ...options, details: detailsLevel }) : await this.httpRequest('GET', `/api/v2/address/${address}`, { ...options, details: detailsLevel }); const codec = this.addressDetailsCodecs[detailsLevel]; return this.doAssertType(codec, response); } async getXpubDetails(xpub, options = {}) { const tokens = options.tokens || 'derived'; const detailsLevel = options.details || 'txids'; const response = this.wsConnected ? await this.wsRequest('getAccountInfo', { descriptor: xpub, details: detailsLevel, tokens, ...options }) : await this.httpRequest('GET', `/api/v2/xpub/${xpub}`, { details: detailsLevel, tokens, ...options }); const codec = xpubDetailsCodecs[detailsLevel]; return this.doAssertType(codec, response); } async getUtxosForAddress(address, options = {}) { const response = this.wsConnected ? await this.wsRequest('getAccountUtxo', { descriptor: address, ...options }) : await this.httpRequest('GET', `/api/v2/utxo/${address}`, options); return this.doAssertType(t.array(UtxoDetails), response); } async getUtxosForXpub(xpub, options = {}) { const response = this.wsConnected ? await this.wsRequest('getAccountUtxo', { descriptor: xpub, ...options }) : await this.httpRequest('GET', `/api/v2/utxo/${xpub}`, options); return this.doAssertType(t.array(UtxoDetailsXpub), response); } async getBlock(block, options = {}) { const response = await this.httpRequest('GET', `/api/v2/block/${block}`, options); return this.doAssertType(this.blockInfoCodec, response); } async sendTx(txHex) { const response = this.wsConnected ? await this.wsRequest('sendTransaction', { hex: txHex }) : await this.httpRequest('POST', '/api/v2/sendtx/', undefined, txHex); const { result: txHash } = this.doAssertType(SendTxSuccess, response); return txHash; } async estimateFee(blockTarget) { const response = await this.httpRequest('GET', `/api/v2/estimatefee/${blockTarget}`); const { result: fee } = this.doAssertType(EstimateFeeResponse, response); return fee; } async subscribeAddresses(addresses, cb) { this.assertWsConnected('call subscribeAddresses'); return this.subscribe('subscribeAddresses', { addresses }, cb); } async unsubscribeAddresses() { this.assertWsConnected('call unsubscribeAddresses'); return this.unsubscribe('subscribeAddresses'); } async subscribeNewBlock(cb) { this.assertWsConnected('call subscribeNewBlock'); return this.subscribe('subscribeNewBlock', {}, cb); } async unsubscribeNewBlock() { this.assertWsConnected('call unsubscribeNewBlock'); return this.unsubscribe('subscribeNewBlock'); } } BaseBlockbook.WS_NORMAL_CLOSURE_CODES = [1000, 1005]; class Blockbook extends BaseBlockbook { constructor(config) { super(config, NormalizedTxCommon, t.any, BlockInfoCommon, { basic: AddressDetailsCommonBasic, tokens: AddressDetailsCommonTokens, tokenBalances: AddressDetailsCommonTokenBalances, txids: AddressDetailsCommonTxids, txs: AddressDetailsCommonTxs, }); } } class BlockbookBitcoin extends BaseBlockbook { constructor(config) { super(config, NormalizedTxBitcoin, SpecificTxBitcoin, BlockInfoBitcoin, { basic: AddressDetailsBitcoinBasic, tokens: AddressDetailsBitcoinTokens, tokenBalances: AddressDetailsBitcoinTokenBalances, txids: AddressDetailsBitcoinTxids, txs: AddressDetailsBitcoinTxs, }); } } class BlockbookEthereum extends BaseBlockbook { constructor(config) { super(config, NormalizedTxEthereum, SpecificTxEthereum, BlockInfoEthereum, { basic: AddressDetailsEthereumBasic, tokens: AddressDetailsEthereumTokens, tokenBalances: AddressDetailsEthereumTokenBalances, txids: AddressDetailsEthereumTxids, txs: AddressDetailsEthereumTxs, }); } async getXpubDetails() { throw new Error('BlockbookEthereum.getXpubDetails not supported'); } async getUtxosForAddress() { throw new Error('BlockbookEthereum.getUtxosForAddress not supported'); } async getUtxosForXpub() { throw new Error('BlockbookEthereum.getUtxosForXpub not supported'); } } exports.AddressDetailsBitcoinBasic = AddressDetailsBitcoinBasic; exports.AddressDetailsBitcoinTokenBalances = AddressDetailsBitcoinTokenBalances; exports.AddressDetailsBitcoinTokens = AddressDetailsBitcoinTokens; exports.AddressDetailsBitcoinTxids = AddressDetailsBitcoinTxids; exports.AddressDetailsBitcoinTxs = AddressDetailsBitcoinTxs; exports.AddressDetailsCommonBasic = AddressDetailsCommonBasic; exports.AddressDetailsCommonTokenBalances = AddressDetailsCommonTokenBalances; exports.AddressDetailsCommonTokens = AddressDetailsCommonTokens; exports.AddressDetailsCommonTxids = AddressDetailsCommonTxids; exports.AddressDetailsCommonTxs = AddressDetailsCommonTxs; exports.AddressDetailsEthereumBasic = AddressDetailsEthereumBasic; exports.AddressDetailsEthereumTokenBalances = AddressDetailsEthereumTokenBalances; exports.AddressDetailsEthereumTokens = AddressDetailsEthereumTokens; exports.AddressDetailsEthereumTxids = AddressDetailsEthereumTxids; exports.AddressDetailsEthereumTxs = AddressDetailsEthereumTxs; exports.BackendInfo = BackendInfo; exports.BaseBlockbook = BaseBlockbook; exports.BlockHashResponse = BlockHashResponse; exports.BlockHashResponseWs = BlockHashResponseWs; exports.BlockInfoBitcoin = BlockInfoBitcoin; exports.BlockInfoCommon = BlockInfoCommon; exports.BlockInfoEthereum = BlockInfoEthereum; exports.Blockbook = Blockbook; exports.BlockbookBitcoin = BlockbookBitcoin; exports.BlockbookConfig = BlockbookConfig; exports.BlockbookEthereum = BlockbookEthereum; exports.BlockbookInfo = BlockbookInfo; exports.EstimateFeeResponse = EstimateFeeResponse; exports.EthereumSpecific = EthereumSpecific; exports.GetAddressDetailsLevels = GetAddressDetailsLevels; exports.GetAddressDetailsOptions = GetAddressDetailsOptions; exports.GetBlockOptions = GetBlockOptions; exports.GetUtxosOptions = GetUtxosOptions; exports.GetXpubDetailsOptions = GetXpubDetailsOptions; exports.GetXpubDetailsTokensOption = GetXpubDetailsTokensOption; exports.NormalizedTxBitcoin = NormalizedTxBitcoin; exports.NormalizedTxBitcoinVin = NormalizedTxBitcoinVin; exports.NormalizedTxBitcoinVinWithCoinbase = NormalizedTxBitcoinVinWithCoinbase; exports.NormalizedTxBitcoinVinWithoutCoinbase = NormalizedTxBitcoinVinWithoutCoinbase; exports.NormalizedTxBitcoinVout = NormalizedTxBitcoinVout; exports.NormalizedTxCommon = NormalizedTxCommon; exports.NormalizedTxCommonVin = NormalizedTxCommonVin; exports.NormalizedTxCommonVout = NormalizedTxCommonVout; exports.NormalizedTxEthereum = NormalizedTxEthereum; exports.NormalizedTxEthereumVin = NormalizedTxEthereumVin; exports.NormalizedTxEthereumVout = NormalizedTxEthereumVout; exports.Paginated = Paginated; exports.SendTxError = SendTxError; exports.SendTxSuccess = SendTxSuccess; exports.SpecificTxBitcoin = SpecificTxBitcoin; exports.SpecificTxBitcoinVin = SpecificTxBitcoinVin; exports.SpecificTxBitcoinVinScriptSig = SpecificTxBitcoinVinScriptSig; exports.SpecificTxBitcoinVout = SpecificTxBitcoinVout; exports.SpecificTxBitcoinVoutScriptPubKey = SpecificTxBitcoinVoutScriptPubKey; exports.SpecificTxEthereum = SpecificTxEthereum; exports.SpecificTxEthereumReceipt = SpecificTxEthereumReceipt; exports.SpecificTxEthereumTx = SpecificTxEthereumTx; exports.SubscribeAddressesEvent = SubscribeAddressesEvent; exports.SubscribeNewBlockEvent = SubscribeNewBlockEvent; exports.SystemInfo = SystemInfo; exports.SystemInfoWs = SystemInfoWs; exports.TokenDetailsCommon = TokenDetailsCommon; exports.TokenDetailsCommonBalance = TokenDetailsCommonBalance; exports.TokenDetailsERC20 = TokenDetailsERC20; exports.TokenDetailsERC20Balance = TokenDetailsERC20Balance; exports.TokenDetailsType = TokenDetailsType; exports.TokenDetailsTypeERC20 = TokenDetailsTypeERC20; exports.TokenDetailsTypeXpubAddress = TokenDetailsTypeXpubAddress; exports.TokenDetailsXpubAddress = TokenDetailsXpubAddress; exports.TokenDetailsXpubAddressBalance = TokenDetailsXpubAddressBalance; exports.TokenTransfer = TokenTransfer; exports.UtxoDetails = UtxoDetails; exports.UtxoDetailsXpub = UtxoDetailsXpub; exports.XpubDetailsBasic = XpubDetailsBasic; exports.XpubDetailsTokenBalances = XpubDetailsTokenBalances; exports.XpubDetailsTokens = XpubDetailsTokens; exports.XpubDetailsTxids = XpubDetailsTxids; exports.XpubDetailsTxs = XpubDetailsTxs; exports.paginated = paginated; //# sourceMappingURL=index.cjs.js.map