Skip to content

Deployment and Configuration

RunTimeAdmin edited this page Nov 4, 2026 · 39 revisions

Deployment & Configuration

**Referenced Files in This Document** - [README.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/README.md) - [oracle/README.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/README.md) - [oracle/index.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/index.js) - [oracle/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) - [docker-compose.checker.yml](https://github.com/RunTimeAdmin/Sigvara/blob/main/docker-compose.checker.yml) - [docker-compose.oracle.vps.yml](https://github.com/RunTimeAdmin/Sigvara/blob/main/docker-compose.oracle.vps.yml) - [docker-compose.checker.vps.yml](https://github.com/RunTimeAdmin/Sigvara/blob/main/docker-compose.checker.vps.yml) - [docker-compose.mainnet.yml](https://github.com/RunTimeAdmin/Sigvara/blob/main/docker-compose.mainnet.yml) - [oracle/.env.mainnet.example](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/.env.mainnet.example) - [oracle/Caddyfile.oracle.example](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/Caddyfile.oracle.example)

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 how to deploy and operate the Sigvara oracle service, including environment configuration, Docker deployment for development and production, required infrastructure, checker mode operation, fee gating, external trust integration, health checks, monitoring, and security considerations. The oracle computes a normalized six-factor reputation score from payment-verified attestations, watchdog flags, tenure, counterparty standing, and optional ERC-8004 feedback, then proposes the result on-chain with a Merkle root over the evidence used.

The repository also includes a second operator running in checker mode that independently recomputes scores, compares them against pending on-chain proposals, and exposes divergence information for the slashing committee. A separate watcher component monitors checker divergence endpoints and alerts when actionable disagreements occur, and when the checker itself stops checking.

Section sources

Project Structure

The oracle is a Node.js service under oracle/. It reads blockchain state through an RPC endpoint, persists attestation and flag state to a mounted volume, and exposes HTTP endpoints for health, metrics, scoring previews, evidence retrieval, attestation submission, the payer-signed outcomes other operators import, flags, links, jobs, and checker diagnostics.

graph TB
subgraph "Oracle Service"
App["index.js"]
Store["Persistent State<br/>Attestations, Flags,<br/>Payment Events, Divergences"]
Chain["Chain Layer<br/>RPC Reads/Writes"]
External["External Feed<br/>ERC-8004 (Optional)"]
Jobs["Jobs Feed<br/>ERC-8183 (Optional)"]
PaymentScan["Payment Scanner<br/>Pull Mode (Optional)"]
end
subgraph "Infrastructure"
RPC["Blockchain RPC Endpoint"]
DB["Mounted Volume<br/>/data/oracle-state.json"]
Proxy["Reverse Proxy / TLS"]
Metrics["Prometheus / Monitoring"]
end
App --> Chain
App --> External
App --> Jobs
App --> PaymentScan
App --> Store
Chain --> RPC
Store --> DB
Proxy --> App
Metrics --> App
Loading

Diagram sources

Section sources

Core Components

  • Oracle process: loads environment configuration, initializes chain and external feed accessors, runs periodic epochs, handles HTTP endpoints, and persists operational state.
  • Persistent store: stores attestations, flags, links, payment events, scan checkpoints, divergences, cooldowns, and last persistence metadata.
  • Chain layer: reads agent identity, status, pending scores, challenge windows, epoch fees, and writes finalized or proposed scores through the oracle wallet.
  • External trust feed: optional ERC-8004 identity/reputation lookup for the externalScore factor.
  • Jobs feed: optional read-only ERC-8183 job registry observation.
  • Payment scanner: optional pull-based scanning of ERC-20 Transfer logs to credited payments without success outcomes.
  • Checker mode: independent recomputation and comparison against pending on-chain proposals; finalizes an agreed score, or proposes into an empty slot, only once the primary is CHECKER_GRACE_MINUTES late.
  • Fee gating: optional gating where only agents with active epoch fee coverage are scored, and uncovered agents are skipped.

Section sources

Architecture Overview

The oracle integrates multiple signal sources and writes computed scores to the protocol’s reputation contract. Production deployments typically place a reverse proxy in front of the service to expose only the intended public surface while keeping write and administrative endpoints loopback-only.

sequenceDiagram
participant Client as "Client"
participant Proxy as "Reverse Proxy"
participant Oracle as "Oracle Service"
participant Chain as "Blockchain RPC"
participant Reputation as "SigvaraReputation"
Client->>Proxy : GET /health
Proxy->>Oracle : GET /health
Oracle-->>Proxy : 200/503 with operational signals
Proxy-->>Client : Health response
Client->>Proxy : POST /attest
Proxy->>Oracle : POST /attest
Oracle->>Chain : Verify payment / read agent address
Chain-->>Oracle : Transaction receipt / agent info
Oracle->>Store : Record payment / cooldown
Oracle-->>Proxy : Accepted or error
Proxy-->>Client : Attestation result
Loading

Diagram sources

Detailed Component Analysis

Environment Variables and Operational Settings

The oracle validates required variables at startup and exits if they are missing. Recommended production settings include admin tokens, persistent state paths, and attestation cooldowns. Optional settings control epoch cadence, network binding, block scanning, fee gating, external trust feeds, checker mode, divergence tolerance, and payment verification behavior. Every numeric setting is also checked before anything starts: a value that is set and is not a plain number in range stops the process with the setting named, and a blank value means unset.

Variable Required / Default Purpose
RPC_URL Required Ethereum JSON-RPC endpoint for the target chain
ORACLE_PRIVATE_KEY Required unless ORACLE_PRIVATE_KEY_FILE is set Private key for the oracle wallet
ORACLE_PRIVATE_KEY_FILE Empty Path to a file holding that key, in place of ORACLE_PRIVATE_KEY (setting both stops the process). A file secret keeps the key out of docker inspect and the container's environment, and a malformed key is refused with a message that never contains it
IDENTITY_ADDRESS Required SigvaraIdentity contract address
REPUTATION_ADDRESS Required SigvaraReputation contract address
EXPECTED_CHAIN_ID Recommended The chain this oracle is for: 5042 Arc mainnet, 5042002 testnet. With it set, nothing is read for scoring or signed while the RPC reports another chain, and a state file saved on another chain is refused. Unset, the oracle signs for whatever chain RPC_URL serves and warns at startup
EPOCH_HOURS Default 24 Hours between automatic epoch runs
DIVERGENCE_TOLERANCE Checker default 3 Numeric tolerance used when comparing checker vs pending scores. In checker mode a non-number, a negative value or one above 10 stops the process; a primary ignores it
HOST Default 127.0.0.1 HTTP bind address
PORT Default 3030 HTTP port
TRUSTED_PROXIES Empty Addresses or CIDR ranges whose X-Forwarded-For is believed besides loopback, for example the proxy's Docker network. Without it, behind a proxy, every visitor shares one rate-limit bucket
ORACLE_SCOPES_<NAME> Unrestricted Limits the token ORACLE_TOKEN_<NAME> to some of attest, flag, link and epoch
TX_WAIT_SECONDS Default 60 How long a transaction is waited for before it is replaced once, at the same nonce and twice the fee (5 to 3600)
MAX_FEE_GWEI Default 1000 Highest maxFeePerGas the oracle will sign
MAX_TX_COST Default 1 Most one transaction may cost at its worst, in the native token
MAX_CLOCK_SKEW_SECONDS Default 600 How far the chain's latest block time may be from the host's clock before the epoch is not run
FROM_BLOCK Default 0 Block to start scanning AgentRegistered events from
LOG_CHUNK_SIZE Default 2000 Max blocks per eth_getLogs call
FEE_REGISTRY_ADDRESS Empty Enables fee-gated scoring when set and on-chain fee is non-zero
EXTERNAL_RPC Empty RPC for ERC-8004 external score lookups
EXTERNAL_IDENTITY_ADDRESS Empty ERC-8004 Identity contract
EXTERNAL_REPUTATION_ADDRESS Empty ERC-8004 Reputation contract
EXTERNAL_PUBLIC_FUNDERS Empty Wallets that fund everyone, such as a faucet or an exchange, comma-separated; they end a reviewer's funding trace. Both operators must list the same ones
EXTERNAL_LINK_FROM_BLOCK Empty The block to read links made on chain from (the ERC-8004 identity's sigvara metadata key), only when the registry is on the chain the oracle scores (index.js:117-121, index.js:526-530). Both operators must use the same block. While it is set, a registry whose chain cannot be read stops all scoring
ORACLE_MODE Not checker by default Set to checker for the second bonded operator
CHECKER_GRACE_MINUTES Checker default twice EPOCH_HOURS How late the primary must be before a checker finalizes an agreed score or fills an empty slot. Refused when not a number or shorter than the checker's own epoch; set it above the primary's epoch
ORACLE_ADMIN_TOKEN Empty Bearer token for authenticated endpoints. A non-loopback bind with no write credential at all is refused at startup
ORACLE_STATE_PATH Default /data/oracle-state.json Path to persistent state file
ATTEST_COOLDOWN_MS Default 3600000 Minimum time before same attester can re-attest same agent
PAYMENT_VERIFICATION Default off off or required; mainnet should use required
PAYMENT_ASSET Empty when verification off ERC-20 asset used for verified payments
PAYMENT_MIN_AMOUNT Default 0 Smallest payment that counts, per sender, in the asset's base units. Must be above 0 when verification is required, or the process refuses to start
PAYMENT_MIN_CONFIRMATIONS Default 1 Confirmations before settlement is accepted
PAYMENT_FEE_UNIT Default 100000000 Base units of volume per point of fee score
PAYMENT_HALF_LIFE_DAYS Default 90 Decay half-life for payment evidence
PAYMENT_MAX_PER_PAYER Default 4 Cap on points per payer
PAYMENT_TRUST_WEIGHT Default 1 Counterparty standing weight
PAYER_OUTCOMES Default off Payer-signed outcomes: off, positive (only "delivered") or both. Needs PAYMENT_VERIFICATION=required, EXPECTED_CHAIN_ID and REPUTATION_ADDRESS. Set it the same on every operator
PAYER_OUTCOMES_PUBLISH Default off on serves each payer's signed payload in /evidence and lists them at GET /outcomes. Stays off on mainnet until the retention policy covers copies held by other operators
PAYER_OUTCOMES_PEERS Empty Base URLs of the other operators to import payer-signed outcomes from at the start of each epoch. Off on mainnet for the same reason
PAYER_OUTCOMES_IMPORT_GRACE_HOURS Default 6 How long after an outcome's deadline an import from a peer may still arrive
PAYER_OUTCOME_WINDOW_DAYS Default 14 How long after the payment a payer-signed outcome may replace a token holder's
PAYER_NEGATIVE_MIN_AMOUNT Default PAYMENT_MIN_AMOUNT Smallest payment leg whose payer's "not delivered" counts
PAYMENT_SCAN_ENABLED Default 0 Enable pull-based payment scanning
PAYMENT_SCAN_FROM_BLOCK Default FROM_BLOCK First block for scan catch-up
PAYMENT_SCAN_CHUNK Default LOG_CHUNK_SIZE Blocks per scan request
PAYMENT_SCAN_CONFIRMATIONS Default 6 Safety margin behind head for scans
PAYMENT_SCAN_PACE_MS Default 500 Milliseconds between scan requests; 0 opts out
PAYMENT_CHAIN_IDS Unset Chains to read payments from beside the scored one (ADR 0009), comma-separated ids. Off by default: unset, no chain is read (payment-chains.js:37-39). Must not name the scored chain, and needs PAYMENT_VERIFICATION=required, PAYMENT_SCAN_ENABLED=1 and EXPECTED_CHAIN_ID, or the process refuses to start (payment-chains.js:140-142, payment-chains.js:171-175, index.js:182-191)
PAYMENT_CHAIN_<id>_RPC_URL Required per chain The chain's RPC, https, or http only to this host or a private network. Never logged or served. It must answer eth_getCode at a payment's own block, and a read that takes more than 30 seconds fails. Bring your own: Base Sepolia's public endpoint limits eth_getLogs to 200 blocks (payment-chains.js:99-114, README.md:1117)
PAYMENT_CHAIN_<id>_ASSET Required per chain The ERC-20 payments settle in (payment-chains.js:148-149)
PAYMENT_CHAIN_<id>_FROM_BLOCK Required per chain First block. Both operators use the same one (README.md:1119)
PAYMENT_CHAIN_<id>_FEE_UNIT Required per chain Base units of this asset per point. Amounts are converted into the scored chain's fee units before they are scored (payment-chains.js:323-337)
PAYMENT_CHAIN_<id>_MIN_AMOUNT Required per chain, above 0 As PAYMENT_MIN_AMOUNT, in this asset's base units (payment-chains.js:158-159)
PAYMENT_CHAIN_<id>_MODE Default shadow shadow scans beside the epoch, reports what it would credit and credits nothing. on is scanned in the epoch, before scoring, and credits. A shadow scan keeps its own cursor, so switching on starts at FROM_BLOCK (payment-chains.js:150, payment-chains.js:201-204, README.md:1121)
PAYMENT_CHAIN_<id>_DELAY_SECONDS Default 3600, at least 1800 How old a payment must be before it enters a score. Must be longer than the chain's finality lag, or every epoch is held (payment-chains.js:46-49, README.md:1122)
PAYMENT_CHAIN_<id>_CHUNK, _PACE_MS, _MAX_BLOCKS Defaults 200, 500, 36000 Blocks per getLogs call, milliseconds between calls, and blocks one scan covers at most (payment-chains.js:50-56)
JOBS_REGISTRY_ADDRESS Unset Enables read-only ERC-8183 job observation
JOBS_RPC Default RPC_URL Separate RPC for jobs registry
JOBS_FROM_BLOCK Default 0 Earliest block for jobs scan
JOBS_MAX_SPAN Default 50000 Widest range one jobs request may scan

Payment chains on the testnet operators on 4 November 2026: Base Sepolia ran on neither. The primary read none, and the checker read Elysium testnet (99801) in shadow, which credits nothing and changes no score (0009-payments-on-base.md:236-237, operations-runbook.md:561-563). Base mainnet is decided at the mainnet launch (0009-payments-on-base.md:230). oracle/.env.example holds both chains' settings, commented out (.env.example:325-353). Both operators need the same values before a chain goes on, and switching on or back has an order (README.md:1164-1173). With a chain on, TX_WAIT_SECONDS must leave room for a charge and a proposal, or the process refuses to start (index.js:192-198).

Checker-specific constraints:

  • Checker mode refuses to run with FEE_REGISTRY_ADDRESS set because a checker does not bill: a registry from before the once-per-epoch charge would debit the agent a second time for one scoring epoch, and a current one would refuse the second charge while the two operators raced for the first.
  • Checker epoch cadence should be below half the on-chain challenge window so proposals are observed before expiring.
  • DIVERGENCE_TOLERANCE is validated at startup; an unparseable value would make comparisons silently agree.
  • ORACLE_MODE must be unset, primary or checker exactly; any other value, Checker included, stops the process.

Section sources

Docker Deployment

Development Deployment

Use the development compose file to build the oracle image locally and mount a persistent state volume. The HTTP port is bound to localhost only, which requires a reverse proxy for public exposure.

Key behaviors:

  • Builds from ./oracle/Dockerfile.
  • Uses env_file: ./oracle/.env.
  • Publishes 127.0.0.1:3030:3030.
  • Mounts oracle_state:/data.
  • Joins a shared external network named sigvara-mesh.
flowchart TD
Start(["docker compose -f docker-compose.oracle.yml up"]) --> Build["Build oracle image"]
Build --> Run["Run container sigvara-oracle"]
Run --> Bind["Bind 127.0.0.1:3030"]
Run --> Volume["Mount /data for persistent state"]
Run --> Network["Join sigvara-mesh network"]
Bind --> Proxy["Reverse proxy forwards public endpoints"]
Volume --> Store["State survives restarts"]
Loading

Diagram sources

Section sources

Production Deployment on Bare VPS

Production uses a compose file that fetches one pinned commit of the repository inside the container, copies only the oracle source files, installs dependencies with npm ci, and runs node index.js. This ensures the running artifact matches a reviewed commit rather than a host-side checkout, and a restart runs the same commit again.

Key behaviors:

  • Uses node:24-alpine, pinned by digest.
  • Fetches the one commit named by SIGVARA_COMMIT in the environment file into /src and refuses to start unless the checkout equals it. /health reports that commit.
  • Copies oracle/*.js, package.json, and package-lock.json into /app.
  • Removes test files.
  • Runs npm ci --omit=dev --no-audit --no-fund.
  • Exposes 127.0.0.1:3030:3030.
  • Mounts oracle_state:/data and oracle_app:/app.
  • Shares an external network named sigvara-web.
flowchart TD
Start(["docker compose up"]) --> Clone["Clone Sigvara main branch"]
Clone --> Copy["Copy oracle source and lockfiles"]
Copy --> Install["Install dependencies with npm ci"]
Install --> Run["Exec node index.js"]
Run --> Health["Expose /health and /metrics via proxy"]
Run --> State["Persist state to oracle_state volume"]
Loading

Diagram sources

Section sources

Mainnet Deployment

docker-compose.mainnet.yml runs the primary, the checker and the watcher from one file. A host runs exactly one of them, chosen by COMPOSE_PROFILES in its .env (oracle, checker or watcher), and the three belong on three separate hosts. oracle/.env.mainnet.example and its checker and watcher siblings are the starting points; one role's file is not copied to another.

Key behaviors:

  • Builds from SIGVARA_REF, a full 40-character commit hash, not from main. A deploy is a change to that line and docker compose up -d; a restart reruns the same image and fetches nothing. The primary and the checker get the same commit unless a change is being rolled out one operator at a time, because differing scoring code shows up as divergence.
  • The build has no key in its environment, and nothing in the running container runs git, a package manager or a download at start.
  • The signing key is a file secret (secrets/oracle_key, owner uid 1000, mode 0400) read through ORACLE_PRIVATE_KEY_FILE. The checker uses a different key from the primary.
  • Runs as an unprivileged user on a read-only root filesystem with every capability dropped, a limit of 256 processes, 768 MB of memory and one CPU. The only writable places are /data and /tmp.
  • HOST, PORT, ORACLE_STATE_PATH and ORACLE_PRIVATE_KEY_FILE are fixed in the compose file and override .env.
  • Publishes 127.0.0.1:${HOST_PORT} (3030 for the primary, 3031 for the checker). Only the primary joins the external edge network (EDGE_NETWORK, default sigvara-edge) that it shares with the reverse proxy.
  • The watcher has no key, no volume and no inbound port, and its image health check is switched off.
  • The mainnet examples set EXPECTED_CHAIN_ID=5042, PAYMENT_VERIFICATION=required, PAYMENT_SCAN_ENABLED=1 and a TRUSTED_PROXIES to fill in, and leave the epoch-fee registry, the ERC-8004 feed and the jobs reader unset at launch. They also leave payer-signed outcomes off (PAYER_OUTCOMES=off, with publishing and peers off until the retention policy covers copies held elsewhere). The scoring settings must be identical on the primary and the checker.

Section sources

Checker Mode Deployment

The checker is a second bonded operator that does not race the primary. It recomputes scores and records divergences, and it finalizes an agreed score, or proposes into an empty slot, only once the primary is CHECKER_GRACE_MINUTES late (default twice EPOCH_HOURS, two hours on testnet). Until that rule went live on both operators on 3 November 2026, the checker sent 35 of the first 262 proposals, each within about an hour after a finalization had emptied the slot, 33 of them the checker's own, and those had no second computation (epoch-policy.js:147-221, divergence-log.md:333-392).

Development compose:

  • Builds from local source.
  • Uses ./oracle/.env.checker.
  • Publishes 127.0.0.1:3031:3030.
  • Mounts checker_state:/data.

Production compose:

  • Fetches the pinned commit inside the container.
  • Uses npm ci.
  • Exposes 127.0.0.1:3031:3030.
  • Mounts checker_state:/data and checker_app:/app.

Important constraints:

  • Do not share the primary’s state volume.
  • Do not set FEE_REGISTRY_ADDRESS in checker mode.
  • Use a different RPC provider and hardware from the primary.
  • Ensure the checker wallet is bonded and admitted, and has ORACLE_ROLE.
  • On main the bond can belong to a cold key that names the checker's wallet with setSigner, so the host's key never holds it. The Arc testnet contracts have supported this since 26 October 2026, but both operators there still sign with the key that holds the bond.
flowchart TD
Start(["Deploy checker"]) --> Env["Set ORACLE_MODE=checker"]
Env --> Bond["Bond wallet and admit operator"]
Bond --> Role["Grant ORACLE_ROLE"]
Role --> NoFee["Unset FEE_REGISTRY_ADDRESS"]
NoFee --> Cadence["Set EPOCH_HOURS below half challenge window"]
Cadence --> Compare["Compare scores with tolerance"]
Compare --> Action{"Decision"}
Action --> |Agree| Finalize["Finalize once the window has passed by CHECKER_GRACE_MINUTES"]
Action --> |Disagree| Record["Record divergence"]
Action --> |Skip| Wait["Wait for next epoch"]
Loading

Diagram sources

Section sources

Reverse Proxy and Public Surface

The example Caddy configuration publishes only the intended public endpoints: /health, /score/*, /evidence/*, /badge/*, /outcomes, and POST /attest. All other paths return 404. Write endpoints such as /flag, /flag/resolve, /link, and /epoch remain loopback-only.

Security-relevant proxy behaviors:

  • /attest is published but not opened: positive attestations carrying verified payments need no token, and record no outcome without one; an outcome the payer signed needs no token either; negative attestations require a bearer token.
  • Read endpoints are rate-limited by the oracle using socket address or X-Forwarded-For, which it believes only from loopback or from a peer in TRUSTED_PROXIES. Behind a proxy in a container, set TRUSTED_PROXIES to the proxy network or every visitor shares one bucket.
  • CORS is restricted to the site origin for browser access.
  • Cache headers protect freshness without caching writes.
  • Security headers include HSTS, content-type sniffing prevention, and referrer policy.
flowchart TD
Public["Public Domain"] --> Caddy["Caddy Reverse Proxy"]
Caddy --> Health["GET /health"]
Caddy --> Score["GET /score/*"]
Caddy --> Evidence["GET /evidence/*"]
Caddy --> Badge["GET /badge/*"]
Caddy --> Outcomes["GET /outcomes"]
Caddy --> Attest["POST /attest"]
Caddy --> Deny["404 for everything else"]
Admin["Loopback Only"] --> Flag["POST /flag"]
Admin --> Resolve["POST /flag/resolve"]
Admin --> Link["POST /link"]
Admin --> Epoch["POST /epoch"]
Loading

Diagram sources

Section sources

Health Check Endpoints

GET /health returns operational signals suitable for production alerting. It returns 200 when healthy and 503 when critical conditions exist, such as unwritable state path, failed state persistence (while it lasts /attest answers 202 with durable: false instead of 200), or stale scoring. Scoring is stale after two epoch intervals with no successful epoch; before the first success the two intervals are counted from process start, so a primary that has never completed an epoch does not stay green.

Important fields:

  • ok: overall health.
  • epochMs: configured epoch interval.
  • uptimeSeconds: process uptime.
  • lastSuccessfulEpochMs and timeSinceLastEpochMs: scoring recency.
  • storeWritable and statePersisted: storage health.
  • stateSource: file, fresh (there was no state file) or backup (the state file could not be read and the previous save was used).
  • chainId and expectedChainId: the chain the RPC was last seen on, and the one EXPECTED_CHAIN_ID names (null when unset).
  • commit: reported git revision of the cloned or built code, or null when the deployment does not report one. It catches a box that missed a deploy; it is not a trust anchor.
  • externalFeed: status of ERC-8004 external feed.
  • paymentScan and paymentScanBlock: status of pull-based payment scanning, and how far it has got.
  • paymentChains and paymentChainHold: only with a payment chain configured. Each chain's mode, asset, fromBlock, fee unit, minimum, delay, cursor and last scan, so two operators can compare what they must share, and what held back the last epoch, or null (index.js:2454-2460, payment-chains.js:362-380).
  • jobsFeed: status of ERC-8183 job feed.

Section sources

Monitoring Setup

GET /metrics exposes Prometheus-format metrics. Operators should scrape this endpoint and monitor:

  • Epoch lifecycle counters.
  • Propose and finalize attempts and successes.
  • Attestation results.
  • Flag operations.
  • Rate limits.
  • Checker comparisons and divergences.
  • Proposal rejection count.
  • Evidence cache hit rates.
  • Payment scan canary failures.
  • Payment chain scans, the ones that could not complete, and epochs held back by a scan that was behind (sigvara_oracle_payment_chain_scans_total, sigvara_oracle_payment_chain_scan_errors_total, sigvara_oracle_epochs_held_by_payment_chain_total, metrics.js:350-363).
  • Transactions refused for their fee or sent and never seen mined (sigvara_oracle_tx_refused_total).
  • Epoch-fee charges, including the ones skipped because the contract would have refused the proposal.
  • Pending proposals stranded behind a proposer that lost its authority.
  • The import of payer-signed outcomes from other operators: imported, refused, stopped early and conflicting (sigvara_oracle_payer_outcomes_imported_total, sigvara_oracle_payer_outcome_import_refused_total, sigvara_oracle_payer_outcome_import_errors_total, sigvara_oracle_payer_outcome_conflicts_total).

Recommended alerts:

  • /health returning 503.
  • Epoch failure counter increasing.
  • Attestation cooldown abuse spikes.
  • Payment scan canary failures.
  • sigvara_oracle_payment_chain_scan_errors_total rising, or any run of sigvara_oracle_epochs_held_by_payment_chain_total: that chain's RPC, or the scored chain's, needs a look (index.js:1149-1152, metrics.js:71-72).
  • Checker divergence activity.
  • sigvara_oracle_tx_refused_total rising, and sigvara_oracle_fee_charges_total{result="skipped"} staying high.
  • stateSource on /health reading anything but file after a restart.

Section sources

Fee Gating Configuration

When FEE_REGISTRY_ADDRESS is set and the on-chain epoch fee is non-zero, the oracle gates scoring on fee coverage. Agents without coverage are skipped during the epoch run. In checker mode, FEE_REGISTRY_ADDRESS must be unset: a checker does not bill, and a second operator charging would either debit the agent twice on a registry from before the once-per-epoch charge or race the primary for the single charge a current one allows. SigvaraEpochFees has never been deployed and is left out of the mainnet launch, so the mainnet examples leave this unset.

Operational implications:

  • Gating is checked once per epoch.
  • Covered agents proceed to scoring.
  • Uncovered agents are logged and skipped.
  • Before charging, the primary asks the contract whether the proposal would be accepted. If the answer is no, or it cannot be asked, the agent is skipped for the epoch and not charged.
  • An agent the registry has already charged in the same epoch is scored without a second charge.
  • Checker mode enforces no fee registry to prevent duplicate charges.

Section sources

External Trust Integration

External trust comes from ERC-8004 identity and reputation registries. When EXTERNAL_RPC, EXTERNAL_IDENTITY_ADDRESS, and EXTERNAL_REPUTATION_ADDRESS are all set, linked agents receive an externalScore derived from recognized feedback tags. If any of these are unset, externalScore remains zero.

Health reporting distinguishes three cases:

  • externalFeed: disabled when not configured.
  • externalFeed: configured when reachable.
  • externalFeed: unreachable when configured but RPC cannot be reached.
  • externalChainId reports the actual chain ID reached by the provider.

Section sources

Checker Mode Operation

Checker mode provides independent recomputation and divergence detection. It:

  • Recomputes every agent’s score.
  • Compares its result against the pending on-chain proposal, scored at the proposal's proposedAt over the evidence as it stood then.
  • Records divergences when disagreement exceeds tolerance.
  • Finalizes an agreed score, or proposes into an empty slot through proposeIfEmpty, only once the primary is CHECKER_GRACE_MINUTES late: the window past by that long, or the slot empty that long, counted from the latest of the last finalized score, registration and the first epoch it found the slot empty. A pending proposal whose proposer has lost its authority counts as empty and is covered at once, on the Arc testnet since 26 October 2026 (the September contract reverted for any pending proposal).
  • Counts what it leaves to the primary in sigvara_oracle_checker_deferred_total{action}.
  • Never finalizes a disputed score.
  • Never overwrites a disputed proposal.

Startup validation:

  • Rejects invalid DIVERGENCE_TOLERANCE.
  • Refuses checker mode with FEE_REGISTRY_ADDRESS.
  • Refuses a CHECKER_GRACE_MINUTES that is not a number or is shorter than the checker's own epoch.
  • Warns if checker epoch cadence is too slow relative to the challenge window.

Divergence endpoints:

  • GET /divergence: list recent divergences.
  • GET /divergence/:didHash: divergence for a specific agent.
  • Returns 404 on a primary because absence means “this oracle does not check,” not “checked and found nothing.”

Section sources

Step-by-Step Deployment Instructions

Prerequisites

  • Node.js runtime compatible with the oracle package.
  • Access to a blockchain RPC endpoint.
  • An oracle wallet with ORACLE_ROLE on SigvaraReputation.
  • For production, a reverse proxy terminating TLS.
  • For checker mode, a separate wallet, hardware, RPC provider, and state volume.
  • For fee gating, a deployed SigvaraEpochFees contract and funded agents.
  • For external trust, ERC-8004 registry addresses and a reachable RPC.

Development Steps

  1. Copy .env.example to .env in the oracle directory.
  2. Configure RPC_URL, ORACLE_PRIVATE_KEY, IDENTITY_ADDRESS, and REPUTATION_ADDRESS.
  3. Install dependencies with npm install.
  4. Run the oracle with node index.js.
  5. Run tests with node --test.
  6. Deploy with docker compose -f docker-compose.oracle.yml up -d.

Section sources

Production Steps

  1. Prepare a bare VPS with Docker.
  2. Place docker-compose.oracle.vps.yml and .env in /docker/sigvara-oracle.
  3. Configure all required and recommended environment variables, including EXPECTED_CHAIN_ID and, behind a reverse proxy, TRUSTED_PROXIES.
  4. Mount a persistent volume for ORACLE_STATE_PATH.
  5. Set ORACLE_ADMIN_TOKEN before exposing the service.
  6. Fund the oracle wallet with native gas.
  7. Grant ORACLE_ROLE on SigvaraReputation.
  8. Set PAYMENT_VERIFICATION=required and PAYMENT_ASSET for mainnet-like trust.
  9. Bond the oracle wallet in SigvaraOracleBond if operatorBond is set.
  10. Configure a reverse proxy using the example Caddy configuration.
  11. Scrape /metrics with Prometheus and alert on /health.
  12. Copy the state volume off the host every night and run the restore drill once a quarter (operations-runbook.md:49-174).

Section sources

Checker Mode Steps

  1. Create a separate wallet and fund it with SVR bond.
  2. Deposit bond on SigvaraOracleBond.
  3. Admit the checker wallet as an operator.
  4. Grant ORACLE_ROLE on SigvaraReputation.
  5. Set ORACLE_MODE=checker.
  6. Set a separate ORACLE_PRIVATE_KEY.
  7. Leave FEE_REGISTRY_ADDRESS unset.
  8. Set EPOCH_HOURS below half the challenge window.
  9. Use a different RPC provider and host.
  10. Mount a separate state volume.
  11. Set PAYER_OUTCOMES, PAYER_OUTCOMES_PUBLISH and PAYER_OUTCOMES_PEERS as the primary does, each operator naming the other in PAYER_OUTCOMES_PEERS.
  12. Deploy with docker compose -f docker-compose.checker.yml up -d or the VPS compose file.
  13. Monitor /divergence and configure a watcher if needed.

Section sources

Security Considerations

Private Key Management

  • ORACLE_PRIVATE_KEY controls on-chain writes and must be kept secret.
  • The key is loaded from ORACLE_PRIVATE_KEY_FILE (preferred in production: a mounted file secret stays out of docker inspect, /proc/<pid>/environ and child processes) or from ORACLE_PRIVATE_KEY, and used by the chain layer for propose/finalize transactions.
  • The chain layer will not sign a transaction whose fee is above MAX_FEE_GWEI or whose worst-case cost is above MAX_TX_COST.
  • Do not log private keys.
  • Rotate keys through the protocol’s identity rotation process when applicable.
  • Keep checker and primary wallets separate.
  • On main an operator can keep its bond on a cold key and name the oracle's signing key with setSigner. clearSigner() revokes that key in one transaction: it can no longer propose, and what it proposed cannot be finalized. The Arc testnet contracts have had the signer registry since 26 October 2026, and neither operator uses it yet.

Section sources

Network Configuration

  • Bind the oracle to 127.0.0.1 unless explicitly exposed through a reverse proxy.
  • Use a reverse proxy to terminate TLS and restrict public endpoints.
  • Ensure X-Forwarded-For is handled correctly so rate limiting works behind proxies: the oracle reads it from the right and believes it only from loopback or a peer in TRUSTED_PROXIES.
  • Keep write endpoints like /flag, /flag/resolve, /link, and /epoch loopback-only.
  • Exclude /metrics from public proxy exposure if it reveals operator-specific telemetry.

Section sources

Token and Credential Management

  • Use distinct bearer tokens for different integrations when configuring admin credentials.
  • Revoking a token means deleting its variable from .env and recreating the container with docker compose up -d. A plain restart keeps the old environment, so the token keeps working.
  • Positive attestations with verified payments do not require a token (without one, the payment is recorded and no outcome), and neither does an outcome the payer signed; negative attestations do.
  • Avoid sharing a single admin token across services when revocation granularity matters.
  • Restrict an integration's token to the writes it needs with ORACLE_SCOPES_<NAME>; a token shorter than 32 characters is named in a startup warning.

Section sources

Dependency Analysis

The oracle depends on:

  • Node.js runtime version specified in the package manifest.
  • dotenv for environment loading.
  • ethers for blockchain interaction.
  • Blockchain RPC endpoints for reading and writing protocol state.
  • Mounted volumes for persistent state.
  • Optional external RPC for ERC-8004 feedback.
  • Optional ERC-8183 job registry RPC.
  • Reverse proxy for TLS and public endpoint routing.
  • Prometheus or compatible tools for metrics scraping.
graph TB
Package["@sigvara/oracle"]
Runtime["Node.js >= 18"]
DotEnv["dotenv"]
Ethers["ethers"]
RPC["Blockchain RPC"]
ExternalRPC["External RPC"]
JobsRPC["Jobs RPC"]
Volume["Persistent Volume"]
Proxy["Reverse Proxy"]
Metrics["Prometheus"]
Package --> Runtime
Package --> DotEnv
Package --> Ethers
Ethers --> RPC
Package --> ExternalRPC
Package --> JobsRPC
Package --> Volume
Proxy --> Package
Metrics --> Package
Loading

Diagram sources

Section sources

Performance Considerations

  • Epoch reads are chunked to avoid releasing thousands of concurrent RPC calls at once.
  • ethers batches nearby calls into fewer JSON-RPC requests.
  • Payer score resolution is cached per epoch to reduce repeated chain reads.
  • Payment scanning uses backoff and pacing to respect RPC rate limits.
  • Evidence responses are cached to reduce expensive Merkle tree rebuilds.
  • Checker mode recomputation avoids unnecessary writes and focuses on comparison.
  • Health checks distinguish writable probe from actual persisted writes because large state files can fail after small probes pass.
  • The state file is written in pieces of about 1 MB, so the memory peak of a save is the live state plus one piece. Size the container for that peak, not for the size of the file (the mainnet compose file limits it to 768 MB).

Troubleshooting Guide

Health Check Returns 503

Check:

  • Whether the state path is writable.
  • Whether the last state write succeeded.
  • Whether the last successful epoch occurred within two times the epoch interval (counted from process start if none has completed).
  • Whether epochs are being refused: an epoch does not run while the RPC reports a chain other than EXPECTED_CHAIN_ID, or a clock more than MAX_CLOCK_SKEW_SECONDS from the host's.
  • Whether the commit reported by /health matches the expected deployment.

Section sources

Attestation Errors

Common statuses:

  • 200: accepted and saved.
  • 202: accepted, but the oracle could not write its state file (durable: false). Do not resend; /health is 503 until a save works.
  • 400: malformed body, unknown agent, or a success that is not a boolean.
  • 401: no token, and the attestation is neither a positive one backed by a verified payment nor a payer-signed outcome.
  • 402: payment did not check out, including one made before the agent registered (before_registration, README.md:354).
  • 403: the token is restricted to other scopes.
  • 409: settlement already credited, or its outcome already recorded. For a payer-signed outcome the body carries sameOutcome, and for outcome_recorded the held outcome's heldOutcomeHash when the payer signed the outcome held.
  • 429: cooldown active.
  • 502: RPC failure; retry.

Verify:

  • PAYMENT_VERIFICATION setting.
  • PAYMENT_ASSET when verification is required.
  • Transaction hash validity.
  • Payer is not the agent or operator.
  • Confirmation requirements.

Section sources

Checker Disagreements

Investigate:

  • DIVERGENCE_TOLERANCE parsing and value.
  • Whether the checker epoch cadence is shorter than the challenge window.
  • Whether both operators have identical payment evidence and the same payer-signed outcomes (node scripts/check-live.mjs outcomes), and whether the record's evidenceMatchesProposal is true.
  • Whether the proposal is still within the challenge window.
  • Whether the checker is configured without FEE_REGISTRY_ADDRESS.

Use:

  • GET /divergence and GET /divergence/:didHash.
  • Compare evidence roots and payment events.
  • Review /health fields for external feed and payment scan status.

Section sources

Payment Scan Issues

Check:

  • PAYMENT_SCAN_ENABLED.
  • PAYMENT_ASSET.
  • PAYMENT_SCAN_CONFIRMATIONS.
  • PAYMENT_SCAN_PACE_MS.
  • Canary verdict: passed, failed, or unavailable.
  • Whether both operators enable or disable scanning consistently.
  • For a payment chain, paymentChains on /health, and that lastBlockTs moves between epochs. canary: "unavailable" is what a shadow scan with nothing to credit reports. On Elysium, could not coalesce error (rpc -32005) means one call would return more than 10,000 logs, and the scan keeps failing until CHUNK is lowered (operations-runbook.md:581-590).

Section sources

Conclusion

The Sigvara oracle is a production-grade reputation computation service that integrates blockchain data, optional external trust feeds, and optional job registry observations. Correct deployment requires careful environment configuration, persistent state management, secure network exposure, and robust monitoring. Checker mode adds independent verification and divergence visibility, while fee gating and payment verification strengthen economic security. Operators should treat private keys, RPC endpoints, and admin tokens as sensitive assets and validate their deployment against the health and metrics surfaces before trusting the service in production.

Appendices

Quick Reference: Required Infrastructure

  • Blockchain RPC endpoint for the target chain.
  • Oracle wallet with ORACLE_ROLE.
  • Persistent volume for oracle state.
  • Reverse proxy with TLS for public endpoints.
  • Prometheus or compatible metrics scraper.
  • Optional: ERC-8004 RPC and registry addresses.
  • Optional: ERC-8183 job registry address and RPC.
  • Optional: SigvaraEpochFees contract for fee gating.

Section sources

Overview

Clone this wiki locally