DEVELOPERS
Certified infrastructure-context API, v2.0
Three primitives: Attestation · Regime · Drift Signal
The fastest way to integrate Invarians. Handles authentication, retry, STALE policy, and calibration metadata automatically.
inv_ key before running the examples below.
pip install "invarians[requests]>=0.5.0" # v2.0 client (Attestation + Regime + Drift)
from invarians import InvariansClient
client = InvariansClient(api_key="inv_your_key_here")
# v2.0 panel with full diagnostic (regime + drift signal per metric)
panel = client.get_panel_v2(
chains=["ethereum", "arbitrum"],
bridges=["cctp"],
include="diagnostic", # core | diagnostic | full
)
eth = panel.l1_by_chain("ethereum")
arb = panel.l2_by_chain("arbitrum")
br = panel.bridge_by_id("arbitrum-ethereum/cctp")
# Primitive 2: regime classification (12 codes per chain)
if eth.regime.startswith("S2") or arb.regime.startswith("S2"):
pause_agent_execution() # structural stress detected
# Primitive 3: drift signal (is the regime persisting or reverting?)
if eth.drift.demand > 0.05 and eth.drift.demand_magnitude_delta > 0:
defer_action() # transition imminent, deviation amplifying
# Per-metric inspection (only available in diagnostic mode)
print(eth.demand.tx.shift) # current deviation vs 30d baseline
print(eth.demand.tx.is_drifting_away) # True if deviation amplifying
print(eth.demand.tx.is_reverting) # True if returning to nominal
# Primitive 1: HMAC integrity
ok = client.verify_panel_v2(
panel_dict, panel.signed_execution_context.signature
)
For raw HTTP access (JavaScript, curl, other languages), see sections below.
https://api.invarians.comAuth:
Authorization: Bearer inv_your_api_keyEndpoint 1, Panel:
GET /v2/panelReturns the full state of every tracked L1, L2 and bridge in one signed response. Optional filters:
?chains=ethereum,arbitrum · ?bridges=ccip,cctp.Endpoint 2, Verify:
POST /v2/verifyVerifies the HMAC signature over a panel payload. Body:
{"payload": <panel without signed_execution_context>, "signature": "hmac-sha256:..."}.Endpoint 3, CCTP attestation retrieval:
GET /v2/cctp/attestation/{message_hash}Returns the per-message Circle ECDSA signature (secp256k1, 65 bytes) for a CCTP message, captured by the Invarians CCTP collector. The signature is independently verifiable against Circle's published attester public key. Use this for audit-trail or independent verification of any CCTP route signal exposed in the panel.
Endpoint 4, CCIP message retrieval:
GET /v2/ccip/message/{message_id}Returns the per-message CCIP row for a bytes32 messageId: source send metadata (sender, receiver, sequence_number, nonce, gas_limit, fee_token), and, when executed, destination metadata (dest_tx_hash, dest_block_number, dest_block_timestamp, execution_state). A
status field reads "pending" or "executed". crypto_anchor is null today; will become non-null once DON multi-sig capture is added.Panel coverage: L1
ethereum, polygon, avalanche · L2 arbitrum, base, optimism · 10 Circle CCTP routes (capability_level: per_message_attested, Circle ECDSA per message) · 10 Chainlink CCIP lanes (capability_level: per_message_attested, source-to-execute matched by messageId). Each bridge entry carries unified BS1 / BS2 state, a structured metrics block, and a crypto pointer to the verifiable attestation when applicable.Direction-agnostic: the panel exposes states, not routes. Your agent composes
from/to client-side by picking the relevant L1/L2/bridge entries.
# Python — raw HTTP, v2.0 panel with diagnostic mode
import requests
API_KEY = "inv_your_api_key"
BASE_URL = "https://api.invarians.com"
def get_panel(chains=None, bridges=None, include="core") -> dict:
params = {"include": include}
if chains: params["chains"] = ",".join(chains)
if bridges: params["bridges"] = ",".join(bridges)
r = requests.get(
f"{BASE_URL}/v2/panel",
headers={"Authorization": f"Bearer {API_KEY}"},
params=params,
)
r.raise_for_status()
return r.json()
panel = get_panel(chains=["ethereum"], bridges=["cctp"], include="diagnostic")
print(panel["version"]) # "2.0.0"
print(panel["oracle_status"]) # "OK" | "DEGRADED"
print(panel["panel"]["l1"][0]["regime"]) # 12 possible codes per chain
print(panel["panel"]["l1"][0]["drift"]["demand"]) # composite drift magnitude
print(panel["panel"]["l1"][0]["drift"]["demand_magnitude_delta"]) # > 0 deviation amplifies, < 0 reverts
print(panel["signed_execution_context"]["signature"]) # "hmac-sha256:..."
// JavaScript / Node — v2.0 panel
const getPanel = async (params = {}) => {
const qs = new URLSearchParams(params).toString();
const res = await fetch(
`https://api.invarians.com/v2/panel?${qs}`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
return res.json();
};
const panel = await getPanel({
chains: "ethereum,arbitrum",
bridges: "cctp",
include: "diagnostic",
});
console.log(panel.version); // "2.0.0"
console.log(panel.panel.l1[0].regime); // "S1D1" | "S1D2+" | ... (12 codes)
console.log(panel.panel.l1[0].demand.tx.shift); // per-metric deviation magnitude
console.log(panel.panel.l1[0].demand.tx.shift_magnitude_delta); // per-metric trend
console.log(panel.panel.bridges[0].state); // "BS1" | "BS2" | null
# Python — compose a route from the v2.0 panel (agent-side)
panel = get_panel(chains=["ethereum", "arbitrum"], bridges=["cctp"], include="diagnostic")
l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
l2 = next(e for e in panel["panel"]["l2"] if e["chain"] == "arbitrum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")
# Primitive 2: regime per chain (12 codes)
print(l1["regime"], l1["status"]) # e.g. "S1D2+" "OK"
print(l2["regime"], l2["status"]) # e.g. "S1D1" "OK"
# Primitive 3: drift signal (axis-grouped, per-metric blocks)
print(l1["drift"]["structural"],
l1["drift"]["demand"]) # composite drift magnitudes per axis
print(l1["demand"]["sigma"]["shift"]) # per-metric current deviation
print(l1["demand"]["sigma"]["shift_magnitude_delta"]) # trend: amplifying or reverting
# Bridge state
print(br["state"], br["calibrated"], br["status"]) # e.g. "BS1" True "OK"
The default panel returns every tracked chain and bridge (~25 entries, ~30 KB JSON in diagnostic mode). Most agents only need a small subset. Always filter your queries.
chains=ethereum,arbitrum— filter to a subset of L1/L2 chainsbridges=cctp— filter bridge types (ccip,cctp)include=core | diagnostic | full— payload tier (defaultcore)
# Minimal panel for a single L1+L2 pair, CCTP route only
GET /v2/panel?chains=ethereum,arbitrum&bridges=cctp&include=core
# → ~2 KB, just regime + status + drift composite
# Drift-aware decision (full trend signal)
GET /v2/panel?chains=ethereum&bridges=cctp&include=diagnostic
# → ~3 KB, with shift + shift_delta + shift_magnitude_delta per metric
# Research / debugging (raw EMA baselines included)
GET /v2/panel?chains=ethereum&include=full
# → ~5 KB, with baseline_short + baseline_long
# Multi-bridge route comparison (CCIP + CCTP)
GET /v2/panel?chains=ethereum,arbitrum&bridges=ccip,cctp&include=core
# → ~6 KB
| Query | Approx size | Use case |
|---|---|---|
?chains=ethereum&bridges=cctp&include=core |
~1.5 KB | Most common: regime + status + drift, single chain pair |
?chains=ethereum&include=diagnostic |
~3 KB | Stateful agent reading shifts directly |
?include=core (no chain filter) |
~5 KB | Multi-chain dashboard, compact payload |
?include=diagnostic (no filter) |
~30 KB | Full panel dump (avoid in production polling) |
capability_level: per_message_attested). CCTP carries the Circle ECDSA signature per message (crypto.anchor: circle_ecdsa); CCIP matches source send and destination execute by bytes32 messageId (crypto.anchor: null today, DON multi-sig CommitReport capture is the next step). BS1/BS2 classification is preliminary on the per-message latency baseline.Polling cadence: the L1/L2 entries refresh every ~5 min, bridges every ~10 min. Polling more frequently than once per minute returns the same payload (cached behind
Cache-Control: max-age=60).
Full response for GET /v2/panel. One signed payload with every tracked L1, L2, and bridge, axis-grouped by chain. The agent picks the entries relevant to its planned action.
{
"version": "2.0.0",
"oracle_status": "OK", // "OK" | "DEGRADED"
"issued_at": "2026-04-30T12:30:00Z",
"panel": {
// L1 chains, axis-grouped (structural / demand) per chain
"l1": [
{
"chain": "ethereum",
"regime": "S1D1", // 12 codes possible: S1, S2+, S2- × D1, D2+, D2-, D2±
"status": "OK", // "OK" | "STALE" | "UNAVAILABLE" | "UNCALIBRATED"
"computed_at": "2026-04-30T12:25:00Z",
// Structural axis (S): rhythm, continuity, plus beacon_participation on Ethereum only
"structural": {
"rhythm": {
"ratio": 1.0013, // short EMA (~10h) vs short baseline
"ratio_long": 0.9996, // long EMA (~30d) vs long baseline (diagnostic+)
"shift": 0.0017, // = ratio - ratio_long, current deviation
"shift_delta": 0.0008, // = shift - shift_prev, direction of value movement
"shift_magnitude_delta": 0.0008 // = |shift| - |shift_prev|, deviation grows or shrinks
},
"continuity": {
"ratio": 1.0, "ratio_long": 1.0, "shift": 0.0, "shift_delta": 0.0, "shift_magnitude_delta": 0.0
},
"beacon_participation": { // Ethereum only (Beacon Chain validators)
"ratio": 0.998,
"epoch": 312547,
"shift_available": false // slow EMA stabilizing, ~30d post-launch
}
},
// Demand axis (D): sigma, size, tx (echo of activity on confirmed blocks)
"demand": {
"sigma": { "ratio": 1.003, "ratio_long": 0.99, "shift": 0.013, "shift_delta": 0.005, "shift_magnitude_delta": 0.005 },
"size": { "ratio": 1.0095, "ratio_long": 1.05, "shift": -0.04, "shift_delta": -0.012, "shift_magnitude_delta": 0.012 },
"tx": { "ratio": 1.2037, "ratio_long": 1.18, "shift": 0.024, "shift_delta": 0.018, "shift_magnitude_delta": 0.018 }
},
// Composite drift per axis (max |shift| over classifying observables, with delta + magnitude_delta)
"drift": {
"structural": 0.0017,
"structural_delta": 0.0008,
"structural_magnitude_delta": 0.0008,
"demand": 0.04,
"demand_delta": -0.012,
"demand_magnitude_delta": 0.018
}
}
],
// L2 rollups, axis-grouped. structural also includes sequencer_publish_latency.
// demand has 5 observables on L2 (sigma + size + tx + complexity + gas_complexity).
"l2": [
{
"chain": "arbitrum",
"regime": "S1D1",
"status": "OK",
"computed_at": "2026-04-30T12:25:00Z",
"structural": {
"rhythm": { "ratio": 1.001, "ratio_long": 0.992, "shift": 0.009, "shift_delta": 0.001, "shift_magnitude_delta": 0.001 },
"continuity": { "ratio": 1.0, "ratio_long": 1.006, "shift": -0.006, "shift_delta": 0.0, "shift_magnitude_delta": 0.0 },
"sequencer_publish_latency": { // L2 third structural observable
"ratio": null,
"seconds": 11.4, // raw delay between L2 batches inscribed on L1
"shift_available": false // slow EMA pending
}
},
"demand": {
"sigma": { "ratio": 1.0, "ratio_long": 1.0, "shift": 0.0, "shift_delta": 0.0, "shift_magnitude_delta": 0.0 },
"size": { "ratio": 0.94, "ratio_long": 0.97, "shift": -0.03, "shift_delta": -0.005, "shift_magnitude_delta": 0.005 },
"tx": { "ratio": 0.91, "ratio_long": 1.01, "shift": -0.10, "shift_delta": -0.02, "shift_magnitude_delta": 0.02 },
"complexity": { "ratio": 1.02, "ratio_long": 1.0, "shift": 0.02, "shift_delta": 0.005, "shift_magnitude_delta": 0.005 },
"gas_complexity": { "ratio": 1.01, "ratio_long": 1.0, "shift": 0.01, "shift_delta": 0.002, "shift_magnitude_delta": 0.002 }
},
"drift": {
"structural": 0.009, "structural_delta": 0.001, "structural_magnitude_delta": 0.001,
"demand": 0.10, "demand_delta": -0.02, "demand_magnitude_delta": 0.02
}
}
// + base, optimism (same shape)
],
// Bridges. Unified state codes BS1/BS2 across all protocols (CCIP, CCTP, future fast bridges); type field distinguishes the protocol.
"bridges": [
{
"id": "arbitrum-ethereum/cctp",
"endpoints": ["ethereum", "arbitrum"],
"type": "cctp",
"state": "BS1",
"calibrated": true,
"status": "OK",
"observed_at": "2026-05-04T12:28:00Z",
"window": "1h",
"circle_api_status": "OK"
}
// + base-ethereum/cctp, optimism-ethereum/cctp, plus CCIP entries with calibrated:false (raw observability until throughput sustained)
]
},
"coverage": {
"l1_chains": ["ethereum"],
"l2_chains": ["arbitrum", "base", "optimism"],
"bridges_ccip": 10,
"bridges_ccip": 10,
"bridges_cctp": 10,
"include_mode": "diagnostic", // echoes the ?include= query param
"methodology_url": "https://invarians.com/methodology"
},
// HMAC-SHA256 over canonical JSON of everything above. anchor populated in v2.x with the on-chain Merkle anchor.
"signed_execution_context": {
"payload_hash": "0x84f549ab8c50cdf609a2a84bae3d23d37fc1496de23acddaebe4d78c6080b268",
"signature": "hmac-sha256:8ad60a8c399a1f779056fca4005841aa9257b9cf721da49a4a68c7173ac76a06",
"key_id": "invarians-v1",
"anchor": null
}
}
?include= param :
core(default) : each metric exposes onlyratio(+epoch/secondsfor beacon/sequencer)diagnostic: addsratio_long,shift,shift_delta,shift_magnitude_deltaper metricfull: adds raw EMA baselines (baseline_short,baseline_long) for research and external calibration
The
drift composite is always exposed regardless of mode (lightweight summary of the trend signal per axis).Per-item
status: "OK" fresh and calibrated · "STALE" no fresh observation in the item's window · "UNAVAILABLE" no data at all · "UNCALIBRATED" raw signals exposed, no regime classification yet (chains pending July 2026 for SOL/AVAX, bridges CCIP/CCTP pending P3).Global
oracle_status: "OK" if every item is OK or UNCALIBRATED, else "DEGRADED". UNCALIBRATED is not an error, it is a known in-progress state.The
shift_available: false flag appears on metrics whose slow EMA is still stabilizing upstream (Ethereum beacon_participation and L2 sequencer_publish_latency during the v2.0 launch period). The flag flips to true automatically once the long EMA is operational, no API version change required.
The four base codes below classify chains against an upper threshold on each axis. On chains with calibrated lower bounds (since 2026-04-29: ETH, POL on L1; ARB, BASE, OP on L2), the API also emits signed regime codes with explicit direction suffixes (+, -, ±). The four base codes remain valid as aliases for chains without lower bounds yet calibrated. See foundations §03b for the full extended grid.
| State | Condition | Typical infrastructure context |
|---|---|---|
| S1D1 | Structural and demand layers within historical baseline | Both layers within historical norms. Standard operating conditions. |
| S1D2 | Demand elevated beyond calibrated historical percentiles | Elevated demand relative to historical baseline. |
| S2D2 | Structural stress AND demand elevated simultaneously | Both layers above historical baseline simultaneously. Structural strain coinciding with elevated demand. |
| S2D1 | Structural stress WITHOUT elevated demand | Structural divergence without corresponding activity conditions. Invisible to fee-based monitors. |
On chains with calibrated lower bounds, each axis carries an explicit direction suffix. Total: 12 codes on L1 and 12 codes on L2 (since v2.0, L2 also reaches S2 thanks to sequencer_publish_latency). Parse with a regex on the regime field, branch on the suffix.
| Code | Axis · Direction | Meaning |
|---|---|---|
D1 |
demand · nominal | All demand ratios within their two-sided bounds. |
D2+ |
demand · high | At least one ratio above its high threshold, none below. Legacy D2 alias. |
D2- |
demand · low (starvation) | At least one ratio below its low threshold, none above. Sequencer slowing user-side, censorship of small transfers, mempool gel. |
D2± |
demand · composition split | At least one ratio above AND at least one below. Composition asymmetry, agentic concentration signature (rsETH 2026-04-18 cascade was D2±). |
S1 |
structure · nominal | Rhythm and continuity within their two-sided bounds. |
S2+ |
structure · slowed | Rhythm slows abnormally, blocks taking longer than baseline. Legacy S2 alias. |
S2- |
structure · accelerated | Rhythm accelerates abnormally, blocks coming faster than baseline. Disabled on L2 rollups (rhythm too tight to be informative). |
Active chains as of 2026-04-29: ETH, POL on L1 (event-based + statistical lows); ARB, BASE, OP on L2 (multi-dim demand statistical). SOL and AVAX remain on the legacy 4-state grid until July 2026 calibration. Until per-chain activation, the API emits the four-state grid as today, additive and non-breaking.
"OK" → all panel items within their freshness window · "DEGRADED" → at least one L1/L2/bridge item is STALE or UNAVAILABLE. UNCALIBRATED items do not trigger DEGRADED.Per-item freshness: each entry in
panel.l1[] · panel.l2[] · panel.bridges[] carries its own status and timestamp (computed_at for L1/L2, 1h window; observed_at for bridges, 10m window). Always inspect the specific items you consume, not only oracle_status.
The Regime classification answers what is the substrate doing now. The Drift Signal answers is it safe to act in the next 30 minutes. Each classifying observable is exposed as a self-contained MetricBlock with the current ratio, the long-term reference, the deviation magnitude, and two trend signals.
{
"ratio": 1.094, // short EMA (~10h) vs short baseline
"ratio_long": 0.991, // long EMA (~30d) vs long baseline
"shift": 0.103, // = ratio - ratio_long, deviation magnitude (signed)
"shift_delta": 0.016, // = shift_now - shift_prev, raw direction of value movement
"shift_magnitude_delta": 0.016 // = |shift_now| - |shift_prev|, deviation growing or shrinking
}
shift_delta tells you the direction the value moved between cycles (positive = up, negative = down).shift_magnitude_delta tells you whether the deviation is amplifying or shrinking (positive = regime persists or worsens, negative = reverting toward nominal).The two values typically agree, except when shift crosses zero between cycles (regime transition through D1/S1 nominal).
Reading rules
| Situation | shift |
shift_magnitude_delta |
Lecture agent |
|---|---|---|---|
| Demand surge accelerating | +0.15 |
+0.07 |
D2+ persists or worsens, prepare for sustained gas competition |
| Demand surge plateau | +0.15 |
≈ 0 |
D2+ stable, regime sticky |
| Demand surge reverting | +0.15 |
−0.07 |
D2+ ending, return to D1 within hours likely |
| Starvation worsening | −0.10 |
+0.05 |
D2- deepens, depressed demand persists |
| Starvation recovering | −0.10 |
−0.05 |
D2- ending, return to D1 likely |
Each chain entry exposes a composite drift summary computed server-side:
"drift": {
"structural": 0.0017, // max |shift| over structural observables
"structural_delta": 0.0008,
"structural_magnitude_delta": 0.0008,
"demand": 0.103, // max |shift| over demand observables
"demand_delta": 0.016,
"demand_magnitude_delta": 0.016
}
Magnitude drift.X | Reading |
|---|---|
< 0.005 | Substrate stable on this axis, regime expected to persist |
0.005 - 0.02 | Mild drift, transition within hours possible |
0.02 - 0.05 | Active drift, vigilance recommended |
> 0.05 | Imminent transition probable (~10–30 min horizon) |
"shift_available": false and the trend fields are absent. Once stabilized, the flag flips to true automatically without API version change.The thresholds above are initial heuristics and will be tightened based on empirical validation against historical regime transitions (research follow-up post-v2.0).
| Field | Description | Notes |
|---|---|---|
| panel.l1[].regime · panel.l2[].regime | Current certified regime per chain | 4 base codes (S1D1 / S1D2 / S2D2 / S2D1) plus 11 signed codes (S1D2+, S1D2-, S1D2±, S2+D1, S2-D1, S2+D2±, S2-D2±, etc.) on chains with calibrated lower bounds since 2026-04-29. Parse with a regex on the structure prefix (S1, S2+, S2-) and demand prefix (D1, D2+, D2-, D2±). |
| panel.bridges[].state | Current certified state per bridge | Unified BS1 / BS2 across all variable-latency bridges (CCIP, CCTP, future fast bridges). The type field on the bridge entry distinguishes the protocol. CCTP routes carry preliminary BS1/BS2 since 2026-05-04. CCIP lanes remain state:null with calibrated:false until sustained throughput emerges. |
| oracle_status | Global panel status | OK / DEGRADED |
| panel.{l1,l2,bridges}[].status | Per-item freshness/availability | OK / STALE / UNAVAILABLE / UNCALIBRATED |
| panel.l1[].computed_at · panel.l2[].computed_at | Timestamp of last L1/L2 computation (~1h window) | ISO 8601 UTC |
| panel.bridges[].observed_at | Timestamp of last bridge observation (~10m window) | ISO 8601 UTC |
| panel.l1[].structural · panel.l2[].structural | Axis object grouping the structural observables (rhythm, continuity, plus beacon_participation on ETH or sequencer_publish_latency on L2) |
Each child is a MetricBlock |
| panel.l1[].demand · panel.l2[].demand | Axis object grouping the demand observables (sigma, size, tx, plus complexity and gas_complexity on L2) |
Each child is a MetricBlock |
| {axis}.{observable}.ratio | Short-term EMA ratio. Calibrated index, ≈1.0 nominal | core mode and above |
| {axis}.{observable}.ratio_long | Long-term EMA ratio (~30-day baseline) | diagnostic mode and above |
| {axis}.{observable}.shift | ratio - ratio_long. Current deviation magnitude, signed |
diagnostic mode and above |
| {axis}.{observable}.shift_delta | shift_now - shift_prev. Raw direction of value movement between cycles |
diagnostic mode and above |
| {axis}.{observable}.shift_magnitude_delta | |shift_now| - |shift_prev|. Whether the deviation is growing or shrinking |
diagnostic mode and above |
| {axis}.{observable}.shift_available | false during the pre-shift period when the long EMA is not yet stabilized for a newly added observable |
diagnostic mode and above |
| {axis}.{observable}.baseline_short · baseline_long | Raw EMA values | full mode only |
| structural.beacon_participation.epoch | Current Beacon Chain epoch | Ethereum only, all modes |
| structural.sequencer_publish_latency.seconds | Raw observed sequencer publish latency in seconds | L2 only, all modes |
| panel.l1[].drift · panel.l2[].drift | Composite drift per axis: structural, structural_delta, structural_magnitude_delta, demand, demand_delta, demand_magnitude_delta |
core mode and above. See Drift Signal section for thresholds |
| signed_execution_context | HMAC envelope: payload_hash, signature, key_id, anchor |
Same on every panel response |
| version | Panel API version, e.g. "2.0.0" |
Negotiable via Accept-Version header |
| issued_at | When this panel snapshot was assembled | ISO 8601 UTC |
# Python, single-chain evaluation from the panel
def evaluate_chain(panel: dict, chain: str) -> str:
# First: global panel freshness
if panel["oracle_status"] != "OK":
return "PANEL_DEGRADED"
l1 = next((e for e in panel["panel"]["l1"] if e["chain"] == chain), None)
l2 = next((e for e in panel["panel"]["l2"] if e["chain"] == chain), None)
entry = l1 or l2
if entry is None or entry["status"] != "OK":
return "CHAIN_UNAVAILABLE"
return entry["regime"] # "S1D1" | "S1D2" | "S2D2" | "S2D1" | signed code (S1D2+, S1D2-, S1D2±, S2+D1, S2-D2±, ...)
import re
SIGNED_PATTERN = re.compile(r"^(S1|S2[+-])(D1|D2[+\-±])$")
def parse_regime(code):
# Returns (structure_axis, demand_axis) where each is the prefix-stripped direction.
# Legacy 4-state codes are mapped to their topological equivalent.
legacy = {"S1D1": ("S1", "D1"),
"S1D2": ("S1", "D2+"),
"S2D1": ("S2+", "D1"),
"S2D2": ("S2+", "D2+")}
if code in legacy: return legacy[code]
m = SIGNED_PATTERN.match(code)
return (m.group(1), m.group(2)) if m else (None, None)
# Your agent decides, Invarians only reads the substrate
panel = client.get_panel_v2(chains=["ethereum"], include="core")
state_code = evaluate_chain(panel, "ethereum")
struct, demand = parse_regime(state_code)
if struct == "S1" and demand == "D1":
handle_nominal()
elif demand == "D2+":
handle_demand_high() # elevated demand, organic surge or coordinated buying
elif demand == "D2-":
handle_demand_starvation() # depressed demand, sequencer slowing or censorship of small transfers
elif demand == "D2±":
handle_composition_split() # size/tx asymmetry, agentic concentration signature
elif struct == "S2+":
handle_structural_slowdown() # rhythm degraded, sequencer or validator-side
elif struct == "S2-":
handle_structural_acceleration() # rhythm faster than baseline, infra change or client optimisation
else:
fallback_behavior() # panel degraded, chain unavailable, or regime not yet calibrated
// JavaScript, multi-chain check from a single panel fetch
const checkAllChains = async () => {
const panel = await getPanel();
const all = [...panel.panel.l1, ...panel.panel.l2];
return all.reduce((acc, entry) => {
acc[entry.chain] = entry.status === "OK"
? { regime: entry.regime, status: entry.status }
: { regime: null, status: entry.status }; // STALE | UNAVAILABLE | UNCALIBRATED
return acc;
}, {});
};
# Python, cross-chain agent pattern, panel API
panel = get_panel()
if panel["oracle_status"] == "DEGRADED":
fallback()
l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")
# Your agent applies its own policy
if l1["regime"].startswith("S1") and br["state"] == "BS1":
proceed()
elif br["status"] == "UNCALIBRATED":
# Bridge classification deferred (CCIP), raw signal available in br["last_sequence_advance_seconds"]
proceed_with_observation_only()
else:
defer_execution()
Invarians produces three distinct primitives per query.
Primitive 1, Attestation: every panel response carries a HMAC-SHA256 envelope (payload_hash + signature + key_id + anchor) covering the full payload. Independently verifiable via POST /v2/verify. This is what makes the certified execution state provable, not just observable.
The execution policy is not prescribed. The agent reads the certified state and applies its own thresholds, risk tolerance, and fallback logic. Invarians certifies the infrastructure state, the decision belongs to the agent.
{
"action": "execution_deferred",
"reason": "L1 structural stress detected, ethereum regime S2D1",
"invarians_proof": {
// Excerpt of the panel entries relevant to the decision
"l1": { "chain": "ethereum", "regime": "S2D1", "status": "OK" },
"l2": { "chain": "arbitrum", "regime": "S1D1", "status": "OK" },
"bridge": { "id": "arbitrum-ethereum/cctp", "state": "BS1", "calibrated": true },
"signed_execution_context": {
"payload_hash": "0x714b64...",
"signature": "hmac-sha256:9b5b1d...",
"key_id": "invarians-v1",
"anchor": null
},
"issued_at": "2026-04-20T10:00:00Z"
}
}
Invarians tracks blockchain behavior under AI agent load and defines safe execution windows. Current monitoring scope:
| Layer | Coverage |
|---|---|
| L1 | Ethereum, Solana, Polygon, Avalanche |
| L2 (Ethereum-anchored) | Arbitrum, Base, Optimism |
| Chainlink CCIP lanes | Bidirectional, L1 ↔ L1 and L1 ↔ L2 |
| Circle CCTP routes | Bidirectional, L1 ↔ L1 and L1 ↔ L2 |
Bridge classification scope is variable-latency surfaces only (Chainlink CCIP, Circle CCTP). Institutional RWA settlement moves stablecoins via CCTP and tokens via CCIP, both bidirectional.
| Chain | Structural axis | Demand axis | 3rd structural observable | Calibration |
|---|---|---|---|---|
| Ethereum | rhythm + continuity | sigma (gas) + size + tx | beacon_participation (epoch + ratio live, threshold pending) | 12 signed codes (S2- on beacon pending) |
| Polygon | rhythm + continuity | sigma (gas) + size + tx | — | 12 signed codes since 2026-04-29 |
| Avalanche | rhythm + continuity | sigma (gas) + size + tx | — | 4 base codes (12 signed scheduled July 2026) |
| Solana | rhythm + continuity | size + tx (user only) | — | UNCALIBRATED (calibration scheduled July 2026) |
| L2 ROLLUPS, 3rd structural axis = sequencer_publish_latency (batch gap on L1) | ||||
| Arbitrum | rhythm + continuity | sigma + size + tx + complexity + gas_complexity | sequencer_publish_latency ✓ calibrated (600 s halt threshold) | 12 signed codes since 2026-05-01 |
| Base | rhythm + continuity | sigma + size + tx + complexity + gas_complexity | sequencer_publish_latency ✓ calibrated (480 s halt threshold) | 12 signed codes since 2026-05-01 |
| Optimism | rhythm + continuity | sigma + size + tx + complexity + gas_complexity | sequencer_publish_latency ✓ calibrated (1800 s halt threshold) | 12 signed codes since 2026-05-01 |
Drift Signal (Primitive 3) shift per metric is computed against the long-term EMA (~30-day baseline). For newly added structural observables (beacon_participation on ETH, sequencer_publish_latency on L2), shift_available: false until the slow EMA stabilizes (~30 days post-launch). The regime trigger uses raw ratio or seconds against calibrated bounds during this period.
401 Unauthorized , invalid or missing API key
403 Forbidden , key does not have access to this chain
404 Not Found , no signal available for this chain
429 Too Many Requests, rate limit exceeded
500 Server Error , internal error, retry after 60s
"hmac-sha256:...")
under signed_execution_context.signature. The signature covers a canonical JSON
serialization of the entire payload with signed_execution_context stripped.
Any agent can independently verify that a panel was issued by Invarians and has not been
altered in transit.
Use the /verify endpoint, no additional secret required, only your API key.
# POST /v2/verify
# Returns: { "valid": true|false, "key_id": "invarians-v1", "version": "2.0.0" }
import requests
# 1. Fetch a panel
r = requests.get(
"https://api.invarians.com/v2/panel",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
panel = r.json()
sig = panel["signed_execution_context"]["signature"]
# 2. Verify the signature independently
# Send the panel with signed_execution_context stripped + the signature.
payload = {k: v for k, v in panel.items() if k != "signed_execution_context"}
verify = requests.post(
"https://api.invarians.com/v2/verify",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"payload": payload, "signature": sig}
)
print(verify.json())
# → { "valid": true, "key_id": "invarians-v1", "version": "2.0.0" }
"valid": true guaranteesThe panel was produced by Invarians infrastructure and has not been modified in transit. It does not guarantee the freshness of the underlying chains. Always check per-item
status ("OK" / "STALE" / "UNAVAILABLE") and the global
oracle_status ("OK" / "DEGRADED") before acting on a signal.Key rotation & roadmap
key_id identifies the current HMAC key (invarians-v1). V1.1+ populates
signed_execution_context.anchor with an on-chain Merkle anchor on Arbitrum,
adding a second independent verification path.
status and a per-item timestamp
(computed_at for L1/L2, observed_at for bridges). A global
oracle_status summarizes the panel. A signal is marked STALE
when its freshest observation is older than the item's window.Important: STALE does not mean the regime has changed, it means the current regime is unknown. Structural regimes (L1/L2) change slowly (~3h). A signal that became STALE 30 minutes ago is likely still valid, but carries uncertainty.
| Tier | Panel field | Window | STALE threshold | Why different |
|---|---|---|---|---|
| Tier 1, L1 / L2 | panel.l1[].status · panel.l2[].status |
window: "1h" |
now - computed_at > 3600s |
Regimes are integrals, change slowly (~3h) |
| Tier 2, Bridge | panel.bridges[].status |
window: "10m" |
now - observed_at > 900s |
Bridge is a liveness snapshot, changes instantly |
| Item age | status |
Confidence | Recommended behavior |
|---|---|---|---|
| L1/L2 < 3600s · Bridge < 900s | OK (or UNCALIBRATED for bridges) |
Full | Execute as per regime/state, signal is fresh |
| L1/L2 3600s – 7200s | STALE |
Degraded | Use last known regime with caution. Regime likely unchanged (structural change takes ~3h). Log the staleness. Avoid initiating new high-value positions. |
| L1/L2 > 7200s · Bridge > 900s | STALE or UNAVAILABLE |
Unknown | State unknown. For time-sensitive or low-value operations, proceed at agent's discretion with explicit staleness flag in the decision log. |
status is STALE or UNAVAILABLE, treat cross-chain
execution as unknown infrastructure state and defer.When
status: "UNCALIBRATED", state is null but the per-message
signal is exposed for observation (metrics.execute_latency_p90_s, message counts, etc.).
This applies when sustained throughput has not yet allowed P97 baseline calibration on that surface.
# Complete STALE handling, Python, panel API
from datetime import datetime, timezone
import requests
r = requests.get(
"https://api.invarians.com/v2/panel",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
panel = r.json()
def age_seconds(iso: str) -> float:
return (datetime.now(timezone.utc) - datetime.fromisoformat(iso.replace("Z", "+00:00"))).total_seconds()
l1 = next(e for e in panel["panel"]["l1"] if e["chain"] == "ethereum")
br = next(b for b in panel["panel"]["bridges"] if b["id"] == "arbitrum-ethereum/cctp")
l1_age = age_seconds(l1["computed_at"])
bridge_age = age_seconds(br["observed_at"]) if br["observed_at"] else 9999
if l1["status"] == "OK" and br["status"] in ("OK", "UNCALIBRATED"):
# Fresh. Apply your own policy.
if l1["regime"].startswith("S1") and br["state"] == "BS1":
proceed()
elif br["status"] == "UNCALIBRATED":
proceed_with_observation_only()
else:
defer_execution()
elif l1["status"] == "STALE" and l1_age <= 7200:
log_warning(f"L1 stale ({int(l1_age)}s), regime likely unchanged: {l1['regime']}")
else:
log_warning(f"Infrastructure state unknown, deferring, global: {panel['oracle_status']}")
# Always log the full item state in the decision record
decision_log = {
"oracle_status": panel["oracle_status"],
"l1": {"chain": l1["chain"], "regime": l1["regime"], "status": l1["status"], "computed_at": l1["computed_at"]},
"bridge": {"id": br["id"], "state": br["state"], "calibrated": br["calibrated"], "status": br["status"]},
"issued_at": panel["issued_at"],
"payload_hash": panel["signed_execution_context"]["payload_hash"],
}
/attestation/panel, /attestation/verify) remain live for a 60-day grace period and will sunset on 2026-06-30. Migrate before that date.
| v1.1.0 | v2.0 | Status |
|---|---|---|
GET /attestation/panel |
GET /v2/panel |
Live until 2026-06-30, then 410 Gone |
POST /attestation/verify |
POST /v2/verify |
Live until 2026-06-30, then 410 Gone |
| v1.1.0 (flat) | v2.0 (axis-grouped) | Reason |
|---|---|---|
panel.l1[].rhythm_ratio |
panel.l1[].structural.rhythm.ratio |
Axis-grouped layout (structural / demand) |
panel.l1[].sigma_ratio |
panel.l1[].demand.sigma.ratio |
Axis-grouped layout |
panel.l1[].structural_slow.rhythm_ratio_slow |
panel.l1[].structural.rhythm.ratio_long |
Naming consistency (long > slow) |
panel.l1[].shifts.rhythm_shift |
panel.l1[].structural.rhythm.shift |
Co-located with the metric it derives from |
| (no demand shifts exposed) | panel.l1[].demand.{sigma,size,tx}.shift |
Symmetric exposure on all classifying observables |
| (no trend signal) | shift_delta + shift_magnitude_delta per metric |
Primitive 3, drift trend (raw + magnitude) |
| (no L2 sequencer obs in regime) | panel.l2[].structural.sequencer_publish_latency |
3rd structural axis on L2 (sequencer halt detection) |
| (no Ethereum beacon in regime) | panel.l1[].structural.beacon_participation (ETH only) |
Beacon Chain participation enters S axis on Ethereum |
panel.l1[].execution_profile.index_a/b/c |
Removed, replaced by demand.{sigma,size,tx}.ratio |
Named fields are clearer than alphabetical indices |
(no drift composite) |
panel.l1[].drift.{structural,demand} + delta + magnitude_delta |
Primitive 3 summary per axis |
# v1.1.0 client (deprecated)
from invarians import InvariansClient
client = InvariansClient(api_key="inv_...")
panel = client.get_panel()
eth = panel.l1_by_chain("ethereum")
print(eth.regime, eth.shifts.rhythm_shift)
# v2.0 client (recommended)
panel = client.get_panel_v2(
chains=["ethereum"],
bridges=["cctp"],
include="diagnostic",
)
eth = panel.l1_by_chain("ethereum")
print(eth.regime, eth.structural.rhythm.shift)
print(eth.drift.demand) # NEW: composite drift magnitude
print(eth.demand.tx.is_drifting_away) # NEW: trend helper
get_panel() still works against the v1.1.0 endpoint until sunset. After 2026-06-30, only get_panel_v2() will resolve.
- Upgrade SDK :
pip install --upgrade "invarians>=0.5.0" - Replace
client.get_panel(...)withclient.get_panel_v2(chains=..., bridges=..., include=...) - Update field accessors :
eth.structural.rhythm_ratio→eth.structural.rhythm.ratio,eth.shifts.rhythm_shift→eth.structural.rhythm.shift - If your agent uses
execution_profile.index_a/b/c, switch todemand.sigma.ratio/demand.size.ratio/demand.tx.ratio - If your agent uses long-term EMA:
structural_slow.rhythm_ratio_slow→structural.rhythm.ratio_long - Always specify
chains=andbridges=filters (see Filters & Best Practices) — default behavior is to return all entries - Replace
verify_panel()withverify_panel_v2() - Optional: consume the new
driftcomposite andshift_magnitude_deltaper-metric trends to make fitness-for-action decisions (see Primitive 3)
v2.0.0 (panel API), exposed as "version": "2.0.0" on every panel response. v1.1.0 endpoints (/attestation/panel, /attestation/verify) remain live until 2026-06-30 then return 410 Gone.Breaking changes are announced a minimum of 30 days in advance via a
Deprecation header in API responses and an entry in the changelog below.Non-breaking changes (new optional fields, new chain coverage, new bridge types) are additive and require no notice.
| Change type | Classification | Example |
|---|---|---|
| Field renamed or removed | Breaking | structural.rhythm_ratio (flat) → structural.rhythm.ratio (axis-grouped, v1.1.0 → v2.0) |
| Field semantics changed | Breaking | shift redefined relative to a different baseline |
| Endpoint URL changed | Breaking | /attestation/panel → /v2/panel (v1.1.0 → v2.0) |
| New optional field added | Non-breaking | structural.beacon_participation added for ETH only |
| New chain added | Non-breaking | New L2 endpoint deployed alongside existing ones |
Deprecation: date="2026-06-01"Sunset: date="2026-06-01"These headers appear at least 30 days before the change takes effect. Parse them in your integration to detect upcoming breaking changes programmatically.
Security-critical fixes may be applied immediately, in that case the header is present with the shortest possible notice and the changelog entry is dated retroactively.
| Version | Date | Changes |
|---|---|---|
| v2.0.0 | April 30, 2026 |
Three Primitives, axis-grouped payload (breaking) ·
Payload restructured by axis (structural · demand) per chain, with a MetricBlock per observable carrying ratio, ratio_long, shift, shift_delta, shift_magnitude_delta ·
beacon_participation promoted to a 3rd structural classifying observable on Ethereum (S2- on low-side participation drops) ·
sequencer_publish_latency promoted to a 3rd structural classifying observable on all L2s (S2+ on sequencer halts) ·
Demand axis gains shifts on sigma, size, tx (and complexity · gas_complexity on L2) ·
New composite drift object per axis with magnitude + raw delta + magnitude delta ·
Tiered exposure via ?include=core|diagnostic|full ·
Endpoints: GET /v2/panel, POST /v2/verify. v1.1.0 endpoints remain live until 2026-06-30 then return 410 Gone ·
Python SDK bumped to invarians>=0.5.0 with get_panel_v2(), verify_panel_v2(), MetricBlock dataclass and trend helpers
|
| v1.1.0 | April 29, 2026 |
Long-term EMA + shifts (additive, non-breaking) ·
Per-entry structural_slow object exposing rhythm_ratio_slow and continuity_ratio_slow (~30-day baseline) on L1 and L2 entries ·
Per-entry shifts object exposing rhythm_shift and continuity_shift (delta between short-term ~10h and long-term ~30d EMAs) ·
Calibration thresholds extracted from server-side code into dedicated Postgres tables (l1_thresholds, l2_thresholds) for transparency ·
Old v1.0.0 clients keep working unchanged ·
Python SDK bumped to invarians>=0.3.0 with StructuralSlow and Shifts dataclasses
|
| v1.0.0 | April 2026 |
Panel API (breaking) ·
Directional /attestation/execution-context?from=&to=, single-chain /attestation/{chain}, and L2 /attestation/l2/{chain} endpoints return 410 Gone ·
Replaced by GET /attestation/panel returning the full L1/L2/bridge panel in one signed payload ·
Direction is composed client-side by the agent (from / to no longer accepted) ·
proof_of_execution_context renamed to signed_execution_context (payload_hash + signature + key_id + anchor: null) ·
Per-item status (OK / STALE / UNAVAILABLE / UNCALIBRATED) and per-item timestamps (computed_at for L1/L2, observed_at for bridges) ·
Global oracle_status (OK / DEGRADED) ·
Canonical bridge IDs {chainA}-{chainB}/{type} ·
coverage block with per-type counts and methodology_url ·
Cache-Control: public, max-age=60 and ETag ·
Python SDK bumped to invarians>=0.2.0 with get_panel(), verify_panel(), and helpers
|
| v2 | March 2026 |
demand field replaced by execution_profile with named indices index_a–index_h ·
L2 endpoints added (/attestation/l2/{chain}) ·
calibration.version field introduced ·
divergence_index (F3) added
|
| v1 | January 2026 |
Initial release, structural, state, proof_of_execution_context · execution_window (removed in v2)
|
The instrument improves over time through closed-loop feedback: when agents log their decisions and outcomes (transaction success/failure, gas usage, timing), this data helps calibrate classification thresholds against real-world behavior.
During the beta period, feedback integration is available to select partners. If you are deploying an agent in production on any of the 7 supported chains and want to contribute, contact us to join the feedback program.