Skip to content
Merged
19 changes: 19 additions & 0 deletions src/lib/blockchain-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,30 @@ const MEMPOOL_SPACE_API_BASE = 'https://mempool.space/api';

const ESPLORA_BASES = [BLOCKSTREAM_API_BASE, MEMPOOL_SPACE_API_BASE];

const ALLOWED_HOSTS = new Set([
'blockstream.info',
'mempool.space',
'api.coingecko.com',
'blockchain.info',
'api.alternative.me',
]);

function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function fetchJson(url: string, options?: RequestInit, revalidate?: number): Promise<any> {
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
} catch {
throw new Error('Invalid provider URL.');
}

if (parsedUrl.protocol !== 'https:' || !ALLOWED_HOSTS.has(parsedUrl.hostname)) {
throw new Error('Disallowed provider URL.');
}

const headers: Record<string, string> = {
'Accept': 'application/json',
'User-Agent': 'BitSleuth/1.0',
Expand Down
11 changes: 9 additions & 2 deletions src/lib/mempool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ export async function getMempoolData(): Promise<{ data: MempoolData | null; erro

export async function getBlockDetails(hash: string, startIndex: number = 0): Promise<{ data: BlockDetails | null; error: string | null; }> {
try {
const blockUrl = `https://mempool.space/api/block/${hash}`;
const txsUrl = `https://mempool.space/api/block/${hash}/txs/${startIndex}`;
const normalizedHash = hash.trim();
if (!/^[a-fA-F0-9]{64}$/.test(normalizedHash)) {
return { data: null, error: 'The block hash you entered is not valid.' };
}

const safeStartIndex = Number.isInteger(startIndex) && startIndex >= 0 ? startIndex : 0;
const encodedHash = encodeURIComponent(normalizedHash);
const blockUrl = `https://mempool.space/api/block/${encodedHash}`;
const txsUrl = `https://mempool.space/api/block/${encodedHash}/txs/${safeStartIndex}`;

// Block data is immutable, so we can cache it for a long time.
const [blockData, blockTxsData] = await Promise.all([
Expand Down
Loading