Skip to Content
⚠️ Alert: Sylan is under active development—only trust contract/wallet addresses announced on our official channels; we will never DM you, ask for funds, or run surprise airdrops/presales.
ContractsAPI Consensus

APIConsensus

On‑chain consensus for provider‑signed snapshots. Nodes submit EIP‑712 Snapshots; the contract tallies identical digests and finalizes either when quorum is reached or after expiry + grace. It can optionally integrate with NodeRegistry for eligibility and slashing/rewards.

Units: timestamps in milliseconds (uint64). Amounts are SYL (18d) only when interacting with Escrow/NodeRegistry. Contract is UUPS‑upgradeable, ownable, pausable, reentrancy‑guarded.


Responsibilities

  • Register requests (mirroring AccessRegistry IDs & nonces).
  • Verify Provider EIP‑712 signatures over snapshots.
  • Enforce freshness (skew/TTL) and one‑vote‑per‑node (optionally gated by NodeRegistry).
  • Tally votes with deterministic fork‑choice; detect ProviderEquivocation.
  • Finalize success/failure and notify APIEscrow to settle/refund.
  • (Optional) Slash mismatching nodes and reward honest ones via NodeRegistry.

Snapshot (what nodes submit)

struct Snapshot { bytes32 apiId; // logical API identifier uint256 seqNo; // provider sequence (monotonic if enabled per API) uint64 providerTs; // provider timestamp (ms) uint64 ttl; // ms; 0 = no TTL (skew check still applies) bytes32 contentHash; // hash of the response artifact/root }

EIP‑712 domain: name = "SylanProviderSnapshot", version = "1"
Typehash: keccak256("Snapshot(bytes32 apiId,uint256 seqNo,uint64 providerTs,uint64 ttl,bytes32 contentHash)")

Checks on submitSnapshot

  • API active (from AccessRegistry).
  • providerSignerOf(apiId) != 0x0 and recovered signer matches.
  • Freshness: providerTs ≤ nowMs + maxSkewMs(apiId); if ttl ≠ 0, then nowMs ≤ providerTs + min(ttl, maxTtlMs(apiId)).
  • Eligibility: if nodeRegistry != 0, caller must be isActiveNode.
  • One vote per node per request (local guard).

Equivocation detection: first contentHash for (apiId, seqNo) is remembered; a different hash later triggers ProviderEquivocation.


Fork‑choice & finalization

Vote digest = EIP‑712 hash of the Snapshot.

Leader selection:

  1. More votes wins.
  2. If tied, higher seqNo wins.
  3. If still tied, lower providerTs wins.

When does it finalize?

  • Early quorum: if leader votes ≥ quorum, finalize immediately.
  • Deadline path: callable when nowMs ≥ expiresAtMs and nowMs ≤ expiresAtMs + requestExpiryGraceMs.
  • Inactive API at finalize time → RequestFailed(reason=2 /*InactiveAPI*/) and Escrow refunds.
  • Seq monotonicity (per‑API flag from AccessRegistry): if enabled and the candidate’s seqNo regresses, treat as NoQuorum failure.

Views (read model)

  • accessRegistry() → address
  • nodeRegistry() → address
  • eventLogger() → address
  • apiEscrow() → address
  • quorum() → uint256
  • requestExpiryGraceMs() → uint64
  • slashAmount() → uint16 (bps of stake)
  • requestMeta(requestId) → { apiId, consumer, expiresAtMs, status } (status: 0=Unset,1=Open,2=Finalized,3=Failed)
  • topCandidate(requestId) → (msgHash, votes, seqNo, providerTs, contentHash)

Core flow (writes)

registerRequest(requestId, apiId, consumer, nonce, expiresAtMs)

Called by APIEscrow when a PPC lock occurs. Recomputes the deterministic requestId and checks the nonce against AccessRegistry, then sets status Open and emits RequestRegistered.

submitSnapshot(requestId, snapshot, providerSig, pointerURI)

Called by Nodes (optionally gated by NodeRegistry). Verifies signature & freshness, records a vote (one per node), updates fork‑choice, emits ResponseSubmitted, and may finalize early if quorum.

finalize(requestId)

Anyone can call once the deadline path is available. Will finalize or fail (NoQuorum/InactiveAPI) and notify Escrow accordingly.


Events

  • RequestRegistered(bytes32 indexed requestId, bytes32 indexed apiId, address indexed consumer, uint64 expiresAtMs, uint256 nonce)
  • ResponseSubmitted(bytes32 indexed requestId, address indexed node, bytes32 msgHash, uint256 seqNo, uint64 providerTs, bytes32 contentHash, string pointerURI)
  • RequestFinalized(bytes32 indexed requestId, bytes32 indexed apiId, uint256 seqNo, uint64 providerTs, bytes32 contentHash, bytes32 msgHash, uint256 votes)
  • RequestFailed(bytes32 indexed requestId, bytes32 indexed apiId, uint8 reason)1 = NoQuorum, 2 = InactiveAPI
  • ProviderEquivocation(bytes32 indexed apiId, uint256 indexed seqNo, bytes32 firstHash, bytes32 laterHash)

The contract can optionally forward human‑readable logs to EventLogger (e.g., Finalized/Failed with JSON metadata) for indexers.


Slashing & rewards (optional NodeRegistry)

When configured:

  • Slash amount: slashAmount bps is applied to each mismatching node’s stake upon finalization.
  • The total slashed is split between nodePool and treasury/burn per NodeRegistry’s BPS policy.
  • Honest nodes (those who voted for the winning digest) may be rewarded from the node pool, and reputation is increased.

If nodeRegistry == 0x0, eligibility and slashing are disabled, but consensus still operates.


Minimal ABI (dapp‑facing)

These are the typical methods UIs and nodes need; full interface lives in the repo.

[ {"type":"function","stateMutability":"view","name":"quorum","inputs":[],"outputs":[{"type":"uint256"}]}, {"type":"function","stateMutability":"view","name":"requestExpiryGraceMs","inputs":[],"outputs":[{"type":"uint64"}]}, {"type":"function","stateMutability":"view","name":"topCandidate","inputs":[{"name":"requestId","type":"bytes32"}],"outputs":[ {"type":"bytes32"},{"type":"uint256"},{"type":"uint256"},{"type":"uint64"},{"type":"bytes32"} ]}, {"type":"function","stateMutability":"nonpayable","name":"submitSnapshot","inputs":[ {"name":"requestId","type":"bytes32"}, {"name":"snapshot","type":"tuple","components":[ {"name":"apiId","type":"bytes32"}, {"name":"seqNo","type":"uint256"}, {"name":"providerTs","type":"uint64"}, {"name":"ttl","type":"uint64"}, {"name":"contentHash","type":"bytes32"} ]}, {"name":"providerSig","type":"bytes"}, {"name":"pointerURI","type":"string"} ],"outputs":[]}, {"type":"function","stateMutability":"nonpayable","name":"finalize","inputs":[{"name":"requestId","type":"bytes32"}],"outputs":[]} ]

Integration tips

  • Pointer vs integrity: pointerURI is only a hint; UIs should verify contentHash when fetching artifacts.
  • Signer rotation windows: if AccessRegistry returns 0x0 from providerSignerOf(apiId), nodes must treat snapshots as unauthorized until the timelock elapses.
  • Event‑driven UI: subscribe to RequestRegisteredResponseSubmitted* → (RequestFinalized | RequestFailed). Then read APIEscrow.withdrawableOf to show payouts/refunds.
  • Idempotency: Escrow calls from consensus are safe to retry. Don’t fear duplicate finalize attempts; the state machine prevents double settlement.

Admin & safety

  • Owner can setQuorum(q) (q > 0) and setRequestExpiryGraceMs(ms); pausing disables writes.
  • Deterministic IDs: registerRequest recomputes the requestId exactly as in AccessRegistry and checks the nonce window.
  • Millis everywhere: clients and nodes should pass/expect ms for all consensus timestamps.

Out of scope for this page

  • Pricing/escrow math → APIEscrow
  • API plan/params and signer management → AccessRegistry
  • Node staking and slashing policy → NodeRegistry
  • Addresses & minimal ABIs → Architecture → Addresses & ABIs
Last updated on