Docs v2 · testnet

Protocol · Composition

Building new events from finalized ones

EventComposer builds composite events from primitive (or other composite) event IDs, deterministically, without duplicating Registry state. A composite is itself just another finalized event — composable again, readable by any application.

OperatorOperandsRule
AND2+True iff every operand resolved true.
OR2+True iff any operand resolved true.
NOT1Negates its single operand.
BEFORE2True iff both resolved true and operand 0 finalized strictly before operand 1.
WITHIN2 + windowTrue iff both resolved true and their finalization timestamps differ by at most `window` seconds (inclusive).
K_OF_N—Coming soon — explicitly out of scope for this pass (SPEC_AND_TASKS.md Issue #10). Not implemented, not deployed.

Canonical identity

Operand order is canonicalized (sorted ascending) for every order-independent operator — AND, OR, NOT, WITHIN — so two apps independently composing AND(a, b) and AND(b, a) land on the same on-chain object. BEFORE is the deliberate exception: “A before B” is not the same claim as “B before A”, so its operand order is preserved exactly as given.

Example

The canonical demo composes a rate-decision event and a price-threshold event with WITHIN(48h):

TypeScript
const compositeId = await client.createComposite(
  {
    op: CompositeOp.Within,
    operands: [rateDecisionEventId, priceThresholdEventId],
    window: 48n * 60n * 60n,
  },
  account,
);

Structural bounds

Composition depth is capped at 8, and fan-in (operands per composite) at 10 — enforced at creation time. Cycles are not just bounded, they are cryptographically impossible: a composite's ID is a hash of its own operands, so it cannot reference itself without already knowing an ID that only exists after that same hash is computed. See Threat model for the full proof.