Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): add mempool client #394

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
1 change: 1 addition & 0 deletions sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './ordinals';
export * from './helpers';
export * from './wallet';
export * from './gateway';
export * from './mempool';
102 changes: 102 additions & 0 deletions sdk/src/mempool.ts
Original file line number Diff line number Diff line change
@@ -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<MempoolRecomendedFee>} 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<MempoolRecomendedFee> {
return this.getJson<MempoolRecomendedFee>(`${this.basePath}/fees/recommended`);
}

/**
* @ignore
*/
private async getJson<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(response.statusText);
}
return (await response.json()) as Promise<T>;
}
}
41 changes: 41 additions & 0 deletions sdk/test/mempool.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading