Skip to content

Subscribing to events

WaveLedger exposes a single SSE stream that pushes every chain-side state change — new blocks, every confirmed transaction, chat messages, and contract receipts. Server-side filtering by event type and address keeps client code small.

See Server-Sent Events for the full endpoint spec.

All events (firehose)

import requests, json

with requests.get(
    'https://api.waveledger.net/api/stream',
    cookies={'session': '...'}, stream=True,
) as r:
    for line in r.iter_lines():
        if line.startswith(b'data: '):
            print(json.loads(line[6:]))

The stream emits four event types: block, tx, message, receipt. Dedupe defensively on tx_id for tx-shaped events and height for block events.

New blocks only

with requests.get(
    'https://api.waveledger.net/api/stream?types=block',
    cookies={'session': '...'}, stream=True,
) as r:
    for line in r.iter_lines():
        if not line.startswith(b'data: '):
            continue
        ev = json.loads(line[6:])
        b = ev['block']
        print(f"block {b['height']}: {b['tx_count']} txs, miner {b['miner'][:16]}")

Watch one address

addr = '34378b1ba5be9d0999acd60be3a8a1f1'
with requests.get(
    f'https://api.waveledger.net/api/stream?address={addr}',
    cookies={'session': '...'}, stream=True,
) as r:
    for line in r.iter_lines():
        if line.startswith(b'data: '):
            ev = json.loads(line[6:])
            # ev['type'] in {'tx', 'message', 'receipt', 'block'}; filtered
            # server-side to events that name `addr` in any role.
            print(ev['type'], ev)

The address filter matches sender, recipient, to, contract address, miner, and message author. Combine with types= to narrow further:

/api/stream?types=tx,receipt&address=<addr>

Contract receipts only

with requests.get(
    'https://api.waveledger.net/api/stream?types=receipt',
    cookies={'session': '...'}, stream=True,
) as r:
    for line in r.iter_lines():
        if line.startswith(b'data: '):
            print(json.loads(line[6:])['receipt'])

Fallback: poll the explorer

The SSE stream is the recommended path. The polling examples below remain accurate for clients that can't open long-lived connections (e.g. some serverless platforms).

Poll for new blocks

import requests, time

last_height = 0
while True:
    s = requests.get('https://api.waveledger.net/api/explorer/stats').json()
    if s['height'] > last_height:
        for h in range(last_height + 1, s['height'] + 1):
            b = requests.get(f'https://api.waveledger.net/api/explorer/block/{h}').json()
            print(f"block {h}: {b['tx_count']} txs")
        last_height = s['height']
    time.sleep(2)

For mainnet (60s block times) sleep(5) is plenty. For testnet (5s blocks) sleep(1-2) to catch every block.

Poll a specific receipt

After submitting a deploy or call, poll the receipt endpoint:

import requests, time

def wait_for_receipt(tx_id, base='https://api.waveledger.net', timeout=30):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f'{base}/api/explorer/tx/{tx_id}').json()
        if r.get('status') == 'confirmed' and r.get('receipt') is not None:
            return r['receipt']
        time.sleep(1)
    raise TimeoutError(f"receipt for {tx_id} never landed")

Mempool changes: poll

Same pattern — poll /api/mempool (dashboard, local only) or /api/explorer/stats for the mempool_size field.

Address activity: poll

For watching when a specific address receives or sends:

last_tx_count = 0
while True:
    r = requests.get(f'.../api/explorer/address/{addr}').json()
    if r['tx_count'] > last_tx_count:
        new = r['transactions'][:r['tx_count'] - last_tx_count]
        for tx in new:
            print(f"{tx['direction']}: {tx['amount']} WAVE")
        last_tx_count = r['tx_count']
    time.sleep(3)

Why no WebSockets?

The testnet uses SSE for the one stream that needs it (chat) because SSE is half the complexity of WebSockets, works through every CDN without special config, has auto-reconnect built into browsers, and fits a one-way push perfectly.

Bidirectional channels (which the chain currently doesn't need) would warrant WebSockets. There's no roadmap item to add them yet.

Roadmap

Feature Status
block SSE event Shipped
tx SSE event Shipped
receipt SSE event Shipped
Per-address SSE filter Shipped
Per-type SSE filter Shipped
eth_subscribe-style WS Not planned
Webhook (HTTP POST on event) Considering