Skip to content

Oracle Service

RunTimeAdmin edited this page Nov 2, 2026 · 19 revisions

Oracle Service

**Referenced Files in This Document** - [index.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/index.js) - [http-helpers.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/http-helpers.js) - [scoring.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/scoring.js) - [epoch-policy.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/epoch-policy.js) - [store.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/store.js) - [metrics.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/metrics.js) - [config.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/config.js) - [secrets.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/secrets.js) - [state-serialize.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/state-serialize.js) - [README.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/README.md) - [package.json](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/package.json) - [docker-compose.oracle.yml](https://github.com/RunTimeAdmin/Sigvara/blob/main/docker-compose.oracle.yml) - [architecture.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/architecture.md)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document describes Sigvara’s off-chain oracle service that computes normalized reputation scores for agents and anchors them on chain. The service exposes a small REST API for attestation submission, score preview, evidence retrieval, health checks, metrics, and operator controls. Internally it coordinates chain interaction, optional payment scanning, scoring computation, persistent state, and Prometheus-style metrics. It also supports a checker mode where a second operator independently recomputes pending scores and reports divergences to the slashing committee.

The oracle is designed so that anyone can audit its outputs: it publishes Merkle roots over the evidence used to compute each score, and serves the underlying events with proofs.

This page describes the oracle as it is on main. The testnet oracles each run one pinned commit (SIGVARA_COMMIT in the environment file) and report it in /health. Both run cf76691 (pull request 138) as of 2 November 2026, which is the oracle code on main apart from a patch release of dotenv, and /health reports the commit each one runs. A change merged to main reaches them only when someone changes the pin and recreates the container.

Section sources

Project Structure

The oracle is a Node.js application built around a single HTTP server entry point and several focused modules:

  • index.js — HTTP server, configuration, route handlers, epoch scheduler, and orchestration of chain, payments, scoring, store, cache, badge, jobs, and metrics.
  • http-helpers.js — JSON helpers, bearer-token authentication and credential scopes, body parsing, rate limiting, client identification (with TRUSTED_PROXIES), canonical request-target parsing, and startup policy checks.
  • scoring.js — Pure functions computing the six-factor reputation score.
  • epoch-policy.js — Epoch scheduling validation, primary/checker decision logic, divergence comparison, chunked mapping utilities, and the clock-skew and stale-scoring checks.
  • store.js — Persistent JSON-backed state for attestations, flags, links, cooldowns, payment events, scan checkpoints, and divergences, with a .bak of the previous save and a recorded schemaVersion and chainId.
  • metrics.js — In-memory Prometheus counters and gauges exposed at /metrics.
  • config.js: Startup checks on the numeric settings and on ORACLE_MODE. A value that is set and unusable stops the process, and a blank value means unset.
  • secrets.js: Reads the signing key from the file named by ORACLE_PRIVATE_KEY_FILE or from ORACLE_PRIVATE_KEY, and refuses a malformed key with a message that never contains the value.
  • state-serialize.js: Writes the state file in pieces of about 1 MB that join to exactly what JSON.stringify would produce.
  • Supporting modules referenced by the entry point include chain interaction, external ERC-8004 integration, payment scanning, Merkle tree utilities, response caching, badge generation, job registry reading, and payment accounting.
  • payer-outcome.js: The checks on an outcome the payer signed (ADR 0006): its shape and timing, the EIP-712 signature by recovery or through ERC-1271, and reading another operator's GET /outcomes pages.
  • evidence-as-of.js: The payment evidence as it stood when a pending proposal was made, which a checker scores that proposal over.
graph TB
Client["Client"] --> HTTP["HTTP Server<br/>index.js"]
HTTP --> Helpers["Auth & Rate Limiting<br/>http-helpers.js"]
HTTP --> Store["Persistent State<br/>store.js"]
HTTP --> Scoring["Score Computation<br/>scoring.js"]
HTTP --> EpochPolicy["Epoch Decisions<br/>epoch-policy.js"]
HTTP --> Metrics["Prometheus Metrics<br/>metrics.js"]
HTTP --> Chain["Chain Interaction"]
HTTP --> Payments["Payment Accounting"]
HTTP --> External["ERC-8004 External Score"]
HTTP --> Jobs["ERC-8183 Job Registry"]
HTTP --> Cache["Response Cache"]
HTTP --> Badge["Badge Generator"]
HTTP --> PaymentScan["Payment Scanner"]
Loading

Diagram sources

Section sources

Core Components

  • HTTP server and routing: The entry point configures environment variables, initializes chain and external integrations, loads persistent state, and exposes endpoints such as /health, /metrics, /attest, /flag, /flag/resolve, /link, /score/:didHash, /evidence/:didHash, /outcomes, /badge/:address.svg, /jobs/:address, /epoch, and /divergence. Numeric settings and ORACLE_MODE are validated before anything else starts, and a request target must be a path in canonical form or the answer is 400 bad request target. An error a route does not handle itself is logged and answered 500 internal error.
  • Authentication: Bearer tokens are loaded from ORACLE_ADMIN_TOKEN and named ORACLE_TOKEN_<NAME> values. Tokens are compared using constant-time comparison, and callers are attributed by name. ORACLE_SCOPES_<NAME> can limit a credential to some of attest, flag, link and epoch.
  • Rate limiting: A fixed-window limiter (60 requests per caller per minute) protects write endpoints and the expensive read endpoints, with a separate bucket for each kind of route: writes, /evidence, /divergence, and the other reads. The caller is the socket address, or an address read from the right of X-Forwarded-For when the connection arrives from loopback or from a proxy listed in TRUSTED_PROXIES.
  • Persistence: Attestation counts, flag timestamps, agent links, cooldowns, payment events, deduplication keys, divergences, and scan checkpoints are persisted to a mounted volume, together with a schemaVersion and the chainId the state belongs to. Writes go to a temp file in pieces, are flushed with fsync and renamed into place, and the file being replaced is kept as <state>.bak.
  • Metrics: Prometheus-format exposition includes epoch lifecycle, proposal/finalization outcomes, attestations, flags, links, badges, jobs, payment scans, throttling, refused or unconfirmed transactions, epoch-fee charges, stranded proposals, refused HTTP requests, evidence cache statistics, and the import of payer-signed outcomes from other operators.

Section sources

Architecture Overview

Sigvara sits on top of ERC-8004 identity and feedback. The oracle consumes multiple signals:

  • Payment-backed attestations: Success or failure tied to an on-chain settlement, reported by a token holder.
  • Payer-signed outcomes: The payer's own EIP-712 signature that the work was delivered, which needs no token and which each operator imports from the others (ADR 0006).
  • On-chain payment scanning: Optional pull-based discovery of transfers to registered agents.
  • Flags: Community watchdog penalties.
  • ERC-8004 feedback: Cross-protocol trust signal for linked identities.
  • Counterparty standing: Payer reputation influences propagation.
  • Tenure: Activity span weighted by recency.

The oracle proposes a computed score and Merkle root to SigvaraReputation; after a challenge window, it finalizes. Two operators may run: a primary proposing and finalizing, and a checker auditing proposals and reporting divergences.

graph TB
subgraph Sources["Signal Sources"]
CA["CounterAudit<br/>Attestations"]
WD["Watchdog<br/>Flags"]
CH["Chain<br/>Transfers"]
EREP["ERC-8004<br/>Feedback"]
end
subgraph Oracle["Oracle Service"]
API["REST API"]
Scan["Payment Scanner"]
Score["Scorer"]
Store["State Store"]
Metrics["Metrics"]
end
subgraph Contracts["On-Chain"]
REP["SigvaraReputation"]
STAKING["SigvaraStaking"]
end
CA --> API
WD --> API
CH --> Scan
EREP --> Score
API --> Store
Scan --> Store
Store --> Score
Score --> REP
STAKING --> REP
REP --> API
API --> Metrics
Loading

Diagram sources

Section sources

Detailed Component Analysis

REST API Endpoints

Health and Diagnostics

  • GET /health: Operational status including epoch timing, uptime, store writability, persistence result, where the state was loaded from (stateSource: file, fresh or backup), the chain the RPC was last seen on beside the one configured (chainId, expectedChainId), the commit the deployment reports, external feed reachability, payment scan progress, and job registry status. Returns 503 when the state path is not writable, state persistence is failing, or scoring is stale: no successful epoch for two epoch intervals, counted from process start until the first success.
  • GET /metrics: Prometheus exposition for monitoring and alerting.

Section sources

Attestation Submission

  • POST /attest: Submits work-outcome attestations. With payment verification required, a positive attestation needs no token because the verified payment is the credential for the payment, but without a token it is stored with no outcome (outcomeRecorded: false); recording an outcome takes a bearer token or the payer's signature, and a negative attestation always requires a token. An outcome the payer signed goes in payerOutcome instead of success and needs no token, where PAYER_OUTCOMES is on; the operators take "delivered" only for now. The endpoint verifies the settlement against the chain, credits the payer from transfer logs, enforces cooldowns, and returns standardized error codes for rejected payments, already-credited settlements, cooldowns, and RPC failures. While the oracle cannot write its state file it answers 202 with durable: false instead of 200.

Request/response patterns:

  • Positive verified payment: minimal request with settlement transaction hash; response includes credited payer, amount, updated tally, and whether an outcome was recorded.
  • Negative or unverified: requires auth; returns appropriate rejection code when credentials or payment checks fail.
  • Payer-signed outcome: payment.txHash plus payerOutcome, no token. The response carries outcomeHash, and a second outcome for the same leg gets 409 outcome_recorded with heldOutcomeHash and sameOutcome.

Section sources

Reputation Retrieval

  • GET /score/:didHash: Computes and returns the current score without writing to chain. Uses chain clock for deterministic time-dependent factors and reads agent info, counterparty standing, flags, external score, and payment-derived factors, the same inputs the epoch uses. When the node cannot answer, after retries, the route answers 502 with code: "rpc_error"; any other failure is a generic 500, because the route is public and does not echo error text.

Section sources

Evidence Access

  • GET /evidence/:didHash: Serves the Merkle root, count, and evidence entries with leaf hashes and Merkle proofs. Unauthenticated and rate-limited. Includes committed fields so verifiers know exactly what is covered. Each entry names its outcomeSource (payer_signed or credentialed, this oracle's label and not part of the leaf), and where PAYER_OUTCOMES_PUBLISH=on a payer-signed entry carries the signed payload, with the signing domain served as payerOutcomeDomain. counterauditPacketId is listed under unverifiedFields: a pointer this oracle checks only for shape.

Section sources

Payer-Signed Outcome Exchange

  • GET /outcomes?since=<ms>: The payer-signed outcomes this oracle has taken, oldest first, 500 to a page, with the signing domain and a next cursor (acceptedAt, this oracle's own time of taking each). Unauthenticated, because each outcome verifies on its own. Served only with PAYER_OUTCOMES on and PAYER_OUTCOMES_PUBLISH=on, and 404 otherwise.
  • Import: an oracle with PAYER_OUTCOMES_PEERS set reads each listed operator's /outcomes at the start of every epoch, before scoring, and puts each outcome through the same checks as a POST /attest, at the block the peer recorded (checkedAtBlock). An outcome must reach it within PAYER_OUTCOMES_IMPORT_GRACE_HOURS (6 by default) of its deadline. One it already holds is kept, and a different one served for the same payment is counted as a conflict. Imported outcomes are served on at this oracle's own /outcomes.

Section sources

Operator Controls

  • POST /flag: Raises community flags; each flag costs points in the community factor.
  • POST /flag/resolve: Clears flags raised previously; clamps to zero and reports before/after counts.
  • POST /link: Links an agent to an ERC-8004 identity for cross-protocol scoring.
  • POST /epoch: Manually triggers an epoch run; answers 202, or 409 if one is already running.

All require bearer tokens unless explicitly exempted. A credential restricted with ORACLE_SCOPES_<NAME> gets 403 on a write outside its scopes.

Section sources

Badge Endpoint

  • GET /badge/:address.svg: Embeddable SVG badge showing finalized on-chain score bands. Caches responses and sets CORS headers for embedding.

Section sources

Job Registry Observation

  • GET /jobs/:address: Reads ERC-8183 job registry data observationally. Disabled unless configured; returns 501 when disabled.

Section sources

Divergence Reporting

  • GET /divergence and GET /divergence/:didHash: Checker-mode only. Reports disagreements between this operator’s recomputation and the pending on-chain proposal, including per-factor deltas and evidence roots. Each record also says whether this operator's evidence as it stood at the proposal matched the root the proposal committed to (evidenceMatchesProposal) and gives that root (ownEvidenceRootAtProposal). The collection holds the 100 most recent, newest first, with total and truncated, and reports seesProposals so an empty list cannot be mistaken for "checked, found nothing". A primary answers 404.

Section sources

Authentication Methods

  • Bearer token authentication uses constant-time comparison across all configured tokens.
  • Named credentials allow revocation and attribution without rotating all secrets.
  • ORACLE_SCOPES_<NAME> restricts a credential to some of attest, flag, link and epoch. An unknown scope, or a restriction naming a credential that does not exist, stops the process at startup, and admin is never restricted. A token shorter than 32 characters works but is named in a startup warning.
  • A non-loopback bind with no configured credential is refused at startup to prevent exposing write endpoints. A loopback bind may run without one for local testing.

Section sources

Request/Response Schemas

Endpoint Method Auth Request Body Response Summary Error Codes
/health GET None None Operational status object 200, 503
/metrics GET None None Prometheus text 200
/attest POST Required for negative/unverified; positive verified payment exempt (recorded with no outcome unless a token is sent), and a payer-signed outcome needs none didHash, success or payerOutcome, optional payment.txHash, optional packetId Credited payer, amount, updated tally, and outcomeHash for a payer-signed outcome 200, 202, 400, 401, 402, 403, 409, 429, 502
/flag POST Required didHash Updated flag count 200, 202, 400, 401, 403, 429
/flag/resolve POST Required didHash, optional count Before/after/resolved counts 200, 202, 400, 401, 403, 429
/link POST Required didHash, agentId Link confirmation 200, 202, 400, 401, 403, 429, 502
/score/:didHash GET None Path parameter Computed score object 200, 429, 500, 502
/evidence/:didHash GET None Path parameter Root, count, evidence array with proofs 200, 400, 429
/outcomes GET None Optional since query (ms) Payer-signed outcomes, oldest first, with the signing domain and a next cursor 200, 400, 404, 429
/badge/:address.svg GET None Address path SVG image 200, 404
/jobs/:address GET None Address path Job registry summary 200, 429, 501, 502
/epoch POST Required None Epoch trigger result 202, 401, 403, 409, 429
/divergence GET None Optional :didHash path parameter Divergence records 200, 400, 404, 429

Section sources

Error Handling Patterns

  • HTTP helpers enforce maximum body size and return structured errors for invalid JSON.
  • Attestation endpoint distinguishes RPC failures from business rejections (cooldown, self-payment, already credited).
  • A request target that is not a canonical path is refused with 400 bad request target before routing, and a handler that throws costs one request (500 internal error), not the process.
  • Epoch execution wraps chain reads and writes in guards; transient RPC failures increment metrics and skip rather than crash the process.
  • A transaction whose fee would exceed MAX_FEE_GWEI, or whose worst-case cost would exceed MAX_TX_COST, is refused before it is signed. One not mined within TX_WAIT_SECONDS is replaced once, at the same nonce and twice the fee, and is counted as unconfirmed if that does not land either.
  • Health endpoint returns 503 when the state path is not writable, state persistence fails, or epochs are stale.
  • Metrics track propose/finalize attempts, successes, errors, throttling, and slot-taken outcomes separately.

Section sources

Epoch Processing Pipeline

The epoch pipeline transforms raw attestations and chain observations into normalized reputation scores:

  1. Check that the RPC is on the expected chain and that the chain's clock is within MAX_CLOCK_SKEW_SECONDS of the host's (an epoch that fails either check does not run), import payer-signed outcomes from the operators in PAYER_OUTCOMES_PEERS if set, then acquire registered agents and scan recent payments if enabled.
  2. Read agent info, pending score, and fee coverage concurrently in chunks.
  3. Filter slashed and uncovered agents; prepare candidates.
  4. For each candidate, perform guard reads (bonded status, fresh status) and decide whether to finalize, propose, or skip based on challenge window.
  5. Compute measured factors from payment events, flags, external score, activity, and propagation.
  6. Call pure scoring function to produce six factors and total.
  7. In checker mode, re-read the pending slot, score the proposal at its own timestamp over the evidence as it stood then (the payments settled by proposedAt, with an outcome a payer signed later read as unreported when that makes the root match the one the proposal committed to), and compare. Record a divergence when the total or a single factor differs by more than the tolerance, finalize an agreed score once its window has elapsed, and propose only into an empty slot (or one whose proposer has lost its authority) through proposeIfEmpty.
  8. Propose or finalize on chain, waiting a bounded time for each transaction; update metrics and persist state.
flowchart TD
Start(["Epoch Start"]) --> Checks["Check Chain ID and Clock"]
Checks --> Agents["Fetch Registered Agents"]
Agents --> Scan["Optional Payment Scan"]
Scan --> Phase1["Phase 1: Batch Agent Info, Pending, Coverage"]
Phase1 --> Filter["Filter Slashed/Uncovered"]
Filter --> Phase2["Phase 2: Guard Reads per Chunk"]
Phase2 --> Decision{"Action?"}
Decision --> |Skip| NextAgent["Next Agent"]
Decision --> |Finalize-Then-Propose| Finalize["Finalize Existing Proposal"]
Finalize --> Compute["Compute Measured Factors"]
Decision --> |Propose| Compute
Compute --> Score["computeScore()"]
Score --> Checker{"Checker Mode?"}
Checker --> |Yes| Compare["Compare Against Pending"]
Compare --> Record{"Divergence?"}
Record --> |Yes| Divergence["Record Divergence"]
Record --> |"No, slot empty"| Propose["Propose New Score"]
Record --> |"No, proposal pending"| Agreed["Skip, or Finalize After Window"]
Checker --> |No| Propose
Propose --> Persist["Persist State"]
Divergence --> Persist
Agreed --> Persist
Persist --> End(["Epoch Complete"])
Loading

Diagram sources

Section sources

Scoring Algorithms

The six-factor model produces a normalized score:

  • Fee score: Real volume when payment verification is enabled; otherwise a proxy based on attestation count.
  • Success score: Weighted success ratio with a prior to avoid scale invariance.
  • Age score: Tenure curve over activity span with recency weighting.
  • External score: ERC-8004 feedback for linked identities, clamped to contract cap.
  • Community score: Penalty from unresolved flags.
  • Propagation score: Inherited trust from counterparties’ external standing.
classDiagram
class Scorer {
+computeScore(opts)
+feeScore(attestationTotal)
+successScore(successful, total, prior)
+ageScore(registeredAtSeconds, activity, nowMs)
+communityScore(unresolvedFlags)
}
class Inputs {
+registeredAt
+attestations
+flags
+externalScore
+measuredFeeScore
+activity
+propagation
+now
}
Scorer --> Inputs : "reads"
Loading

Diagram sources

Section sources

Chain Interaction, Payment Scanning, and External Integrations

  • Chain interaction: Initializes provider, reads agent info, pending scores, challenge windows, latest block timestamp, fee gating, and performs propose/finalize transactions. Reads are retried with backoff; writes are not resent blindly, and are checked for throttling, signed only under the fee caps, and waited for a bounded time (one same-nonce replacement at twice the fee).
  • Payment scanning: Optionally scans ERC-20 Transfer logs to agent addresses, plans credits, validates with a canary, persists checkpoints, and avoids losing evidence during RPC refusals.
  • External integrations: ERC-8004 external score for linked identities; ERC-8183 job registry observation.

Section sources

Metrics Collection

Prometheus-style metrics cover epoch lifecycle, proposal/finalization outcomes, attestations, flags, links, badges, jobs, payment scans, throttling, evidence cache, and checker comparisons. Others cover sigvara_oracle_tx_refused_total{reason} (a transaction refused for its fee, or sent and never seen mined), sigvara_oracle_fee_charges_total{result}, sigvara_oracle_proposals_stranded_total, sigvara_oracle_http_refused_total{reason}, and the payer-outcome import (sigvara_oracle_payer_outcomes_imported_total, sigvara_oracle_payer_outcome_import_refused_total, sigvara_oracle_payer_outcome_import_errors_total and sigvara_oracle_payer_outcome_conflicts_total). All are exposed at /metrics and should be scraped by Prometheus or compatible tools.

Section sources

Dependency Analysis

The oracle’s runtime dependencies are minimal:

  • Node.js runtime version requirement.
  • dotenv for environment loading.
  • ethers for Ethereum JSON-RPC interaction.
graph LR
App["oracle/index.js"] --> Dotenv["dotenv"]
App --> Ethers["ethers"]
App --> HttpHelpers["http-helpers.js"]
App --> Scoring["scoring.js"]
App --> EpochPolicy["epoch-policy.js"]
App --> Store["store.js"]
App --> Metrics["metrics.js"]
Loading

Diagram sources

Section sources

Performance Considerations

  • Concurrency control: Epoch phase one batches independent reads in chunks to avoid overwhelming RPC providers while still benefiting from ethers batched JSON-RPC calls.
  • Guard batching: Pre-write guards are issued in parallel per chunk to reduce round trips.
  • Time determinism: All time-dependent scoring uses chain-provided timestamps to ensure two operators compute identical results.
  • Evidence caching: /evidence rebuilds Merkle trees; a response cache reduces expensive recomputation.
  • Merkle caching: Stored payment events are frozen, and merkle.js caches each leaf and each agent's root by identity, so an epoch in which an agent's evidence has not changed reuses its root instead of hashing every payment again.
  • Payment scan pacing: Configurable pace between getLogs calls prevents provider refusal during long catch-ups.
  • Flag and event pruning: Decay-based pruning keeps state bounded.
  • State saves: The state file is written in pieces, so the memory peak of a save is the live state plus one piece rather than several copies of the whole file.

Troubleshooting Guide

Common issues and diagnostics:

  • Missing or unusable settings: Startup exits when required variables are absent, when a numeric setting is set to something that is not a plain number in range (a blank value counts as unset), or when both ORACLE_PRIVATE_KEY and ORACLE_PRIVATE_KEY_FILE are set.
  • Bad epoch interval: Validation rejects intervals that would cause continuous epoch runs.
  • Checker misconfiguration: Checker mode refuses fee registry configuration and warns if epoch cadence is too slow relative to the challenge window.
  • State persistence failures: /health reports writability and last persist result; investigate volume mounts and disk space. A state file that exists but cannot be read falls back to the .bak of the previous save (the bad file is kept as <state>.corrupt-<time>) and stateSource reads backup; the process stops if there is no usable backup. A file written by a newer version, or saved on another chain than the RPC reports, also stops it.
  • Payment verification disabled: Without verification, fee score proxies HTTP request counts and is not a trustworthy signal.
  • Payment scan empty but broken: Canary detects filter/query failures; monitor sigvara_oracle_payment_scan_canary_failures_total.
  • Stale epochs: /health returns 503 when no successful epoch occurred within twice the epoch interval, counted from process start until the first success.
  • Rate limiting: Requests exceeding 60 per caller per minute in a route's bucket receive 429; if every visitor behind a proxy is limited together, set TRUSTED_PROXIES to the proxy network.
  • Throttled writes: Separate metrics distinguish provider throttling from proposal/finalization errors.
  • Payer-signed outcome on one operator only: the other normally imports it at its next epoch, within about an hour. After two hours, read the payer outcomes from log line and the import counters and run node scripts/check-live.mjs outcomes; docs/operations-runbook.md, section 2, says what each refusal and stall means.

Section sources

Conclusion

Sigvara’s oracle service provides a transparent, auditable reputation system anchored on chain. Its REST API supports secure attestation submission, public score and evidence access, and operator controls. The epoch pipeline combines payment-backed attestations, optional chain-scanned payments, flags, external feedback, counterparty standing, and tenure into a normalized score. Checker mode adds independent recomputation and divergence reporting, enabling the slashing committee to act within challenge windows. Docker deployment, environment configuration, and Prometheus metrics support production operations.

Appendices

Docker Deployment Configuration

  • Service builds from oracle/Dockerfile, binds port 3030 on localhost, mounts oracle_state volume to /data, and joins a shared network.
  • Environment file is expected at ./oracle/.env.
  • docker-compose.mainnet.yml builds the primary, checker and watcher from one pinned commit (SIGVARA_REF), reads the signing key from a file secret, and runs the container as an unprivileged user on a read-only root filesystem.
graph TB
Compose["docker-compose.oracle.yml"] --> Service["oracle service"]
Service --> Volume["oracle_state:/data"]
Service --> Port["127.0.0.1:3030"]
Service --> Network["sigvara-mesh"]
Loading

Diagram sources

Section sources

Environment Variables Summary

  • Required: RPC URL, oracle private key (or ORACLE_PRIVATE_KEY_FILE, naming a file that holds it), identity address, reputation address.
  • Recommended: Admin token, EXPECTED_CHAIN_ID, state path, attestation cooldown, and TRUSTED_PROXIES behind a reverse proxy.
  • Optional: Epoch interval, host/port, scan ranges, fee registry, external ERC-8004 settings, payment verification and scanning parameters, job registry settings, transaction wait and fee caps (TX_WAIT_SECONDS, MAX_FEE_GWEI, MAX_TX_COST), the clock-skew limit (MAX_CLOCK_SKEW_SECONDS), and payer-signed outcomes (PAYER_OUTCOMES, PAYER_OUTCOMES_PUBLISH, PAYER_OUTCOMES_PEERS, PAYER_OUTCOMES_IMPORT_GRACE_HOURS, PAYER_OUTCOME_WINDOW_DAYS, PAYER_NEGATIVE_MIN_AMOUNT). Set the payer-outcome settings the same on every operator.

Section sources

Decentralization Strategy and Operator Coordination

  • Primary operator proposes and finalizes scores.
  • Checker operator recomputes pending scores, records divergences, and finalizes only when it agrees and the challenge window has elapsed. It proposes only into an empty slot, through proposeIfEmpty, to cover a silent primary. That call also replaces a pending proposal whose proposer has lost its authority, on the Arc testnet since 26 October 2026 (the September contract reverted for any pending proposal).
  • Watcher monitors checker availability and divergence alerts, re-checking live chain state before escalation. It also alerts when the checker is unreachable, cannot be read, is not a checker, or has not completed an epoch within MAX_SILENCE_MINUTES (counted from its start if it never has), and when its own polls keep failing.
  • Separation of hosts and RPC providers reduces shared failure modes.
  • Each operator lists the other in PAYER_OUTCOMES_PEERS and imports its payer-signed outcomes, so an outcome posted to one reaches both. A token holder's outcome stays with the operator it was posted to.

Section sources

Overview

Clone this wiki locally