From 52a7920dc29ebb295093292107736317e9383582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sim=C3=A3o?= Date: Thu, 17 Oct 2024 12:40:46 +0100 Subject: [PATCH] feat(sdk): add mempool client (#394) --- sdk/package.json | 2 +- sdk/src/index.ts | 1 + sdk/src/mempool.ts | 102 +++++++++++++++++++++++++++++++++++++++ sdk/test/mempool.test.ts | 41 ++++++++++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 sdk/src/mempool.ts create mode 100644 sdk/test/mempool.test.ts diff --git a/sdk/package.json b/sdk/package.json index 8dc2c5b0..2febb4a8 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@gobob/bob-sdk", - "version": "3.0.0", + "version": "3.0.1", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { diff --git a/sdk/src/index.ts b/sdk/src/index.ts index ae217bfd..2f308ab8 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -5,3 +5,4 @@ export * from './ordinals'; export * from './helpers'; export * from './wallet'; export * from './gateway'; +export * from './mempool'; diff --git a/sdk/src/mempool.ts b/sdk/src/mempool.ts new file mode 100644 index 00000000..c39a8c63 --- /dev/null +++ b/sdk/src/mempool.ts @@ -0,0 +1,102 @@ +/** + * Base path for the mainnet Memopool API. + * @default "https://btc-mainnet.gobob.xyz" + */ +export const MAINNET_MEMPOOL_BASE_PATH = 'https://mempool.space/api/v1/'; +/** + * Base path for the testnet Memopool API. + * @default "https://btc-testnet.gobob.xyz" + */ +export const TESTNET_MEMPOOL_BASE_PATH = 'https://mempool.space/testnet4/api/v1'; +/** + * Base path for the regtest Memopool API. + * @default "http://localhost:3003" + */ +export const REGTEST_MEMPOOL_BASE_PATH = 'http://localhost:3003'; + +/** + * @ignore + */ +export type MempoolRecomendedFee = { + fastestFee: number; + halfHourFee: number; + hourFee: number; + economyFee: number; + minimumFee: number; +}; + +export class MempoolClient { + private basePath: string; + + /** + * Create an instance of the `MempoolPool` 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 `MempoolPool` configured for the specified network or URL. + * + * @example + * const BITCOIN_NETWORK = "regtest"; + * const mempoolClient = new MempoolPool(BITCOIN_NETWORK); + * + * @example + * // Create a client for the mainnet using the default URL. + * const mempoolClientMainnet = new MempoolPool(); + */ + constructor(networkOrUrl: string = 'mainnet') { + switch (networkOrUrl) { + case 'mainnet': + this.basePath = MAINNET_MEMPOOL_BASE_PATH; + break; + case 'testnet': + this.basePath = TESTNET_MEMPOOL_BASE_PATH; + break; + case 'regtest': + this.basePath = REGTEST_MEMPOOL_BASE_PATH; + break; + default: + this.basePath = networkOrUrl; + } + } + + /** + * Get the recommended Bitcoin transaction fee rates from the Mempool API. + * + * This method returns the fee estimates in satoshis per virtual byte (sat/vB) + * for different confirmation targets, including: + * + * - `fastestFee`: The fee rate for transactions that are likely to be included + * in the next block (fastest possible confirmation). + * - `halfHourFee`: The fee rate for transactions that are likely to be confirmed + * within 30 minutes. + * - `hourFee`: The fee rate for transactions that are likely to be confirmed + * within an hour. + * - `economyFee`: The fee rate for transactions that are likely to be confirmed + * in a longer period (low priority). + * - `minimumFee`: The lowest fee rate that is still accepted by miners. + * + * @returns {Promise} A promise that resolves to an object containing + * the recommended fees for various confirmation times. + * + * @example + * const mempoolClient = new MempoolClient(); + * mempoolClient.getRecommendedFees() + * .then(fees => console.log(fees)) + * .catch(error => console.error('Failed to fetch fees:', error)); + */ + async getRecommendedFees(): Promise { + return this.getJson(`${this.basePath}/fees/recommended`); + } + + /** + * @ignore + */ + private async getJson(url: string): Promise { + const response = await fetch(url); + if (!response.ok) { + throw new Error(response.statusText); + } + return (await response.json()) as Promise; + } +} diff --git a/sdk/test/mempool.test.ts b/sdk/test/mempool.test.ts new file mode 100644 index 00000000..0b3ce5b9 --- /dev/null +++ b/sdk/test/mempool.test.ts @@ -0,0 +1,41 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { MempoolClient } from '../src/mempool'; + +const MOCKS = { + fees: { + recommended: { + fastestFee: 100, + halfHourFee: 80, + hourFee: 60, + economyFee: 40, + minimumFee: 10, + }, + }, +}; + +describe('Mempool Tests', () => { + const client = new MempoolClient(); + + beforeEach(() => { + // Mock the fetch API only for URLs including /fees/recommended + global.fetch = vi.fn((url) => { + if (url.includes('/fees/recommended')) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(MOCKS.fees.recommended), + } as Response); + } + return Promise.reject(new Error('Unexpected URL')); + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should get recommended fee', async () => { + const fees = await client.getRecommendedFees(); + + expect(fees).toEqual(MOCKS.fees.recommended); + }); +});