Metadata-Version: 2.4
Name: electrum-client
Version: 0.1.0
Summary: Async Electrum protocol client: JSON-RPC over TCP/SSL with subscriptions and onchain address/tx/block tracking.
Project-URL: Repository, https://github.com/lnbits/electrum-client
Author-email: dni <office@dnilabs.com>
License-Expression: MIT
Requires-Python: <3.14,>=3.10
Requires-Dist: embit<0.9.0,>=0.8.0
Requires-Dist: loguru<0.8.0,>=0.7.0
Requires-Dist: pydantic<2.0.0,>=1.10.0
Description-Content-Type: text/markdown

# electrum-client

Async Python client for the [Electrum protocol](https://github.com/spesmilo/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.version` handshake sent automatically on connect, as required by the spec
- Response payloads parsed into typed `pydantic` models
- Raw transaction / block header parsing (via [`embit`](https://github.com/diybitcoinhardware/embit))
- Reconnecting trackers for address balances/history, single transactions, and new blocks

## Install

```bash
pip install electrum-client
```

## Usage

```python
import 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())
```

### Scripthashes and balances

```python
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)
```

### Subscriptions

```python
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 notifications
```

### Trackers

`AddressTracker`, `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.

```python
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)
```

### Parsing helpers

```python
from electrum_client import parse_raw_tx, parse_block_header

tx = parse_raw_tx(raw_tx_hex)
header = parse_block_header(header_hex, height)
```

## Development

Requires [`uv`](https://docs.astral.sh/uv/).

```bash
uv sync
make format   # black + ruff --fix
make check    # black --check, ruff check, mypy
make test     # unit tests
```

### Regtest integration tests

The 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 implement `server.features`
  or `blockchain.scripthash.get_mempool`
- `fulcrum` — fully spec-compliant, used to cover the protocol paths electrs
  can't (see `tests/regtest/test_fulcrum.py`)

```bash
make regtest-up
uv run pytest tests/regtest
make regtest-down
```

## License

MIT
