---

title: Python Wrappers — Subscriptions

audience: industry_consumers

game: WIKI-PYTHON-WRAPPERS-SUBSCRIPTIONS-001

---

Live Substrate Event Streams

FranklinClient.subscribe(subject) is an async iterator yielding newly-appended substrate rows. Polling fallback works without NATS; full NATS bus path activates when ~/.gaiaftcl/nats.toml or GAIAFTCL_NATS_URL is configured.

---

Subjects

NATS subject Substrate table When it fires
gaiaftcl.substrate.franklin_heartbeat.sealed franklin_heartbeat_history Per heartbeat tick (60s default)
gaiaftcl.qc020.franklin.window_filtering.sealed franklin_window_filtering Per substrate measurement window-close
gaiaftcl.qc020.substrate.joint_variation_evidence.sealed qc020_joint_variation_evidence Per substrate measurement
gaiaftcl.substrate.healing_action.sealed substrate_healing_actions Per heal-cycle action

Wildcard matching follows NATS conventions (* for one token, > for tail).

---

Async iterator pattern

import asyncio
from gaiaftcl import FranklinClient

async def watch_heartbeats():
    with FranklinClient.connect() as franklin:
        async for event in franklin.subscribe(
                "gaiaftcl.substrate.franklin_heartbeat.sealed"):
            row = event["row"]
            print(f"{row['tick_at_iso']} "
                  f"obs={row['observations_count']} "
                  f"sov_auth={row['commissions_franklin_authorized']}")

asyncio.run(watch_heartbeats())

Output (one line per Franklin tick, ~60s cadence):

2026-06-01T16:27:30Z obs=654 sov_auth=0
2026-06-01T16:28:30Z obs=655 sov_auth=0
2026-06-01T16:29:30Z obs=656 sov_auth=0

---

Watching substrate measurements

V178 fires per substrate measurement (~3–4 measurements per second under joint variation):

import asyncio
from gaiaftcl import FranklinClient

async def watch_v178():
    with FranklinClient.connect() as franklin:
        async for event in franklin.subscribe(
                "gaiaftcl.qc020.substrate.joint_variation_evidence.sealed"):
            row = event["row"]
            if row["leading_zero_nibble_count"] > 1:
                print(f"interesting: lz={row['leading_zero_nibble_count']} "
                      f"extranonce={row['cursor_extranonce']}")

asyncio.run(watch_v178())

---

Verifying every event as it arrives

import asyncio
from gaiaftcl import FranklinClient
from gaiaftcl.federation.cosignature import verify_signature_quintet

async def watch_and_verify():
    with FranklinClient.connect() as franklin:
        async for event in franklin.subscribe(
                "gaiaftcl.substrate.franklin_heartbeat.sealed"):
            row = event["row"]
            ok = verify_signature_quintet(
                row["canonical_witness"],
                row["witness_hash_sha256"],
                row["signature_quintet"],
            )
            mark = "✓" if ok else "✗"
            print(f"{mark} {row['tick_at_iso']} {row['heartbeat_id']}")

asyncio.run(watch_and_verify())

---

Polling fallback

When NATS is unavailable, the client polls the substrate's SQLite at 1-second cadence and yields newly-appended rows by row_id ordering. Behavior is equivalent to the NATS path for read-only consumers.

Polling watermarks reset at subscribe() call time — only events newer than the call yield. To replay historical events, query the substrate read methods (heartbeat_history, research_telemetry, etc.) and process the returned list.

---

NATS bus configuration

~/.gaiaftcl/nats.toml:

[nats]
url = "nats://localhost:4222"
credentials = "/etc/gaiaftcl/nats.creds"

Or GAIAFTCL_NATS_URL environment variable.

When NATS is configured, the client connects on first subscribe() call; subsequent subscriptions reuse the connection.

---

*Substrate cadence is yours to consume. Every event verifies; every event is replay-bit-exact.*

Federation cosignature: pending operator signing host (v26). Witness (sha256 of rendered body): d63cea390204fef660b0b8f04e3bfb9b8377bcd06fe41f15a905dcb517bfae53. This page serves with a substrate-honest pending-signature notice until the operator's Franklin signer cosigns it.