@samouraiwallet/electrum-client
Version:
Electrum protocol client for Node.js
265 lines • 10.5 kB
JavaScript
import { Client } from "./lib/client.js";
export class ElectrumClient extends Client {
/**
* Constructs an instance of ElectrumClient.
*
* @param {number} port - The port number to connect to.
* @param {string} host - The host address to connect to.
* @param {Protocol} protocol - The protocol to use for the connection.
* @param {Callbacks} [callbacks] - Optional callbacks for connection events.
*/
constructor(port, host, protocol, callbacks) {
super(port, host, protocol, callbacks);
this.onConnectCallback = callbacks?.onConnect ?? null;
this.onCloseCallback = callbacks?.onClose ?? null;
this.onLogCallback = callbacks?.onLog
? callbacks.onLog
: (str) => {
console.log(str);
};
this.timeLastCall = 0;
this.persistencePolicy = {
retryPeriod: 10000,
maxRetry: 1000,
pingPeriod: 120000,
callback: null,
};
this.electrumConfig = null;
this.pingInterval = null;
this.versionInfo = ["", ""];
}
/**
* Creates an instance of ElectrumClient and initializes it with the provided configuration.
*
* @param {CreateClientParams} params - The parameters required to create and initialize the client.
* @param {number} params.port - The port number to connect to.
* @param {string} params.host - The host address to connect to.
* @param {Protocol} params.protocol - The protocol to use for the connection.
* @param {Callbacks} [params.callbacks] - Optional callbacks for connection events.
* @param {ElectrumConfig} params.electrumConfig - The Electrum configuration to use.
* @param {PersistencePolicy} [params.persistencePolicy] - Optional persistence policy for the client.
*
* @returns {Promise<ElectrumClient>} A promise that resolves to an initialized ElectrumClient instance.
*/
static createClient(params) {
const client = new ElectrumClient(params.port, params.host, params.protocol, params.callbacks);
return client.initElectrum(params.electrumConfig, params.persistencePolicy);
}
/**
* Initializes the Electrum client with the provided configuration and persistence policy.
*
* @param {ElectrumConfig} electrumConfig - The Electrum configuration to use.
* @param {PersistencePolicy} [persistencePolicy] - Optional persistence policy for the client.
* @returns {Promise<ElectrumClient>} A promise that resolves to the initialized ElectrumClient instance.
*/
async initElectrum(electrumConfig, persistencePolicy) {
this.persistencePolicy = {
...this.persistencePolicy,
...persistencePolicy,
};
this.electrumConfig = electrumConfig;
this.timeLastCall = 0;
await this.connect();
this.versionInfo = (await this.server_version(electrumConfig.client, electrumConfig.version));
if (this.onConnectCallback != null) {
this.onConnectCallback(this, this.versionInfo);
}
return this;
}
async request(method, params) {
this.timeLastCall = Date.now();
const response = await super.request(method, params);
this.keepAlive();
return response;
}
async requestBatch(method, params, secondParam) {
this.timeLastCall = Date.now();
const response = await super.requestBatch(method, params, secondParam);
this.keepAlive();
return response;
}
onClose() {
super.onClose();
const list = [
"server.peers.subscribe",
"blockchain.numblocks.subscribe",
"blockchain.headers.subscribe",
"blockchain.address.subscribe",
];
for (const event of list)
this.subscribe.removeAllListeners(event);
let retryPeriod = 10000;
if (this.persistencePolicy.retryPeriod > 0) {
retryPeriod = this.persistencePolicy.retryPeriod;
}
if (this.onCloseCallback != null) {
this.onCloseCallback(this);
}
setTimeout(() => {
if (this.persistencePolicy.maxRetry > 0) {
this.reconnect().catch((error) => {
this.onError(error);
});
this.persistencePolicy.maxRetry -= 1;
}
else if (this.persistencePolicy.callback != null) {
this.persistencePolicy.callback();
}
}, retryPeriod);
}
keepAlive() {
if (this.pingInterval != null) {
clearInterval(this.pingInterval);
}
let pingPeriod = 120000;
if (this.persistencePolicy.pingPeriod > 0) {
pingPeriod = this.persistencePolicy.pingPeriod;
}
this.pingInterval = setInterval(() => {
if (this.timeLastCall !== 0 &&
Date.now() > this.timeLastCall + pingPeriod) {
this.server_ping().catch((error) => {
this.log(`Keep-Alive ping failed: ${error}`);
});
}
}, pingPeriod);
}
/**
* Closes the Electrum client connection and stops the keep-alive ping interval.
*
* @returns {void}
*/
close() {
super.close();
if (this.pingInterval != null) {
clearInterval(this.pingInterval);
}
this.reconnect =
this.reconnect =
this.onClose =
this.keepAlive =
() => Promise.resolve(this); // dirty hack to make it stop reconnecting
}
reconnect() {
this.log("Electrum attempting reconnect...");
this.initSocket();
return this.persistencePolicy == null
? // biome-ignore lint/style/noNonNullAssertion:
this.initElectrum(this.electrumConfig)
: // biome-ignore lint/style/noNonNullAssertion:
this.initElectrum(this.electrumConfig, this.persistencePolicy);
}
log(str) {
this.onLogCallback(str);
}
// ElectrumX API
server_version(client_name, protocol_version) {
return this.request("server.version", [client_name, protocol_version]);
}
server_banner() {
return this.request("server.banner", []);
}
server_features() {
return this.request("server.features", []);
}
server_ping() {
return this.request("server.ping", []);
}
server_addPeer(features) {
return this.request("server.add_peer", [features]);
}
serverDonation_address() {
return this.request("server.donation_address", []);
}
serverPeers_subscribe() {
return this.request("server.peers.subscribe", []);
}
blockchainAddress_getProof(address) {
return this.request("blockchain.address.get_proof", [address]);
}
blockchainScripthash_getBalance(scripthash) {
return this.request("blockchain.scripthash.get_balance", [scripthash]);
}
blockchainScripthash_getBalanceBatch(scripthashes) {
return this.requestBatch("blockchain.scripthash.get_balance", scripthashes);
}
blockchainScripthash_listunspentBatch(scripthashes) {
return this.requestBatch("blockchain.scripthash.listunspent", scripthashes);
}
blockchainScripthash_getHistory(scripthash) {
return this.request("blockchain.scripthash.get_history", [scripthash]);
}
blockchainScripthash_getHistoryBatch(scripthashes) {
return this.requestBatch("blockchain.scripthash.get_history", scripthashes);
}
blockchainScripthash_getMempool(scripthash) {
return this.request("blockchain.scripthash.get_mempool", [scripthash]);
}
blockchainScripthash_listunspent(scripthash) {
return this.request("blockchain.scripthash.listunspent", [scripthash]);
}
blockchainScripthash_subscribe(scripthash) {
return this.request("blockchain.scripthash.subscribe", [scripthash]);
}
blockchainBlock_getHeader(height) {
return this.request("blockchain.block.get_header", [height]);
}
blockchainBlock_headers(start_height, count) {
return this.request("blockchain.block.headers", [start_height, count]);
}
blockchainEstimatefee(number) {
return this.request("blockchain.estimatefee", [number]);
}
blockchainHeaders_subscribe() {
return this.request("blockchain.headers.subscribe", []);
}
blockchain_relayfee() {
return this.request("blockchain.relayfee", []);
}
blockchainTransaction_broadcast(rawtx) {
return this.request("blockchain.transaction.broadcast", [rawtx]);
}
blockchainTransaction_get(tx_hash, verbose = false) {
return this.request("blockchain.transaction.get", [tx_hash, verbose]);
}
blockchainTransaction_getBatch(tx_hashes, verbose = false) {
return this.requestBatch("blockchain.transaction.get", tx_hashes, verbose);
}
blockchainTransaction_getMerkle(tx_hash, height) {
return this.request("blockchain.transaction.get_merkle", [tx_hash, height]);
}
mempool_getFeeHistogram() {
return this.request("mempool.get_fee_histogram", []);
}
// ---------------------------------
// protocol 1.1 deprecated method
// ---------------------------------
blockchainUtxo_getAddress(tx_hash, index) {
return this.request("blockchain.utxo.get_address", [tx_hash, index]);
}
blockchainNumblocks_subscribe() {
return this.request("blockchain.numblocks.subscribe", []);
}
// ---------------------------------
// protocol 1.2 deprecated method
// ---------------------------------
blockchainBlock_getChunk(index) {
return this.request("blockchain.block.get_chunk", [index]);
}
blockchainAddress_getBalance(address) {
return this.request("blockchain.address.get_balance", [address]);
}
blockchainAddress_getHistory(address) {
return this.request("blockchain.address.get_history", [address]);
}
blockchainAddress_getMempool(address) {
return this.request("blockchain.address.get_mempool", [address]);
}
blockchainAddress_listunspent(address) {
return this.request("blockchain.address.listunspent", [address]);
}
blockchainAddress_subscribe(address) {
return this.request("blockchain.address.subscribe", [address]);
}
}
//# sourceMappingURL=index.js.map