Async Python client for the Electrum protocol (JSON-RPC 2.0 over TCP/SSL, newline-delimited), plus higher-level helpers for tracking onchain addresses, transactions, and blocks.
- Request/response correlation by id, subscription dispatch, automatic keepalive pings
server.versionhandshake sent automatically on connect, as required by the spec- Response payloads parsed into typed
pydanticmodels - Raw transaction / block header parsing (via
embit) - Reconnecting trackers for address balances/history, single transactions, and new blocks
pip install electrum-clientimport asyncio
from electrum_client import ElectrumClient
async def main():
async with ElectrumClient("ssl://electrum.blockstream.info:50002") as client:
print(await client.get_height())
print(await client.server_banner())
asyncio.run(main())from electrum_client import ElectrumClient, scripthash_from_address
async with ElectrumClient("ssl://electrum.blockstream.info:50002") as client:
scripthash = scripthash_from_address("bc1q...")
balance = await client.get_balance(scripthash)
history = await client.get_history(scripthash)
utxos = await client.listunspent(scripthash)def on_change(params):
print("scripthash status changed:", params)
async with ElectrumClient("ssl://electrum.blockstream.info:50002") as client:
await client.subscribe_scripthash(scripthash, callback=on_change)
await asyncio.sleep(60) # keep the connection open to receive notificationsAddressTracker, TransactionTracker, and BlockTracker wrap a reconnecting
ElectrumClient connection and dispatch typed events (OnchainAddressEvent,
OnchainTxEvent, BlockInfo) to an async callback — useful for driving
websockets or other push-based consumers.
from electrum_client import AddressTracker
tracker = AddressTracker("ssl://electrum.blockstream.info:50002")
tracker.add("bc1q...")
async def on_event(event):
print(event.address, event.confirmed, event.unconfirmed)
await tracker.run(callback=on_event, is_active=lambda: True)from electrum_client import parse_raw_tx, parse_block_header
tx = parse_raw_tx(raw_tx_hex)
header = parse_block_header(header_hex, height)Requires uv.
uv sync
make format # black + ruff --fix
make check # black --check, ruff check, mypy
make test # unit testsThe regtest suite runs the client against bitcoind in Docker, indexed by
two independent Electrum servers so the client is exercised against more
than one implementation:
electrs(electrs-esplora) — fast, but doesn't implementserver.featuresorblockchain.scripthash.get_mempoolfulcrum— fully spec-compliant, used to cover the protocol paths electrs can't (seetests/regtest/test_fulcrum.py)
make regtest-up
uv run pytest tests/regtest
make regtest-downMIT