UNPKG

blockbook-client

Version:

Client for interacting with Trezor's blockbook API

868 lines (858 loc) 33.9 kB
import { type, number, intersection, array, string, boolean, keyof, partial, literal, union, any } from 'io-ts'; import { requiredOptionalCodec, nullable, Logger, extendCodec, optional, isString, assertType, DelegateLogger, isUndefined } from '@bitaccess/ts-common'; import WebSocket from 'ws'; import axios from 'axios'; import qs from 'qs'; const Paginated = type({ page: number, totalPages: number, itemsOnPage: number, }, 'Paginated'); function paginated(c) { return intersection([Paginated, c]); } const BlockbookConfig = requiredOptionalCodec({ nodes: array(string), }, { logger: nullable(Logger), disableTypeValidation: boolean, requestTimeoutMs: number, reconnectDelayMs: number, }, 'BlockbookConfig'); const BlockbookInfo = type({ coin: string, host: string, version: string, gitCommit: string, buildTime: string, syncMode: boolean, initialSync: boolean, inSync: boolean, bestHeight: number, lastBlockTime: string, inSyncMempool: boolean, lastMempoolTime: string, mempoolSize: number, decimals: number, dbSize: number, about: string, }, 'BlockbookInfo'); const BackendInfo = requiredOptionalCodec({ chain: string, blocks: number, bestBlockHash: string, difficulty: string, version: string, }, { protocolVersion: string, subversion: string, sizeOnDisk: number, headers: number, timeOffset: number, warnings: string, }, 'BackendInfo'); const SystemInfo = type({ blockbook: BlockbookInfo, backend: BackendInfo, }, 'ApiStatus'); const SystemInfoWs = type({ name: string, shortcut: string, decimals: number, version: string, bestHeight: number, bestHash: string, block0Hash: string, testnet: boolean, }, 'SystemInfoWs'); const NormalizedTxCommonVin = requiredOptionalCodec({ n: number, }, { txid: string, vout: number, sequence: number, addresses: array(string), value: string, hex: string, asm: string, coinbase: string, isAddress: boolean, }, 'NormalizedTxCommonVin'); const NormalizedTxCommonVout = requiredOptionalCodec({ n: number, addresses: nullable(array(string)), }, { value: string, spent: boolean, spentTxId: string, spentIndex: number, spentHeight: number, hex: string, asm: string, type: string, isAddress: boolean, }, 'NormalizedTxCommonVout'); const EthereumSpecific = type({ status: number, nonce: number, gasLimit: number, gasUsed: number, gasPrice: string, }, 'EthereumSpecific'); const TokenTransfer = type({ type: string, from: string, to: string, token: string, name: string, symbol: string, decimals: number, value: string, }, 'TokenTransfer'); const NormalizedTxCommon = requiredOptionalCodec({ txid: string, vin: array(NormalizedTxCommonVin), vout: array(NormalizedTxCommonVout), blockHeight: number, confirmations: number, blockTime: number, value: string, }, { version: number, lockTime: number, blockHash: string, size: number, vsize: number, valueIn: string, fees: string, hex: string, tokenTransfers: array(TokenTransfer), ethereumSpecific: EthereumSpecific, }, 'NormalizedTxCommon'); const BlockHashResponse = type({ blockHash: string, }, 'BlockHashResponse'); const BlockHashResponseWs = type({ hash: string, }, 'BlockHashResponseWs'); const SubscribeNewBlockEvent = type({ height: number, hash: string, }, 'SubscribeNewBlockEvent'); const SubscribeAddressesEvent = type({ address: string, tx: NormalizedTxCommon, }, 'SubscribeAddressesEvent'); const GetAddressDetailsLevels = keyof({ basic: null, tokens: null, tokenBalances: null, txids: null, txs: null, }); const GetAddressDetailsOptions = partial({ page: number, pageSize: number, from: number, to: number, details: GetAddressDetailsLevels, }); const TokenDetailsTypeERC20 = literal('ERC20'); const TokenDetailsTypeXpubAddress = literal('XPUBAddress'); const TokenDetailsType = union([ TokenDetailsTypeERC20, TokenDetailsTypeXpubAddress, ], 'TokenDetailsType'); const TokenDetailsCommon = requiredOptionalCodec({ type: TokenDetailsType, name: string, transfers: number, }, { path: string, contract: string, symbol: string, decimals: number, balance: string, totalReceived: string, totalSent: string, }, 'TokenDetailsCommon'); const TokenDetailsCommonBalance = extendCodec(TokenDetailsCommon, { balance: string, }, 'TokenDetailsCommonBalance'); const AddressDetailsCommonBasic = requiredOptionalCodec({ address: string, balance: string, unconfirmedBalance: string, unconfirmedTxs: number, txs: number, }, { totalReceived: string, totalSent: string, nonTokenTxs: number, nonce: string, usedTokens: number, erc20Contract: any, }, 'AddressDetailsCommonBasic'); const AddressDetailsCommonTokens = extendCodec(AddressDetailsCommonBasic, { tokens: array(TokenDetailsCommon), }, 'AddressDetailsCommonTokens'); const AddressDetailsCommonTokenBalances = extendCodec(AddressDetailsCommonBasic, {}, { tokens: array(TokenDetailsCommonBalance), }, 'AddressDetailsCommonTokenBalances'); const AddressDetailsCommonTxids = paginated(extendCodec(AddressDetailsCommonTokenBalances, {}, { txids: array(string), }, 'AddressDetailsCommonTxids')); const AddressDetailsCommonTxs = paginated(extendCodec(AddressDetailsCommonTokenBalances, {}, { txs: array(NormalizedTxCommon), }, 'AddressDetailsCommonTxs')); const GetUtxosOptions = partial({ confirmed: boolean, }, 'GetUtxosOptions'); const UtxoDetails = requiredOptionalCodec({ txid: string, vout: number, value: string, confirmations: number, }, { height: number, coinbase: boolean, lockTime: number, }, 'UtxoDetails'); const UtxoDetailsXpub = extendCodec(UtxoDetails, {}, { address: string, path: string, }, 'UtxoDetailsXpub'); const GetBlockOptions = partial({ page: number, }, 'GetBlockOptions'); const BlockInfoCommon = paginated(requiredOptionalCodec({ hash: string, height: number, confirmations: number, size: number, version: number, merkleRoot: string, nonce: string, bits: string, difficulty: string, txCount: number, }, { previousBlockHash: string, nextBlockHash: string, time: number, txs: array(NormalizedTxCommon), }, 'BlockInfoCommon')); const SendTxSuccess = type({ result: string, }, 'SendTransactionSuccess'); const SendTxError = type({ error: type({ message: string, }) }, 'SendTxFailed'); const EstimateFeeResponse = type({ result: string, }, 'EstimateFeeResponse'); const NormalizedTxBitcoinVinWithoutCoinbase = extendCodec(NormalizedTxCommonVin, { value: string, }, 'NormalizedTxBitcoinVinWithoutCoinbase'); const NormalizedTxBitcoinVinWithCoinbase = extendCodec(NormalizedTxCommonVin, { coinbase: string, }, 'NormalizedTxBitcoinVinWithCoinbase'); const NormalizedTxBitcoinVin = union([NormalizedTxBitcoinVinWithoutCoinbase, NormalizedTxBitcoinVinWithCoinbase], 'NormalizedTxBitcoinVin'); const NormalizedTxBitcoinVout = extendCodec(NormalizedTxCommonVout, { value: string, }, 'NormalizedTxBitcoinVout'); const NormalizedTxBitcoin = extendCodec(NormalizedTxCommon, { vin: array(NormalizedTxBitcoinVin), vout: array(NormalizedTxBitcoinVout), valueIn: string, fees: string, }, 'NormalizedTxBitcoin'); const SpecificTxBitcoinVinScriptSig = type({ asm: string, hex: string, }, 'SpecificTxBitcoinVinScriptSig'); const SpecificTxBitcoinVin = type({ txid: string, vout: number, scriptSig: SpecificTxBitcoinVinScriptSig, sequence: number, }, 'SpecificTxBitcoinVin'); const SpecificTxBitcoinVoutScriptPubKey = requiredOptionalCodec({ asm: string, hex: string, type: string, }, { reqSigs: number, addresses: array(string), address: string, }, 'SpecificTxBitcoinVoutScriptPubKey'); const SpecificTxBitcoinVout = type({ value: number, n: number, scriptPubKey: SpecificTxBitcoinVoutScriptPubKey, }, 'SpecificTxBitcoinVout'); const SpecificTxBitcoin = requiredOptionalCodec({ txid: string, hash: string, version: number, size: number, locktime: number, vin: array(SpecificTxBitcoinVin), vout: array(SpecificTxBitcoinVout), hex: string, }, { vsize: number, weight: number, blockhash: string, confirmations: number, time: number, blocktime: number, }, 'SpecificTxBitcoin'); const AddressDetailsBitcoinBasic = extendCodec(AddressDetailsCommonBasic, { totalReceived: string, totalSent: string, }, 'AddressDetailsBitcoinBasic'); const AddressDetailsBitcoinTokens = AddressDetailsBitcoinBasic; const AddressDetailsBitcoinTokenBalances = AddressDetailsBitcoinBasic; const AddressDetailsBitcoinTxids = paginated(extendCodec(AddressDetailsBitcoinTokenBalances, {}, { txids: array(string), }, 'AddressDetailsBitcoinTxids')); const AddressDetailsBitcoinTxs = paginated(extendCodec(AddressDetailsBitcoinTokenBalances, {}, { transactions: array(NormalizedTxBitcoin), }, 'AddressDetailsBitcoinTxs')); const GetXpubDetailsTokensOption = keyof({ nonzero: null, used: null, derived: null, }, 'GetXpubDetailsTokensOption'); const GetXpubDetailsOptions = extendCodec(GetAddressDetailsOptions, {}, { usedTokens: number, tokens: GetXpubDetailsTokensOption, }, 'GetXpubDetailsOptions'); const TokenDetailsXpubAddress = type({ type: TokenDetailsTypeXpubAddress, name: string, path: string, transfers: number, decimals: number, }, 'TokenDetailsXpubAddress'); const TokenDetailsXpubAddressBalance = extendCodec(TokenDetailsXpubAddress, {}, { balance: string, totalReceived: string, totalSent: string, }, 'TokenDetailsXpubAddressBalance'); const XpubDetailsBasic = AddressDetailsBitcoinBasic; const XpubDetailsTokens = extendCodec(XpubDetailsBasic, {}, { tokens: array(TokenDetailsXpubAddress), }, 'XpubDetailsTokens'); const XpubDetailsTokenBalances = extendCodec(XpubDetailsBasic, {}, { tokens: array(TokenDetailsXpubAddressBalance), }, 'XpubDetailsTokenBalances'); const XpubDetailsTxids = paginated(extendCodec(XpubDetailsTokenBalances, {}, { txids: array(string), }, 'XpubDetailsTxids')); const XpubDetailsTxs = paginated(extendCodec(XpubDetailsTokenBalances, {}, { transactions: array(NormalizedTxBitcoin), }, 'XpubDetailsTxs')); const BlockInfoBitcoin = extendCodec(BlockInfoCommon, {}, { txs: array(NormalizedTxBitcoin), }, 'BlockInfoBitcoin'); const NormalizedTxEthereumVin = extendCodec(NormalizedTxCommonVin, { addresses: array(string), }, 'NormalizedTxEthereumVin'); const NormalizedTxEthereumVout = extendCodec(NormalizedTxCommonVout, { value: string, }, 'NormalizedTxEthereumVout'); const NormalizedTxEthereum = extendCodec(NormalizedTxCommon, { vin: array(NormalizedTxEthereumVin), vout: array(NormalizedTxEthereumVout), fees: string, ethereumSpecific: EthereumSpecific, }, 'NormalizedTxEthereum'); const SpecificTxEthereumTx = type({ nonce: string, gasPrice: string, gas: string, to: string, value: string, input: string, hash: string, blockNumber: string, blockHash: string, from: string, transactionIndex: string, }, 'SpecificTxEthereumTx'); const SpecificTxEthereumReceipt = type({ gasUsed: string, status: string, logs: array(any), }, 'SpecificTxEthereumReceipt'); const SpecificTxEthereum = type({ tx: SpecificTxEthereumTx, receipt: SpecificTxEthereumReceipt, }, 'SpecificTxEthereum'); const TokenDetailsERC20 = type({ type: TokenDetailsTypeERC20, name: string, contract: string, transfers: number, symbol: optional(string), }, 'TokenDetailsERC20'); const TokenDetailsERC20Balance = extendCodec(TokenDetailsERC20, { balance: optional(string), }, 'TokenDetailsERC20Balance'); const AddressDetailsEthereumBasic = extendCodec(AddressDetailsCommonBasic, { nonTokenTxs: optional(number), nonce: string, }, 'AddressDetailsEthereumBasic'); const AddressDetailsEthereumTokens = extendCodec(AddressDetailsEthereumBasic, {}, { tokens: array(TokenDetailsERC20), }, 'AddressDetailsEthereumTokens'); const AddressDetailsEthereumTokenBalances = extendCodec(AddressDetailsEthereumBasic, {}, { tokens: array(TokenDetailsERC20Balance), }, 'AddressDetailsEthereumTokenBalances'); const AddressDetailsEthereumTxids = paginated(extendCodec(AddressDetailsEthereumTokenBalances, {}, { txids: array(string), }, 'AddressDetailsEthereumTxids')); const AddressDetailsEthereumTxs = paginated(extendCodec(AddressDetailsEthereumTokenBalances, {}, { transactions: array(NormalizedTxEthereum), }, 'AddressDetailsEthereumTxs')); const BlockInfoEthereum = extendCodec(BlockInfoCommon, {}, { txs: 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 (isString(body === null || body === void 0 ? void 0 : body.error)) { throw new Error(body.error); } else if (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 = 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 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 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 (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 (!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 (!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(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(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, 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'); } } export { AddressDetailsBitcoinBasic, AddressDetailsBitcoinTokenBalances, AddressDetailsBitcoinTokens, AddressDetailsBitcoinTxids, AddressDetailsBitcoinTxs, AddressDetailsCommonBasic, AddressDetailsCommonTokenBalances, AddressDetailsCommonTokens, AddressDetailsCommonTxids, AddressDetailsCommonTxs, AddressDetailsEthereumBasic, AddressDetailsEthereumTokenBalances, AddressDetailsEthereumTokens, AddressDetailsEthereumTxids, AddressDetailsEthereumTxs, BackendInfo, BaseBlockbook, BlockHashResponse, BlockHashResponseWs, BlockInfoBitcoin, BlockInfoCommon, BlockInfoEthereum, Blockbook, BlockbookBitcoin, BlockbookConfig, BlockbookEthereum, BlockbookInfo, EstimateFeeResponse, EthereumSpecific, GetAddressDetailsLevels, GetAddressDetailsOptions, GetBlockOptions, GetUtxosOptions, GetXpubDetailsOptions, GetXpubDetailsTokensOption, NormalizedTxBitcoin, NormalizedTxBitcoinVin, NormalizedTxBitcoinVinWithCoinbase, NormalizedTxBitcoinVinWithoutCoinbase, NormalizedTxBitcoinVout, NormalizedTxCommon, NormalizedTxCommonVin, NormalizedTxCommonVout, NormalizedTxEthereum, NormalizedTxEthereumVin, NormalizedTxEthereumVout, Paginated, SendTxError, SendTxSuccess, SpecificTxBitcoin, SpecificTxBitcoinVin, SpecificTxBitcoinVinScriptSig, SpecificTxBitcoinVout, SpecificTxBitcoinVoutScriptPubKey, SpecificTxEthereum, SpecificTxEthereumReceipt, SpecificTxEthereumTx, SubscribeAddressesEvent, SubscribeNewBlockEvent, SystemInfo, SystemInfoWs, TokenDetailsCommon, TokenDetailsCommonBalance, TokenDetailsERC20, TokenDetailsERC20Balance, TokenDetailsType, TokenDetailsTypeERC20, TokenDetailsTypeXpubAddress, TokenDetailsXpubAddress, TokenDetailsXpubAddressBalance, TokenTransfer, UtxoDetails, UtxoDetailsXpub, XpubDetailsBasic, XpubDetailsTokenBalances, XpubDetailsTokens, XpubDetailsTxids, XpubDetailsTxs, paginated }; //# sourceMappingURL=index.es.js.map