use aide::axum::{ApiRouter, routing::get_with}; use axum::{ extract::{Path, State}, http::{HeaderMap, Uri}, }; use brk_types::{ BlockTemplate, BlockTemplateDiff, Dollars, MempoolInfo, MempoolRecentTx, NextBlockHash, ReplacementNode, Txid, }; use crate::{ AppState, extended::TransformResponseExtended, params::{Empty, NextBlockHashParam}, }; pub trait MempoolRoutes { fn add_mempool_routes(self) -> Self; } impl MempoolRoutes for ApiRouter { fn add_mempool_routes(self) -> Self { self.api_route( "/api/mempool", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| q.mempool_info()) .await }, |op| { op.id("get_mempool") .mempool_tag() .summary("Mempool statistics") .description("Get current mempool statistics including transaction count, total vsize, total fees, and fee histogram.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool)*") .json_response::() .not_modified() .server_error() }, ), ) .api_route( "/api/mempool/hash", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| q.mempool_hash()) .await }, |op| { op.id("get_mempool_hash") .mempool_tag() .summary("Mempool content hash") .description("Returns an opaque hash that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.") .json_response::() .not_modified() .server_error() }, ), ) .api_route( "/api/mempool/txids", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| q.mempool_txids()) .await }, |op| { op.id("get_mempool_txids") .mempool_tag() .summary("Mempool transaction IDs") .description("Get all transaction IDs currently in the mempool.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-transaction-ids)*") .json_response::>() .not_modified() .server_error() }, ), ) .api_route( "/api/mempool/recent", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| q.mempool_recent()) .await }, |op| { op.id("get_mempool_recent") .mempool_tag() .summary("Recent mempool transactions") .description("Get the last 10 transactions to enter the mempool.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-recent)*") .json_response::>() .not_modified() .server_error() }, ), ) .api_route( "/api/v1/replacements", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| { q.recent_replacements(false) }) .await }, |op| { op.id("get_replacements") .mempool_tag() .summary("Recent RBF replacements") .description("Returns up to 25 most-recent RBF replacement trees across the whole mempool. Each entry has the same shape as `tx_rbf().replacements`.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-replacements)*") .json_response::>() .not_modified() .server_error() }, ), ) .api_route( "/api/v1/fullrbf/replacements", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| { q.recent_replacements(true) }) .await }, |op| { op.id("get_fullrbf_replacements") .mempool_tag() .summary("Recent full-RBF replacements") .description("Like `/api/v1/replacements`, but limited to trees where at least one predecessor was non-signaling (full-RBF).\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-fullrbf-replacements)*") .json_response::>() .not_modified() .server_error() }, ), ) .api_route( "/api/v1/mempool/block-template", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| { q.block_template() }) .await }, |op| { op.id("get_block_template") .mempool_tag() .summary("Projected next block template") .description("Bitcoin Core's `getblocktemplate` selection: full transaction bodies in GBT order with aggregate stats. The returned `hash` is an opaque content token; pass it as `` on `/api/v1/mempool/block-template/diff/{hash}` to fetch deltas instead of refetching the whole template.") .json_response::() .not_modified() .server_error() }, ), ) .api_route( "/api/v1/mempool/block-template/diff/{hash}", get_with( async |uri: Uri, headers: HeaderMap, Path(path): Path, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, move |q| { q.block_template_diff(path.hash) }) .await }, |op| { op.id("get_block_template_diff") .mempool_tag() .summary("Block template diff since hash") .description("Delta of the projected next block since ``. `added` carries full transaction bodies; `removed` is just txids. Returns `404` when `` has aged out of server history; clients should fall back to `/api/v1/mempool/block-template`.") .json_response::() .not_modified() .not_found() .server_error() }, ), ) .api_route( "/api/mempool/price", get_with( async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State| { state .respond_json(&headers, state.mempool_strategy(), &uri, |q| q.live_price()) .await }, |op| { op.id("get_live_price") .mempool_tag() .summary("Live BTC/USD price") .description( "Returns the current BTC/USD price in dollars, derived from \ on-chain round-dollar output patterns in the last 12 blocks \ plus mempool.", ) .json_response::() .not_modified() .server_error() }, ), ) } }