jmcore.fee_source
jmcore.fee_source
External HTTP fee-rate estimation for backends without a full node.
The neutrino backend cannot estimate fees from its own view of the network
(no mempool, no estimatesmartfee). Like other light clients (LND's
fee.url, Wasabi's mempool.space provider, Electrum servers), we fetch
estimates from a configurable HTTP endpoint, routed through Tor.
Supported response formats (auto-detected):
- mempool.space recommended fees:
{"fastestFee": 3, "halfHourFee": 2, "hourFee": 1, "economyFee": 1, ...} - Esplora
/fee-estimates:{"1": 87.8, "2": 55.1, ..., "144": 1.0}(sat/vB keyed by target) - LND
fee.urlstyle:{"fee_by_block_target": {"2": 12500, "6": 5000}}(sat/kvB)
All results are normalized to a {block_target: sat_per_vb} mapping.
Privacy: a fee query reveals only "someone behind this connection wants fee estimates" (no addresses), but at send time it correlates with an imminent broadcast, so callers must pass a Tor SOCKS proxy for non-local URLs. The default (third-party) sources are only auto-enabled when a proxy is available.
Safety: estimates are advisory input from a third party. Entries above
:data:ABSURD_FEE_RATE_SAT_VB are discarded at parse time, and every
consumer additionally enforces the operator's hard cap
(wallet.max_fee_rate_sat_vb, default 1000 sat/vB) on the resolved rate,
so a compromised or broken provider cannot make transactions spend more than
the configured maximum.
Attributes
ABSURD_FEE_RATE_SAT_VB = 5000.0
module-attribute
DEFAULT_FEE_SOURCE_URLS: dict[str, tuple[str, ...]] = {'mainnet': (f'{_MEMPOOL_ONION}/api/v1/fees/recommended', f'{_ESPLORA_ONION}/api/fee-estimates', 'https://mempool.space/api/v1/fees/recommended', 'https://blockstream.info/api/fee-estimates'), 'testnet': (f'{_MEMPOOL_ONION}/testnet/api/v1/fees/recommended', f'{_ESPLORA_ONION}/testnet/api/fee-estimates', 'https://mempool.space/testnet/api/v1/fees/recommended', 'https://blockstream.info/testnet/api/fee-estimates'), 'testnet4': (f'{_MEMPOOL_ONION}/testnet4/api/v1/fees/recommended', 'https://mempool.space/testnet4/api/v1/fees/recommended'), 'signet': (f'{_MEMPOOL_ONION}/signet/api/v1/fees/recommended', 'https://mempool.space/signet/api/v1/fees/recommended')}
module-attribute
SOURCE_RETRY_SECONDS = 600.0
module-attribute
Classes
FeeEstimateResult
Bases: NamedTuple
Normalized estimates and the source that successfully returned them.
Source code in jmcore/src/jmcore/fee_source.py
99 100 101 102 103 | |
Attributes
estimates: dict[int, float]
instance-attribute
source_url: str
instance-attribute
FeeSourceError
Bases: Exception
Raised when external fee estimates cannot be fetched or parsed.
Source code in jmcore/src/jmcore/fee_source.py
95 96 | |
Functions:
build_fee_source_proxy(socks_host: str | None, socks_port: int | None, stream_isolation: bool = False) -> str | None
Build the SOCKS proxy URL used for fee source requests.
With stream_isolation the shared MEMPOOL isolation category is used so
fee queries ride a Tor circuit separate from directory/peer traffic.
Source code in jmcore/src/jmcore/fee_source.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
default_fee_source_urls(network: str) -> list[str]
Return the default fee source URLs for network (empty for regtest).
Source code in jmcore/src/jmcore/fee_source.py
111 112 113 | |
fetch_fee_estimates(url: str, socks_proxy: str | None = None, timeout: float = 20.0, transport: Any | None = None) -> dict[int, float]
async
Fetch and parse fee estimates from url.
socks_proxy routes the request through Tor (socks5h://host:port,
optionally with stream-isolation credentials). transport allows tests
to inject an httpx mock transport.
Source code in jmcore/src/jmcore/fee_source.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
fetch_fee_estimates_with_fallback(urls: list[str], socks_proxy: str | None = None, timeout: float = 20.0, transport: Any | None = None) -> FeeEstimateResult
async
Fetch estimates from the first working source in urls.
Sources are tried in order (defaults put onion services first, then
clearnet mirrors); per-source failures are logged and the next source is
tried. Raises :class:FeeSourceError when every source fails.
Source code in jmcore/src/jmcore/fee_source.py
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
is_fee_source_disabled(url: str | None) -> bool
Return True when url is an explicit "disable" sentinel (not None).
Source code in jmcore/src/jmcore/fee_source.py
106 107 108 | |
parse_fee_estimates(data: Any) -> dict[int, float]
Normalize a fee source response to {block_target: sat_per_vb}.
Raises :class:FeeSourceError when the payload matches no known format
or contains no usable estimate.
Source code in jmcore/src/jmcore/fee_source.py
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
pick_fee_rate(estimates: dict[int, float], target_blocks: int) -> float
Select the sat/vB rate for confirming within target_blocks.
Picks the largest available target that does not exceed the requested one (a faster-or-equal estimate is always sufficient); when the request is faster than anything available, the fastest available estimate is used.
Source code in jmcore/src/jmcore/fee_source.py
193 194 195 196 197 198 199 200 201 202 203 204 | |