blockbook-client
Version:
Client for interacting with Trezor's blockbook API
868 lines (858 loc) • 33.9 kB
JavaScript
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