SON
Squad Oracle Network
Disconnected
Trying to reach node...
Docs

What is Squad Oracle Network?

Squad Oracle Network (SON) is a decentralized, multi-feed price oracle for the SquadSwap ecosystem. Operators stake SQUAD tokens, fetch prices from exchanges and DEX pools, reach consensus via cryptographic signatures, and post results on-chain.

Consumer contracts read prices through the familiar Chainlink AggregatorV3Interface, so any existing integration works without code changes.

How a round works

Every feed goes through the same 5-step pipeline, once per heartbeat or whenever the price deviates enough to trigger an early update.

1
Fetch
Each operator node polls configured price sources in parallel: CEX REST APIs (Kraken, KuCoin, MEXC, Huobi for XMR/USD) for major assets, or DEX pool contracts via RPC for long-tail tokens. Each source has a weight (Kraken 3, KuCoin/MEXC 2, Huobi 1) reflecting liquidity depth — exchanges that delisted the asset are dropped from the per-feed source array but remain available for other pairs.
2
Aggregate
Collected prices go through weighted median → MAD outlier rejection (drops sources more than 3× median absolute deviation away) → TWAP smoothing across a rolling window. Result: one canonical price plus a confidence score.
3
Sign
Each operator signs the (feedId, price, timestamp, nonce) tuple with EIP-712 structured signing bound to the SquadOracle contract domain. Signatures can't be replayed across feeds or chains.
4
Submit
One operator (the submitter role this round) bundles 2N/3+1 signatures and calls submitPrice on the SquadOracle contract. The contract verifies every signature, runs the circuit breaker check, and stores the new round.
5
Read
Consumer contracts read through the Chainlink-compatible AggregatorV3 adapter. The staleness check enforces feed liveness — expired prices revert. One interface, any feed.

Glossary

Every value you see across the dashboard maps to one of these terms.

feeda price pair
A price pair tracked by the oracle, identified by feedId = keccak256("XMR/USD"). Each feed has its own config, sources, and adapter contract. Consumers select a feed by ID or pair string.
heartbeatmax interval between updates
The longest time allowed between two on-chain price posts. Even if the price doesn't move, the oracle forces a fresh submission every heartbeat. Short heartbeat → fresher data, higher gas cost. Long heartbeat → cheaper, but consumers see staler prices.
deviationearly-update threshold
Minimum price change (as a percentage of the last on-chain price) that triggers an early update without waiting for the next heartbeat. Low deviation → sensitive to small moves. High deviation → filters out market noise.
stalenessconsumer-side validity window
Maximum age a price can have when a consumer reads it. Adapters revert on prices older than this — a safety gate against dead or frozen oracles. Rule of thumb: staleness ≈ 2 × heartbeat (one missed heartbeat tolerated).
circuit breakersingle-round jump guard
Maximum price change allowed between two consecutive on-chain rounds. Exceeding it rejects the submission AND pauses the feed until a guardian unpauses it. Designed to catch manipulation, flash attacks, and fat-finger mistakes. Note: the contract does not revert — it pauses the feed and returns early, leaving the rest of the oracle functional.
min opssignature quorum
Minimum number of distinct operator signatures required for a valid price submission. Combined with the 2N/3+1 Byzantine Fault Tolerance rule, this ensures no single operator can post a price alone.
sourcesactive data inputs this round
How many data sources (CEX or DEX) responded successfully this round out of the total configured. Example: "4/6" means 4 of 6 CEX APIs returned within the timeout. Must reach minSources or the round is skipped.
confidenceaggregation quality score
A 0–100 percentage from the aggregator indicating how tightly the sources agreed. High confidence = tight spread, no outliers rejected. Low confidence = sources disagreed significantly, a warning signal for downstream consumers.
roundone on-chain submission
A single completed price update on-chain. Each feed has its own independent round counter (latestRoundId). Round IDs are monotonically increasing and used in Chainlink getRoundData(roundId).
adapterChainlink-compatible reader
Per-feed contract implementing the AggregatorV3Interface. Consumers point their existing Chainlink integration code at this address; it forwards calls to the SquadOracle core with staleness and pause checks.

Why CEX and DEX feeds use different parameters

The same parameters serve very different risk profiles depending on where price discovery happens. Here's the current production tuning:

ParameterXMR (CEX Agg)SQUAD (DEX TWAP)Why
Heartbeat1h5mCEX liquidity is deep → hourly refresh is fine. DEX pools drift faster → 5m keeps consumers current.
Deviation0.5%1.0%0.5% matters on a liquid CEX book. On thin DEX pools it's just noise; 1% filters spam updates.
Staleness2h15mAlways ≈ 2 × heartbeat. Short DEX window prevents arbitrage windows on low-liquidity assets.
Circuit Breaker20%10%Logic inverts: 20% XMR move is panic worth pausing. 10% SQUAD move already looks like pool manipulation.
Min Ops32XMR is a critical integration target — higher quorum. SQUAD is ecosystem-internal — 2-of-N acceptable for now.

All parameters are governance-tunable. These values will evolve as feeds gain integrations and historical data.

Run an operator node

Operators are the network. Each one fetches prices independently, signs them, and gossips with peers over libp2p — the contract accepts a price only when 2/3+1 of subscribed operators sign the same value. Adding more honest operators directly raises the cost of attacking any feed.

The mainnet node is open-source and packaged for one-command install on any Linux host. Public IP detection, libp2p announce-addresses, and peer auto-discovery are all handled for you — operators do not need to exchange IP addresses out-of-band.

1
Provision a server
Any Linux box with a static public IPv4 works. Minimum: 1 vCPU, 2 GB RAM, 10 GB disk, ports 9090 (HTTP) and 9091 (libp2p) reachable. Cloud VPS providers (DigitalOcean, Hetzner, AWS Lightsail) all fit comfortably in their lowest tiers.
2
Run the installer
A single curl-piped script installs Docker, generates a fresh operator wallet, downloads the node container, and registers a systemd service. Public IPv4 is auto-detected and written to the .env so libp2p announces a routable address — no manual IP swapping.
3
Fund + stake
Send a small amount of BNB to the new wallet (gas), plus the SQUAD stake floor (100,000 SQUAD at the time of writing — check the Staking page for the current minimum). Then approve and register on the Staking page.
4
Subscribe to feeds
Staking alone does NOT enroll you in any feed. On the same Staking page, click Subscribe under Feed Subscriptions for each pair you want to serve. Without this step the contract treats every submission as unauthorized — you'll burn gas and earn nothing.
5
Verify the mesh
Within ~10 seconds of startup, curl http://localhost:9090/health and confirm peerCount has risen to (subscribed_operators - 1). Logs show 'Multi-op round operators=N required=⌊N×2/3⌋+1' once a deviation triggers a real consensus round — that line confirms the contract registry agrees with the libp2p mesh view.
One-line install (Ubuntu / Debian, root required):
curl -fsSL https://raw.githubusercontent.com/SquadSwapDeFi/SquadSwapOracleNetwork/main/setup.sh | sudo bash

Production environments should download the script, review it, and only then execute. The full setup README — minimum requirements, firewall notes, log access tokens, update procedure — lives in the public-node-setup repository. Once your wallet is funded, complete steps 3 and 4 on the Staking page.

Heads-up: the SQUAD/USD feed uses an on-chain TWAP that needs ~24 minutes of node uptime to fill its window before the first valid value publishes. The XMR/USD feed starts submitting within minutes. Don't restart the container in a tight loop — the TWAP warmup resets every time.

Consuming a feed

Any contract that reads Chainlink can read SON. Point the aggregator address at the per-feed adapter (listed on the Feeds page) and use the standard interface:

import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract MyConsumer { AggregatorV3Interface public immutable oracle; constructor(address xmrUsdAdapter) { oracle = AggregatorV3Interface(xmrUsdAdapter); } function latestPrice() external view returns (int256) { (, int256 price, , uint256 updatedAt, ) = oracle.latestRoundData(); require(block.timestamp - updatedAt < 2 hours, "stale"); return price; // 8 decimals, e.g. 35498000000 == $354.98 } }

Prices use 8 decimals, matching Chainlink convention. The adapter reverts on stale or paused feeds, so your contract doesn't need extra liveness checks beyond the staleness requirement shown above.