Skip to content
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
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