blockbook-client
Version:
Client for interacting with Trezor's blockbook API
954 lines (941 loc) • 39.9 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('io-ts'), require('@bitaccess/ts-common'), require('ws'), require('axios'), require('qs')) :
typeof define === 'function' && define.amd ? define(['exports', 'io-ts', '@bitaccess/ts-common', 'ws', 'axios', 'qs'], factory) :
(global = global || self, factory(global.blockbookClient = {}, global.t, global.tsCommon, global.WebSocket, global.axios, global.qs));
}(this, (function (exports, t, tsCommon, WebSocket, axios, qs) { 'use strict';
WebSocket = WebSocket && WebSocket.hasOwnProperty('default') ? WebSocket['default'] : WebSocket;
axios = axios && axios.hasOwnProperty('default') ? axios['default'] : axios;
qs = qs && qs.hasOwnProperty('default') ? qs['default'] : 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;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=index.umd.js.map