Skip to Content
How it works

How it works

Claros is a verifiable real-world-data oracle on Casper testnet. An autonomous agent fetches data from an upstream source, normalizes it to an exact integer, hashes the provenance, and signs an on-chain attestation. Two contracts, keyed by a shared feed_id, hold the result: the AttestationRegistry stores the value, and the FeedRegistry stores the metadata that tells you how to read it. Any consumer reads both by feed_id and reconstructs the human number with one formula:

value = amount / 10^decimals

This is the Pyth model. The price ships with its exponent, so a consumer can interpret the integer entirely on-chain, with no off-chain schema.

Architecture at a glance

UPSTREAM AGENT ON-CHAIN (Casper) -------- ----- ----------------- EIA API v2 fetch 1. scale to integer (energy markets) -------> 2. hash provenance attest -----> AttestationRegistry City of San Diego 3. sign TransactionV1 (value, by feed_id) (parking revenue) 4. submit register_feed ---> FeedRegistry (metadata, by feed_id) CONSUMERS ----- read both by feed_id -----> value = amount / 10^decimals (REST / SDK / cross-contract / x402)

The agent runs on an hourly heartbeat and only attests when the upstream source publishes a period it has not processed yet, so it never spends gas re-attesting the same reading. The data path below is one step inside a larger autonomous cycle that also clears a zero-knowledge eligibility gate and manages a treasury (see the agent).

The attestation pipeline

Every value on Claros is produced by the same five steps.

Fetch the upstream reading

The agent pulls the latest reading from the feed’s provider. Claros uses a single generic adapter for the entire EIA API v2 (electricity, natural gas, petroleum, coal, nuclear, and more), plus a dedicated reader for the City of San Diego parking-revenue dataset. Each reading carries a period, a raw value, and the exact request parameters used to obtain it.

Scale the value to an integer

Smart contracts have no native floating point, so Claros stores every value as a scaled integer at the feed’s fixed decimals. The value is multiplied by 10^decimals and rounded, using exact decimal-string math so there is no float drift.

// EIA: a decimal string like "78.94" at the feed's fixed decimals (6) amount = scaleDecimalString('78.94', 6); // 78940000n (value * 10^6) // San Diego: parking revenue already arrives as integer cents, so decimals = 2 amount = 273420n; // $2,734.20

Hash the provenance

The agent computes a deterministic source_hash: a SHA-256 over only the stable provenance fields (route, frequency, sorted facets, period, value, unit for EIA; the asset, period, amount, transaction count, and source URL for San Diego), truncated to a 32-character (128-bit) hex prefix. Because it is computed from canonical fields, the same reading hashes identically wherever it is produced, so the on-chain attestation and the same feed sold over x402 share one verifiable digest.

source_hash = sha256(canonical).slice(0, 32); // 32 hex chars, deterministic

See provenance for how the canonical digest is built and how a consumer reproduces it to verify a reading.

Sign a Casper TransactionV1

The agent builds a Casper 2.0 (Condor) TransactionV1 with casper-js-sdk v5, signs it with the attester key, and submits it to the AttestationRegistry’s attest entry point.

const tx = new ContractCallBuilder() .byPackageHash(ATTESTATION_REGISTRY) .entryPoint('attest') .runtimeArgs(Args.fromMap({ asset_id: CLValue.newCLString('EIA.PET.PRICE.WTI.DAILY'), period: CLValue.newCLUint64(20260623), amount: CLValue.newCLUInt512(78940000), source_hash: CLValue.newCLString(sourceHash), })) .chainName('casper-test') .payment(20_000_000_000, 1) // gas limit in motes .from(agentKey.publicKey) .build(); tx.sign(agentKey); await rpc.putTransaction(tx);

Casper testnet charges the gas limit you set, not the gas actually consumed (there is no refund). The agent sizes each call’s limit deliberately. See Network and addresses for the gas model.

Store the attestation on-chain

attest writes the value into the AttestationRegistry, appends it to that feed’s history, bumps the per-feed and global counters, and emits a RevenueAttested event. The contract records who signed it and the block time, so the reading is permanent and auditable. See the attestation record for the full on-chain shape and its events.

Two registries, one feed_id

Claros splits a feed into two on-chain records that share a single key. The value changes every period; the metadata describes how to interpret it and rarely changes.

ContractHoldsKeyed byUpdated
AttestationRegistrythe latest value plus full historyfeed_idevery new period
FeedRegistryself-describing metadata (decimals, unit, source)feed_idonce, when the feed is registered

In the AttestationRegistry source the key parameter is named asset_id, while the FeedRegistry calls it feed_id. They are the same string. A feed’s value and its metadata live under one identifier, so get_latest("EIA.PET.PRICE.WTI.DAILY") and get_feed("EIA.PET.PRICE.WTI.DAILY") describe the same feed.

What an attestation stores

AttestationRegistry.get_latest(feed_id) returns an Attestation:

FieldTypeMeaning
periodu64Reporting period, for example 20260623 for a daily reading (epoch seconds for hourly feeds)
amountU512The reading scaled to an integer (value * 10^decimals)
source_hashString32-char SHA-256 prefix over the canonical upstream fields (provenance)
attesterAddressThe account that signed the attestation
timestampu64Casper block time when attested (epoch milliseconds)

The registry also keeps every historical reading: get_at(feed_id, index) returns a past attestation, get_count(feed_id) is that feed’s history length, and total_attestations() is the global count across all feeds.

What a feed’s metadata stores

FeedRegistry.get_feed(feed_id) returns a Feed:

FieldTypeMeaning
decimalsu8Scale exponent: real value = amount / 10^decimals
unitStringHuman unit, for example $/bbl, MWh, or percent
titleStringHuman title
sourceStringProvider, for example EIA or City of San Diego
routeStringProvider route, for example petroleum/pri/spt
frequencyStringOne of daily, weekly, monthly, annual, hourly, quarterly
descriptionStringLonger human description

To browse what is registered, feed_count() returns the number of feeds and get_feed_at(index) returns the feed_id at each position. The mapping from a feed_id to its provider route, frequency, column, and facet filters is covered in the feed model.

Trust model. Only the registered attester account may call attest, and only the owner may call register_feed; any other caller reverts with Unauthorized. On the Claros testnet deployment both roles are held by the agent account, so every value carries a verifiable attester you can check on each Attestation.

Self-describing feeds, and why value = amount / 10^decimals

A consumer should never need to know out-of-band that “WTI is quoted in dollars with six decimals.” That fact lives on-chain in the FeedRegistry, alongside the value in the AttestationRegistry. This is what “self-describing” means: the chain ships the value and the exponent and unit to read it.

Storing the value as a scaled integer (U512) rather than a float is what makes this work:

  • Exact. Integers have no floating-point rounding error, so the value is the same byte-for-byte for everyone.
  • Deterministic and language-agnostic. A Rust contract, the TypeScript SDK, and a REST client all apply the identical amount / 10^decimals formula.
  • Overflow-safe. U512 is a 512-bit unsigned integer, large enough for any feed Claros attests.

The scale is fixed per feed and stored once, so the value is just the raw integer and decimals is the exponent that turns it back into a human number.

Worked examples

FeedUpstream valuedecimalsamount (on-chain integer)value = amount / 10^decimals
WTI crude (EIA.PET.PRICE.WTI.DAILY)78.9467894000078.94 $/bbl
Henry Hub (EIA.NG.PRICE.HENRYHUB.DAILY)3.16631600003.16 $/MMBtu
San Diego parking (OP-1)$2,734.2022734202,734.20 USD

Reading a value back

Consumers pull both records by feed_id and divide. There are four ways to do it, depending on whether you want a free off-chain read, a free on-chain read, a hosted REST endpoint, or a metered pay-per-call feed.

The claros-oracle SDK reads Casper state directly (no indexer) and returns the human value already computed.

import { ClarosOracle } from 'claros-oracle'; const oracle = new ClarosOracle(); // testnet addresses baked in const r = await oracle.getReading('EIA.PET.PRICE.WTI.DAILY'); // r = { value: 78.94, amount: 78940000n, decimals: 6, unit: '$/bbl', period: 20260623, ... }

For a paid, per-request feed gated by the HTTP 402 protocol, see pay-per-call with x402.

Where this fits in the agent cycle

The attestation pipeline is steps 1 and 2 of the agent’s autonomous cycle. Before it attests, the agent confirms its on-chain zero-knowledge eligibility credential and anomaly-checks the reading against the feed’s typical range; after it attests, it decides whether to reinvest treasury into a yield venue and records that decision on-chain.

The agent never attests data it cannot verify. If the eligibility gate is not cleared, or the reading is anomalous, the cycle stops without writing anything. See the autonomous agent and the ZK eligibility gate.

Last updated on