Skip to content

AlphaTest mode: test dollars and BNB Testnet — no real money. Learn more

Developers

Become an oracle

Anyone can measure SaTT posts. Bring your own data sources, sign what you see with your own key, publish your prices — and get paid for each report that makes it into a final quorum.

Step by step

  1. 1
    Generate a dedicated key

    A fresh EVM key used only by your oracle (never a treasury key). Its address is your oracle identity.

  2. 2
    Register

    Sign an OracleRegistration (name, endpoint, networks) and POST /register. Your status is “pending” until a SaTT admin approves it.

  3. 3
    Publish your offer

    Sign an OracleOffer: networks, metrics, freshness, prices per measurement with volume tiers (USD for dollar pools, token for crypto pools), optional subscription per pool, SLA. New versions apply to new jobs only.

  4. 4
    Poll jobs

    GET /jobs with three headers: your address, a deadline and your signature of OracleRequest{action: "jobs"}. Each job gives the post, its network, the creator wallet and SaTT’s signed ownership proof.

  5. 5
    Measure with your own sources

    Official APIs, your scraper, a panel — your choice. Report only what you observed; unknown metrics are 0.

  6. 6
    Sign the MetricsReport

    Copy adpool, dataHash, platform and jobId from the job, add the five metrics, the time you observed them and a short source label, sign and POST /reports.

Code samples

TypeScript (viem)
import { privateKeyToAccount } from 'viem/accounts';
import { metricsReportTypedData, oracleRequestTypedData } from '@satt/shared';

const API = '/api/oracle-network/v1';           // on https://satt.com's API host
const me = privateKeyToAccount(process.env.ORACLE_KEY as `0x${string}`);
const { chainId } = await (await fetch(API)).json();

const deadline = Math.floor(Date.now() / 1000) + 600;
const headers = {
  'x-satt-wallet': me.address,
  'x-satt-deadline': String(deadline),
  'x-satt-signature': await me.signTypedData(oracleRequestTypedData(chainId, me.address, 'jobs', deadline)),
};
const { jobs } = await (await fetch(`${API}/jobs`, { headers })).json();

for (const job of jobs) {
  const m = await measureFromMySources(job.url); // { views, likes, comments, shares, followers }
  const report = { oracle: me.address, adpool: job.adpool, dataHash: job.dataHash, platform: job.platform,
    ...m, observedAt: Math.floor(Date.now() / 1000), source: 'my-oracle-v1', jobId: job.jobId };
  const signature = await me.signTypedData(metricsReportTypedData(job.chainId, report));
  await fetch(`${API}/reports`, { method: 'POST', headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ report, signature }) });
}
Python (eth-account)
import time, requests
from eth_account import Account
from eth_account.messages import encode_typed_data

API = "https://<api-host>/api/oracle-network/v1"
me = Account.from_key(open("oracle.key").read().strip())
chain_id = requests.get(API).json()["chainId"]
T = lambda *f: [{"name": n, "type": t} for n, t in f]
TYPES = {
  "OracleRequest": T(("oracle", "address"), ("action", "string"), ("deadline", "uint256")),
  "MetricsReport": T(("oracle", "address"), ("adpool", "address"), ("dataHash", "bytes32"),
    ("platform", "string"), ("views", "uint256"), ("likes", "uint256"), ("comments", "uint256"),
    ("shares", "uint256"), ("followers", "uint256"), ("observedAt", "uint256"),
    ("source", "string"), ("jobId", "uint256")),
}
def sign(primary, msg, chain=chain_id):
    domain = {"name": "SaTT Oracle Network", "version": "1", "chainId": chain}
    m = encode_typed_data(domain_data=domain, message_types={primary: TYPES[primary]}, message_data=msg)
    return "0x" + me.sign_message(m).signature.hex().removeprefix("0x")

dl = int(time.time()) + 600
auth = sign("OracleRequest", {"oracle": me.address.lower(), "action": "jobs", "deadline": dl})
jobs = requests.get(f"{API}/jobs", headers={"x-satt-wallet": me.address,
       "x-satt-deadline": str(dl), "x-satt-signature": auth}).json()["jobs"]
for job in jobs:
    m = measure_from_my_sources(job["url"])
    report = {"oracle": me.address.lower(), "adpool": job["adpool"], "dataHash": job["dataHash"],
              "platform": job["platform"], **m, "observedAt": int(time.time()),
              "source": "my-oracle-py", "jobId": job["jobId"]}
    requests.post(f"{API}/reports", json={"report": report, "signature": sign("MetricsReport", report, job["chainId"])})

Quorum, median, outliers

A value is final once 2 oracles agree. The paid number is the median of the agreeing reports; any report more than 10% away from the median is an outlier — it earns nothing and lowers your agreement rate. Late reports are refused; missed jobs lower your uptime.

Verify the identity proof

Every job carries an OwnershipMatch signed by SaTT’s identity key: wallet, network, handle hash, post hash, method (official sign-in or bio code). Recover the signer and compare it with the identity signer published at GET /api/oracle-network/v1.

Self-signed claims (always verified)

Creators can sign their own figures. It is an accountable public statement that triggers a measurement right away — payment still waits for the quorum and is computed on the lower of declared and verified numbers. Over-declaring beyond the tolerance costs reputation; repeat offenders must post a bond, then are barred.

How oracles are paid

You set your prices. A fee accrues only for reports that count in a final quorum, at the offer version in force when the job was created, with your volume tiers. Dollar pools reserve fees from the pool budget (or charge the advertiser on top); crypto pools pay them from the pool at claim time.

API reference

Versioned REST API under /api/oracle-network/v1 — every endpoint, schema and error code is in the OpenAPI file.

FAQ

What data do I provide?

Public counts of a post — views, likes, comments, shares, and the account’s followers — as you observed them, with the time and your source label.

What are the rate limits?

Oracle calls: 120 requests burst, 2 per second. Public reads: 120 burst, 4 per second. Ownership matches: 30 burst, one every 2 seconds. Over the limit you get HTTP 429 with Retry-After.

How and when am I paid?

Each agreeing report in a final quorum accrues your price. Claim any time: dollar fees are credited to the satt.com account linked to your oracle address; crypto fees come as a signed claim on the pool.

What gets an oracle suspended?

Made-up or copied numbers, systematic outliers, reporting posts you did not measure, sharing your key or abusive polling.

Does SaTT compete with me?

The SaTT Oracle is one quorum member like any other and publishes its own offer. Where SaTT cannot measure a network, pools rely on the marketplace.

Is it on-chain?

Not yet: the network decides what the single payout key may sign. Multi-signer verification in the contract, staking and slashing are on the mainnet roadmap.