---
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.*
d63cea390204fef660b0b8f04e3bfb9b8377bcd06fe41f15a905dcb517bfae53.
This page serves with a substrate-honest pending-signature notice until the operator's Franklin signer cosigns it.