Skip to content

Commit

Permalink
Merge pull request #326 from bob-collective/refactor/default
Browse files Browse the repository at this point in the history
refactor: remove unnecessary client interfaces
  • Loading branch information
gregdhill authored Aug 28, 2024
2 parents 9c52341 + 2d8f336 commit 858761f
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 270 deletions.
232 changes: 89 additions & 143 deletions sdk/src/esplora.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,61 @@ export interface Block {
}

/**
*
* @ignore
*/
function encodeEsploraMerkleProof(merkle: string[]): string {
// convert to little-endian
return merkle.map(item => Buffer.from(item, "hex").reverse().toString("hex")).join('');
}

/**
* The `EsploraClient` interface provides a set of methods for interacting with an Esplora API
* for Bitcoin network data retrieval.
* See https://github.com/blockstream/esplora/blob/master/API.md for more information.
*/
export interface EsploraClient {
export class EsploraClient {
private basePath: string;

/**
* Create an instance of the `EsploraClient` with the specified network or URL.
* If the `networkOrUrl` parameter is omitted, it defaults to "mainnet."
*
* @param networkOrUrl The Bitcoin network (e.g., "mainnet," "testnet," "regtest")
*
* @returns An instance of the `EsploraClient` configured for the specified network or URL.
*
* @example
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
*
* @example
* // Create a client for the mainnet using the default URL.
* const esploraClientMainnet = new EsploraClient();
*/
constructor(networkOrUrl: string = "mainnet") {
switch (networkOrUrl) {
case "mainnet":
this.basePath = MAINNET_ESPLORA_BASE_PATH;
break;
case "testnet":
this.basePath = TESTNET_ESPLORA_BASE_PATH;
break;
case "regtest":
this.basePath = REGTEST_ESPLORA_BASE_PATH;
break;
default:
this.basePath = networkOrUrl;
}
}

/**
* Get the latest block height of the Bitcoin chain.
*
* @returns {Promise<number>} A promise that resolves to the latest block number.
*/
getLatestHeight(): Promise<number>;
async getLatestHeight(): Promise<number> {
return parseInt(await this.getText(`${this.basePath}/blocks/tip/height`), 10);
}

/**
* Get the complete block data for a Bitcoin block with a given hash.
Expand All @@ -119,7 +162,7 @@ export interface EsploraClient {
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
* const blockHash = 'your_block_hash_here';
* esploraClient.getBlock(blockHash)
* .then((block) => {
Expand All @@ -130,7 +173,9 @@ export interface EsploraClient {
* });
* ```
*/
getBlock(hash: string): Promise<Block>;
async getBlock(blockHash: string): Promise<Block> {
return this.getJson(`${this.basePath}/block/${blockHash}`);
}

/**
* Get the block hash of the Bitcoin block at a specific height.
Expand All @@ -143,7 +188,7 @@ export interface EsploraClient {
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
* const blockHeight = 123456;
* esploraClient.getBlockHash(blockHeight)
* .then((blockHash) => {
Expand All @@ -154,7 +199,9 @@ export interface EsploraClient {
* });
* ```
*/
getBlockHash(height: number): Promise<string>;
async getBlockHash(height: number): Promise<string> {
return this.getText(`${this.basePath}/block-height/${height}`);
}

/**
* Get the raw block header, represented as a hex string, for a Bitcoin block with a given hash.
Expand All @@ -165,7 +212,7 @@ export interface EsploraClient {
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
* const blockHash = 'your_block_hash_here';
* esploraClient.getBlockHeader(blockHash)
* .then((blockHeader) => {
Expand All @@ -176,7 +223,17 @@ export interface EsploraClient {
* });
* ```
*/
getBlockHeader(hash: string): Promise<string>;
async getBlockHeader(hash: string): Promise<string> {
return this.getText(`${this.basePath}/block/${hash}/header`);
}

/**
* @ignore
*/
async getBlockHeaderAt(height: number): Promise<string> {
const blockHash = await this.getBlockHash(height);
return await this.getBlockHeader(blockHash);
}

/**
* Get the complete transaction data for a Bitcoin transaction with a given ID (txId).
Expand All @@ -187,7 +244,7 @@ export interface EsploraClient {
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
* const transactionId = 'your_transaction_id_here';
* esploraClient.getTransaction(transactionId)
* .then((transaction) => {
Expand All @@ -198,7 +255,9 @@ export interface EsploraClient {
* });
* ```
*/
getTransaction(txId: string): Promise<Transaction>;
async getTransaction(txId: string): Promise<Transaction> {
return this.getJson(`${this.basePath}/tx/${txId}`);
}

/**
* Get the transaction data, represented as a hex string, for a Bitcoin transaction with a given ID (txId).
Expand All @@ -209,7 +268,7 @@ export interface EsploraClient {
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
* const transactionId = 'your_transaction_id_here';
* esploraClient.getTransactionHex(transactionId)
* .then((transactionHex) => {
Expand All @@ -220,7 +279,9 @@ export interface EsploraClient {
* });
* ```
*/
getTransactionHex(txId: string): Promise<string>;
async getTransactionHex(txId: string): Promise<string> {
return this.getText(`${this.basePath}/tx/${txId}/hex`);
}

/**
* Get the encoded merkle inclusion proof for a Bitcoin transaction with a given ID (txId).
Expand All @@ -231,7 +292,7 @@ export interface EsploraClient {
* @example
* ```typescript
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
* const esploraClient = new EsploraClient(BITCOIN_NETWORK);
* const transactionId = 'your_transaction_id_here';
* esploraClient.getMerkleProof(transactionId)
* .then((merkleProof) => {
Expand All @@ -242,130 +303,6 @@ export interface EsploraClient {
* });
* ```
*/
getMerkleProof(txId: string): Promise<MerkleProof>;

/**
* Get the fee estimate (in sat/vB) for the given confirmation target.
*
* @param {number} confirmationTarget - The number of blocks to be included in.
* @returns {Promise<number>} A promise that resolves to the fee rate.
*/
getFeeEstimate(confirmationTarget: number): Promise<number>;

/**
* Get the Unspent Transaction Outputs (UTXOs) for an address.
*
* @param {string} address - The Bitcoin address to check.
* @returns {Promise<Array<UTXO>>} A promise that resolves to an array of UTXOs.
*/
getAddressUtxos(address: string): Promise<Array<UTXO>>;

/**
* Broadcast a raw transaction to the network.
*
* @param {string} txHex - The hex encoded transaction.
* @returns {Promise<string>} A promise that resolves to the txid.
*/
broadcastTx(txHex: string): Promise<string>;
}

/**
* @ignore
*/
function encodeEsploraMerkleProof(merkle: string[]): string {
// convert to little-endian
return merkle.map(item => Buffer.from(item, "hex").reverse().toString("hex")).join('');
}

/**
* The `DefaultEsploraClient` class provides a client for interacting with an Esplora API
* for Bitcoin network data retrieval.
*/
export class DefaultEsploraClient implements EsploraClient {
private basePath: string;

/**
* Create an instance of the `DefaultEsploraClient` with the specified network or URL.
* If the `networkOrUrl` parameter is omitted, it defaults to "mainnet."
*
* @param networkOrUrl The Bitcoin network (e.g., "mainnet," "testnet," "regtest")
*
* @returns An instance of the `DefaultEsploraClient` configured for the specified network or URL.
*
* @example
* const BITCOIN_NETWORK = "regtest";
* const esploraClient = new DefaultEsploraClient(BITCOIN_NETWORK);
*
* @example
* // Create a client for the mainnet using the default URL.
* const esploraClientMainnet = new DefaultEsploraClient();
*/
constructor(networkOrUrl: string = "mainnet") {
switch (networkOrUrl) {
case "mainnet":
this.basePath = MAINNET_ESPLORA_BASE_PATH;
break;
case "testnet":
this.basePath = TESTNET_ESPLORA_BASE_PATH;
break;
case "regtest":
this.basePath = REGTEST_ESPLORA_BASE_PATH;
break;
default:
this.basePath = networkOrUrl;
}
}

async getLatestHeight(): Promise<number> {
return parseInt(await this.getText(`${this.basePath}/blocks/tip/height`), 10);
}

/**
* @ignore
*/
async getBlock(blockHash: string): Promise<Block> {
return this.getJson(`${this.basePath}/block/${blockHash}`);
}

/**
* @ignore
*/
async getBlockHash(height: number): Promise<string> {
return this.getText(`${this.basePath}/block-height/${height}`);
}

/**
* @ignore
*/
async getBlockHeader(hash: string): Promise<string> {
return this.getText(`${this.basePath}/block/${hash}/header`);
}

/**
* @ignore
*/
async getBlockHeaderAt(height: number): Promise<string> {
const blockHash = await this.getBlockHash(height);
return await this.getBlockHeader(blockHash);
}

/**
* @ignore
*/
async getTransaction(txId: string): Promise<Transaction> {
return this.getJson(`${this.basePath}/tx/${txId}`);
}

/**
* @ignore
*/
async getTransactionHex(txId: string): Promise<string> {
return this.getText(`${this.basePath}/tx/${txId}/hex`);
}

/**
* @ignore
*/
async getMerkleProof(txId: string): Promise<MerkleProof> {
const response = await this.getJson<{
"block_height": number,
Expand All @@ -380,15 +317,21 @@ export class DefaultEsploraClient implements EsploraClient {
}

/**
* @ignore
* Get the fee estimate (in sat/vB) for the given confirmation target.
*
* @param {number} confirmationTarget - The number of blocks to be included in.
* @returns {Promise<number>} A promise that resolves to the fee rate.
*/
async getFeeEstimate(confirmationTarget: number): Promise<number> {
const response = await this.getJson<any>(`${this.basePath}/fee-estimates`);
return response[confirmationTarget];
}

/**
* @ignore
* Get the Unspent Transaction Outputs (UTXOs) for an address.
*
* @param {string} address - The Bitcoin address to check.
* @returns {Promise<Array<UTXO>>} A promise that resolves to an array of UTXOs.
*/
async getAddressUtxos(address: string, confirmed?: boolean): Promise<Array<UTXO>> {
const response = await this.getJson<Array<{
Expand Down Expand Up @@ -416,7 +359,10 @@ export class DefaultEsploraClient implements EsploraClient {
}

/**
* @ignore
* Broadcast a raw transaction to the network.
*
* @param {string} txHex - The hex encoded transaction.
* @returns {Promise<string>} A promise that resolves to the txid.
*/
async broadcastTx(txHex: string): Promise<string> {
const res = await fetch(`${this.basePath}/tx`, {
Expand All @@ -429,7 +375,7 @@ export class DefaultEsploraClient implements EsploraClient {
/**
* @ignore
*/
async getJson<T>(url: string): Promise<T> {
private async getJson<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(response.statusText);
Expand All @@ -440,7 +386,7 @@ export class DefaultEsploraClient implements EsploraClient {
/**
* @ignore
*/
async getText(url: string): Promise<string> {
private async getText(url: string): Promise<string> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(response.statusText);
Expand Down
Loading

0 comments on commit 858761f

Please sign in to comment.