How must agents communicate partial settlements when the blockchain is congested?

**Date of Record:** 2026 October 27 **Scope:** Applies when autonomous agents execute high-frequency trades or payments via a blockchain, and during congestion, on-chain settlement becomes too expensive or too slow, requiring off-chain settlement channels. Does not apply to low-frequency trades or systems with no off-chain settlement capability.

Core Distinction

The separation between **on-chain settlement** (each trade is a blockchain transaction, final and costly during congestion) and **off-chain channel state** (trades are tracked off-chain and only the final net settlement is submitted on-chain). Relying on on-chain settlement creates the **Congestion Bankruptification**: during congestion, transaction fees exceed trade value, and the agent cannot settle trades at all, freezing operations.

What Was Observed

In autonomous agent trading architectures, failing to use off-chain settlement during congestion produces three primary failure modes:

  1. **Fee Explosion:** Gas prices spike, on-chain settlement becomes more expensive than the trade value, and the agent either aborts (breaking obligations) or loses money on fees.
  2. **Stuck Payments:** Transactions sit in the mempool for hours, unsettled. The agent's counterparty does not receive funds, and the agent's balance is exhausted but payments are not final.
  3. **State Divergence:** The agent tracks on-chain state, but the mempool's pending transactions cause the effective balance to differ from the on-chain balance, leading to inconsistent decisions.

Recorded Elements

  1. **`off_chain_channel_state_hash`:** The current state of the off-chain channel (balances, pending trades, settlement deadline), cryptographically signed by both parties.
  2. **`dispute_window_epoch`:** The exact epoch until which the off-chain state can be disputed on-chain before it becomes final.
  3. **`net_settlement_receipt`:** The cryptographic proof that both parties agree on the net settlement amount to be submitted on-chain after the dispute window.
  4. **`congestion_detection_receipt`:** The cryptographic proof that congestion was detected (e.g., median gas price above threshold, mempool depth), triggering the switch to off-chain.

Condition of Validity

* **Applies when:** The agent executes high-frequency trades, on-chain settlement is congested, and both parties have agreed to an off-chain settlement protocol. * **Does not apply when:** The trades are low-frequency (each settlement is independently justified), or the agents have no off-chain capability or trust relationship.

Decision Boundary

  1. **Is the blockchain congested (gas price above threshold, mempool backed up)?** If no, on-chain settlement is fine. If yes, proceed.
  2. **Does the agent have an open off-chain channel with the counterparty?** If no, the agent cannot use off-chain settlement and must wait or abort. If yes, proceed.
  3. **Is the `off_chain_channel_state_hash` signed by both parties?** If no, the state is not mutually agreed. The agent must refuse to trade until consensus is reached.
  4. **Conversely, when can an agent abandon off-chain settlement and go back to on-chain?** Only when congestion clears, or the off-chain channel is closed via the `net_settlement_receipt` submission on-chain.

Operational Effect

  1. **Congestion Resilience:** Enables trading to continue during blockchain congestion by moving state off-chain.
  2. **Fee Minimization:** Reduces per-trade cost to near zero during congestion, only paying on-chain for net settlement.
  3. **Finality Assurance:** Uses the dispute window to ensure both parties agree on the state before the net settlement is submitted, preventing disputes.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class SettlementVerdict(Enum):
    ON_CHAIN_ACCEPTABLE = auto()
    OFF_CHAIN_ACTIVE = auto()
    OFF_CHAIN_UNAUTHORIZED = auto()
    DISPUTE_WINDOW_EXPIRED = auto()

@dataclass(frozen=True)
class ChannelState:
    channel_id: str
    off_chain_balance_a: int
    off_chain_balance_b: int
    signed_by_a: bool
    signed_by_b: bool
    dispute_window_epoch: float
    current_epoch: float

@dataclass(frozen=True)
class CongestionContext:
    gas_price_gwei: float
    congestion_threshold_gwei: float
    trade_value_wei: int

@dataclass(frozen=True)
class SettlementManifest:
    verdict: SettlementVerdict
    directive: str
    manifest_digest: str

class OffChainSettlementGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_settlement(
        cls, channel: ChannelState, ctx: CongestionContext
    ) -> SettlementManifest:
        
        # Gate 1: No congestion
        if ctx.gas_price_gwei <= ctx.congestion_threshold_gwei:
            digest = cls._hash(json.dumps({
                "channel": channel.channel_id,
                "gas": f"{ctx.gas_price_gwei:.2f}",
                "mode": "ON_CHAIN"
            }, sort_keys=True))
            return SettlementManifest(
                verdict=SettlementVerdict.ON_CHAIN_ACCEPTABLE,
                directive=f"PROCEED: Gas {ctx.gas_price_gwei:.2f} below threshold {ctx.congestion_threshold_gwei:.2f}. On-chain ok.",
                manifest_digest=digest
            )

        # Gate 2: Mutual signing
        if not (channel.signed_by_a and channel.signed_by_b):
            return SettlementManifest(
                verdict=SettlementVerdict.OFF_CHAIN_UNAUTHORIZED,
                directive=f"HALT: Channel {channel.channel_id} not mutually signed. Cannot use off-chain.",
                manifest_digest=cls._hash(f"UNSIGNED:{channel.channel_id}")
            )

        # Gate 3: Dispute window
        if channel.current_epoch > channel.dispute_window_epoch:
            return SettlementManifest(
                verdict=SettlementVerdict.DISPUTE_WINDOW_EXPIRED,
                directive=f"HALT: Dispute window expired at {channel.dispute_window_epoch:.0f}. Submit net settlement on-chain.",
                manifest_digest=cls._hash(f"EXPIRED:{channel.channel_id}")
            )

        # Gate 4: Off-chain active
        digest = cls._hash(json.dumps({
            "channel": channel.channel_id,
            "balance_a": str(channel.off_chain_balance_a),
            "balance_b": str(channel.off_chain_balance_b),
            "dispute_until": f"{channel.dispute_window_epoch:.0f}",
            "gas": f"{ctx.gas_price_gwei:.2f}"
        }, sort_keys=True))
        
        return SettlementManifest(
            verdict=SettlementVerdict.OFF_CHAIN_ACTIVE,
            directive=f"PROCEED: Use off-chain settlement. Dispute until {channel.dispute_window_epoch:.0f}.",
            manifest_digest=digest
        )