Metadata-Version: 2.4
Name: invarians
Version: 0.9.1
Summary: Python SDK for the Invarians panel API v2.0: three primitives (Attestation, Regime, Drift Signal) plus per-message CCTP (Circle ECDSA) and CCIP (source-dest matched by messageId) retrieval
Project-URL: Homepage, https://invarians.com
Project-URL: API-docs, https://invarians.com/developers.html
Project-URL: Source, https://github.com/InvariansLabs/invarians-py
License: MIT
Keywords: arbitrum,attestation,autonomous-agents,blockchain,drift-signal,ethereum,execution-context,infrastructure,regime-classification
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest-httpx; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: responses; extra == 'dev'
Provides-Extra: httpx
Requires-Dist: httpx>=0.24; extra == 'httpx'
Provides-Extra: requests
Requires-Dist: requests>=2.28; extra == 'requests'
Description-Content-Type: text/markdown

# invarians-py

**Cross-chain infrastructure context for autonomous agents. Three primitives in one signed payload: Attestation, Regime, Drift Signal.**

L2 activity no longer shows up in L1 gas fees. Sequencer slowdowns and bridge delays leave no economic trace. Fee monitors stay silent. Invarians detects them.

**v0.9.0 (2026-05-12)**: per-message CCIP capture. `CCIPSendRequested` (source OnRamp) is matched against `ExecutionStateChanged` (destination OffRamp) via the bytes32 `messageId`, deriving real send-to-execute latency per lane per direction. CCIP bridges now carry `capability_level: per_message_attested`, matching the depth previously achieved on CCTP routes in v0.8.0. Per-message rows retrievable via `client.get_ccip_message(message_id)`. `crypto.anchor` for CCIP is `null` today (no per-message cryptographic anchor captured yet).

**v0.8.0 (2026-05-11)**: per-message CCTP attestation retrieval with Circle ECDSA crypto-grounding. Each CCTP message exposes its independently verifiable ECDSA signature from Circle's attester. Bridge entries carry a `capability_level` semantic plus structured `metrics` and `crypto` objects.

Since v0.7.0 (2026-05-04), the SDK targets the production endpoint at `https://api.invarians.com` and exposes the v2.0 panel: a single direction-agnostic payload with axis-grouped metric blocks, drift signals, and 12 signed regime codes per chain. Bridge classification scope is variable-latency surfaces only (Chainlink CCIP, Circle CCTP).

```python
from invarians import InvariansClient

client = InvariansClient(api_key="inv_your_key_here")
panel  = client.get_panel_v2(include="diagnostic")

eth = panel.l1_by_chain("ethereum")
arb = panel.l2_by_chain("arbitrum")
br  = panel.bridge_by_id("arbitrum-ethereum/cctp")

# Regime: 12 signed codes (S1D1, S1D2+, S1D2-, S1D2±, S2+D1, S2-D1, S2+D2+, ...)
if eth.regime and (eth.regime.startswith("S2") or eth.regime.endswith("D2-")):
    pause_agent_execution()

# Drift: per-axis composite trend, plus per-metric shift
if eth.drift.demand_magnitude_delta is not None and eth.drift.demand_magnitude_delta > 0:
    log.info("Demand axis deviation amplifying on ethereum")

print(panel.oracle_status)        # "OK" | "DEGRADED"
print(eth.regime, eth.status)     # e.g. "S1D1" "OK"
print(br.state, br.calibrated)    # e.g. "BS1" True (CCTP preliminary)
```

---

## Install

```bash
pip install invarians[requests]  # default
pip install invarians[httpx]     # async-friendly
```

Requires Python 3.9+. Get an API key at [invarians.com](https://invarians.com).

---

## The three primitives

The v2.0 panel separates three independent concerns. Every panel response carries all three.

### 1. Attestation (HMAC integrity)

Every panel response carries a `signed_execution_context` with `payload_hash`, `signature`, `key_id`, and an optional on-chain `anchor`. Independently verifiable.

```python
panel = client.get_panel_v2()
sec   = panel.signed_execution_context

ok = client.verify_panel_v2(panel_raw_dict, sec.signature)
# True if HMAC matches the canonical JSON of the payload
```

### 2. Regime (12 signed codes per chain)

Per-chain regime is a 2-axis tuple on the SxDx grid. Structure axis: `S1` nominal, `S2+` structural high, `S2-` structural low. Demand axis: `D1` nominal, `D2+` demand high, `D2-` demand low, `D2±` composition split. Twelve codes total per chain on both L1 and L2.

```python
eth = panel.l1_by_chain("ethereum")

if eth.regime == "S2+D1":
    # Structural high stress, demand nominal
    return {"action": "hold", "reason": eth.regime}
elif eth.regime and eth.regime.startswith("S1") and not eth.regime.endswith("D1"):
    # Nominal infrastructure, asymmetric or elevated demand
    proceed_with_caution()
```

### 3. Drift Signal (substrate-only)

Applies to L1 and L2 substrate entries. Bridges, being operational pipelines rather than substrates, expose their fitness-for-action directly via current state, metrics, and crypto pointer — without a drift block.


Each metric block exposes `ratio` (current state vs short EMA), `ratio_long` (current vs long EMA), `shift` (deviation magnitude), `shift_delta` (raw direction), `shift_magnitude_delta` (deviation amplifying or shrinking). Plus a per-axis composite drift on every chain entry.

```python
eth = panel.l1_by_chain("ethereum")

# Per-axis composite drift
print(eth.drift.demand)                     # composite drift magnitude on demand axis
print(eth.drift.demand_magnitude_delta)     # > 0 amplifying, < 0 reverting

# Per-metric shift (diagnostic mode only)
print(eth.demand.tx.shift)                  # per-metric tx-count shift
print(eth.structural.rhythm.shift)          # per-metric rhythm shift
```

Trend reading rule for an agent in an active regime:

| `shift_magnitude_delta` | Interpretation |
|---|---|
| `> 0` | Deviation amplifying. Regime persists or worsens. |
| `< 0` | Deviation shrinking. Regime exit toward nominal likely. |
| `≈ 0` | Regime stable. |

---

## Common patterns

### Hold on structural stress

```python
panel = client.get_panel_v2()
eth   = panel.l1_by_chain("ethereum")

if eth.regime and eth.regime.startswith("S2"):
    # Structural stress, regardless of polarity (S2+ or S2-)
    return {"action": "hold", "reason": eth.regime}
```

### Detect silent slowdown (no fee signal)

S2+D1 and S2-D1 are the codes where infrastructure degrades without any demand signature. Fee monitors stay silent.

```python
eth = client.get_panel_v2().l1_by_chain("ethereum")

if eth.regime in ("S2+D1", "S2-D1"):
    # No gas spike, no price move, but the chain is structurally stressed
    alert_ops(f"Silent stress on ethereum: {eth.regime}")
```

### Certify execution conditions on chain

```python
panel = client.get_panel_v2()
sec   = panel.signed_execution_context

if panel.oracle_status == "OK":
    result = execute_trade(...)
    audit_log.append({
        "tx":            result.hash,
        "panel_version": panel.version,           # "2.0.0"
        "issued_at":     panel.issued_at,
        "payload_hash":  sec.payload_hash,        # "0x{sha256}"
        "signature":     sec.signature,           # "hmac-sha256:{hex}"
        "key_id":        sec.key_id,
        "anchor":        sec.anchor,              # on-chain anchor slot when available
    })
```

### Route around bridge stress

Bridge IDs are canonical: `{chainA}-{chainB}/{type}` with `type ∈ {ccip, cctp}`. Bridge classification scope is variable-latency surfaces only.

```python
panel = client.get_panel_v2(bridges=["ccip", "cctp"])

# CCTP: per-message capture since 2026-05-11
#   capability_level = "per_message_attested", crypto.anchor = "circle_ecdsa"
#   metrics.latency_p90_s / latency_p99_s / success_rate_1h are computed on per-message latencies
br = panel.bridge_by_id("arbitrum-ethereum/cctp")
if br and br.state == "BS2":
    use_fallback_route()
if br and br.metrics and br.metrics.latency_p90_s and br.metrics.latency_p90_s > 1200:
    log.warning(f"{br.id}: attestation P90 {br.metrics.latency_p90_s:.0f}s above 20-min threshold")

# CCIP: capability_level = "per_message_attested" since 2026-05-12
#   metrics.execute_latency_p90_s = source-to-execute P90 derived from messageId matching
#   metrics.sequence_gap and metrics.messages_confirmed_1h derived from per-message data
#   crypto.anchor = null (no per-message cryptographic anchor captured yet)
ccip = panel.bridge_by_id("ethereum-arbitrum/ccip")
if ccip and ccip.state == "BS2":
    use_fallback_route()
if ccip and ccip.metrics and ccip.metrics.execute_latency_p90_s and ccip.metrics.execute_latency_p90_s > 1800:
    log.warning(f"{ccip.id}: send-to-execute P90 {ccip.metrics.execute_latency_p90_s:.0f}s above 30-min threshold")

# RMN cursed override (CCIP only) — absolute binary, lane is frozen
if ccip and ccip.is_frozen:
    block_ccip_route()
```

### Retrieve a CCTP per-message ECDSA attestation

Each attested CCTP message exposes a Circle ECDSA signature (65-byte secp256k1), independently verifiable against Circle's published attester public key.

```python
# Lookup by message hash (32-byte keccak256, 0x prefix optional)
att = client.get_cctp_attestation("0x654c0c87fb7895ec703d200469e8ef2b57876e06ad88b65e74e6e515f0ee510e")

print(att["status"])                              # "attested" or "pending"
print(att["source_chain"], "→", att["dest_chain"])
print(att["attestation"]["signature"])            # "0x..." 65-byte ECDSA secp256k1
print(att["attestation"]["latency_ms"])           # observed source_block → Iris attestation latency
print(att["attestation"]["verification_url"])     # https://iris-api.circle.com/attestations/...
```

### Retrieve a CCIP per-message row

Each CCIP message tracked by Invarians can be retrieved by its bytes32 `messageId`. Source send metadata is always present; destination metadata is filled when the message is executed.

```python
msg = client.get_ccip_message("0x03b7a89cf45aeac5898c57fcb4deafabf3b6d3ac1ecb5e70cebd86e769fea5b1")

print(msg["status"])              # "pending" or "executed"
print(msg["source_chain"], "→", msg["dest_chain"])
print(msg["sequence_number"], msg["nonce"])
print(msg["sender"], "→", msg["receiver"])

if msg["status"] == "executed":
    print(msg["dest_tx_hash"], msg["execution_state"])  # 2 = Success, 3 = Failure
```

### Handle degraded data gracefully

```python
panel = client.get_panel_v2()

if panel.oracle_status == "DEGRADED":
    for entry in panel.l1 + panel.l2:
        if entry.status != "OK":
            log.warning(f"{entry.chain}: {entry.status}")
    for br in panel.bridges:
        if br.status in ("STALE", "UNAVAILABLE"):
            log.warning(f"{br.id}: {br.status}")
    fall_back_to_conservative_mode()
```

Per-item status values:

| Status | Meaning |
|---|---|
| `OK` | Signal fresh and calibrated |
| `STALE` | Last update older than the freshness window (1h) |
| `UNAVAILABLE` | Signal temporarily missing |
| `UNCALIBRATED` | Collector running, thresholds not yet published. Does not trigger `DEGRADED`. |

---

## Regime grid

12 signed codes per chain on both L1 and L2.

| Code | Structure | Demand | What it captures |
|---|---|---|---|
| `S1D1` | nominal | nominal | Within calibrated norms |
| `S1D2+` | nominal | high | Demand surge, infrastructure healthy |
| `S1D2-` | nominal | low | Demand depressed, infrastructure healthy |
| `S1D2±` | nominal | split | Asymmetric demand composition |
| `S2+D1` | high stress | nominal | Silent structural slowdown. No fee signal. |
| `S2-D1` | low stress | nominal | Silent structural underrun. No fee signal. |
| `S2+D2+` | high stress | high | Combined upward stress |
| `S2+D2-` | high stress | low | Stress with depressed demand |
| `S2+D2±` | high stress | split | Stress with asymmetric demand |
| `S2-D2+` | low stress | high | Underrun with elevated demand |
| `S2-D2-` | low stress | low | Underrun with depressed demand |
| `S2-D2±` | low stress | split | Underrun with asymmetric demand |

Bridge states (variable-latency surfaces only):

| Code | Type | Meaning |
|---|---|---|
| `BS1` | ccip / cctp | Within calibrated latency threshold |
| `BS2` | ccip / cctp | Above calibrated latency threshold |
| `null` | any | Not yet calibrated. Raw signals still exposed on the entry. |

Calibration status:
- **CCTP**: per-message capture since 2026-05-11 (`capability_level: per_message_attested`). Each message exposes a Circle ECDSA signature (`crypto.anchor: "circle_ecdsa"`), retrievable via `client.get_cctp_attestation(message_hash)` and independently verifiable against Circle's published attester public key. `metrics.latency_p90_s / latency_p99_s / success_rate_1h` computed on per-message latencies. Confidence MEDIUM (EVM only; Solana routes scheduled 2026-Q3).
- **CCIP**: per-message capture since 2026-05-12 (`capability_level: per_message_attested`). Source `CCIPSendRequested` matched against destination `ExecutionStateChanged` via bytes32 `messageId`. `metrics.execute_latency_p90_s`, `metrics.sequence_gap`, `metrics.messages_confirmed_1h` derived from per-message data. Per-message rows retrievable via `client.get_ccip_message(message_id)`. `crypto.anchor: null` (no per-message cryptographic anchor captured yet; DON multi-sig `CommitReport` capture is the next step). RMN cursed override remains absolute.

---

## Chain coverage

| Chain | Layer | Confidence | Status |
|---|---|---|---|
| ethereum | L1 | HIGH | live |
| polygon | L1 | MEDIUM | live |
| arbitrum | L2 | MEDIUM | live |
| base | L2 | MEDIUM | live |
| optimism | L2 | MEDIUM | live |
| avalanche | L1 | LOW | observation |
| solana | L1 | LOW | calibration target Q3 2026 |

Bridges live (variable-latency scope, 20 lanes total): 10 CCTP routes (per-message ECDSA capture since 2026-05-11, `capability_level: per_message_attested`) and 10 CCIP lanes (per-message capture since 2026-05-12, `capability_level: per_message_attested`).

---

## Migrating from v0.6.x

v0.7.0 narrows the bridge scope to variable-latency surfaces (CCIP, CCTP). Native L2-to-L1 bridges operate on protocol-defined timeframes outside any observability lever and are removed from the panel. Several types and fields are removed accordingly.

```python
# Removed types
# from invarians import CcipState, CctpState, AnyBridgeState  # gone — use BridgeState

# Removed fields on BridgeEntry
# br.last_batch_age_seconds                                   # gone (native-only signal)

# Removed fields on Coverage / V2Coverage
# coverage.bridges_native                                     # gone

# BridgeType is now Literal["ccip", "cctp"]  (was: "native" | "ccip" | "cctp")
# State codes CS1 / CS2 (CCIP) and TS1 / TS2 (CCTP) are unified as BS1 / BS2
```

If you previously did `bridge_by_id("arbitrum-ethereum/native")`, switch to a CCTP route (`"...-ethereum/cctp"`) or a CCIP lane (`"...-ethereum/ccip"`).

---

## Error handling

```python
from invarians.exceptions import AuthError, RateLimitError, ServerError

try:
    panel = client.get_panel_v2()
except AuthError:
    print("Invalid API key")
except RateLimitError:
    print("Quota exceeded. Free tier: 20 req/day")
except ServerError as e:
    print(f"Service unavailable: {e}")
```

---

## Documentation

API reference: [invarians.com/developers.html](https://invarians.com/developers.html)

---

## License

MIT
