Skip to main content

Oracles (Stork)

Stork Oracle

Stork is the primary oracle integration for Horizen Chain. It is a pull oracle that delivers price data and other off-chain data feeds at sub-second latency, designed for use cases like perpetuals, lending protocols, and any application that requires fast, verifiable market data.

Unlike push oracles (which maintain feeds on-chain at all times), Stork operates on a consumer-driven model: feeds are not posted to the chain continuously. Instead, your application fetches the latest signed data off-chain and pushes it on-chain exactly when needed. This makes it highly cost-efficient: you only pay for the data updates your protocol actually uses.

How Stork Works on Horizen​

Diagram showing how Stork pull oracle works on Horizen: off-chain aggregator signs price data, app fetches it via REST API, pushes it on-chain to the Stork contract, then reads the verified price

Contract Addresses​

NetworkStork Contract Address
Mainnet Horizen0xacC0a0cF13571d30B4b8637996F5D6D774d4fd62
Testnet Horizen0xacC0a0cF13571d30B4b8637996F5D6D774d4fd62

Both mainnet and testnet share the same Stork contract address on Horizen. Source: docs.stork.network/resources/contract-addresses/evm.

Integration Steps​

Step 1: Fetch Data via the Stork REST API​

Before pushing anything on-chain, fetch the latest signed price data from Stork's off-chain API. Each response contains a signed payload ready to be submitted directly to the on-chain contract.

API endpoint:

GET https://rest.jp.stork-oracle.network/v1/prices/latest?assets=<ASSET_ID>

Full REST API reference: docs.stork.network/api-reference/rest-api

Available asset IDs (e.g. BTCUSD, ETHUSD) are listed in the Stork Asset ID Registry.

Deriving Feed IDs​

Encoded feed IDs are the keccak256 hash of the asset ID string. You can derive any feed ID locally rather than looking it up:

cast keccak "ETHUSD"
# 0x59102b37de83bdda9f38ac8254e596f0d9ac61d2035c07936675e87342817160

The same value is returned as encoded_asset_id in the REST API response.

Step 2: Push Data On-Chain​

Once you have the signed payload, submit it to the Stork contract on Horizen using updateTemporalNumericValuesV1. This verifies the aggregator signature and stores the price on-chain.

interface IStork {
function updateTemporalNumericValuesV1(
StorkStructs.TemporalNumericValueInput[] calldata updateData
) external payable;

function getUpdateFeeV1(
StorkStructs.TemporalNumericValueInput[] calldata updateData
) external view returns (uint feeAmount);
}

Important: Always call getUpdateFeeV1 first to determine the required fee, then pass that value as msg.value when calling updateTemporalNumericValuesV1. Submitting without sufficient fee will revert with InsufficientFee.

// Get required fee
uint fee = stork.getUpdateFeeV1(updateData);

// Push signed data on-chain
stork.updateTemporalNumericValuesV1{value: fee}(updateData);

Tip: If your protocol uses multiple price feeds, batch them in a single updateTemporalNumericValuesV1 call. This saves gas and ensures all prices are updated atomically in the same block.

Step 3: Read Data On-Chain​

Once a feed is updated on-chain, your smart contract can read it using getTemporalNumericValueV1. This function includes an automatic staleness check — it reverts with StaleValue if the stored price is older than the chain's configured freshness threshold.

interface IStork {
function getTemporalNumericValueV1(
bytes32 id
) external view returns (StorkStructs.TemporalNumericValue memory value);
}

struct TemporalNumericValue {
// Nanosecond-precision Unix timestamp of the price update
uint64 timestampNs;
// Price scaled to 18 decimal places
int192 quantizedValue;
}

Example — reading ETH/USD price in a Solidity contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IStork {
struct TemporalNumericValue {
uint64 timestampNs;
int192 quantizedValue;
}
function getTemporalNumericValueV1(
bytes32 id
) external view returns (TemporalNumericValue memory value);
}

contract PriceConsumer {
IStork public immutable stork;

// ETHUSD feed ID — keccak256("ETHUSD"), verify with: cast keccak "ETHUSD"
bytes32 public constant ETH_USD_ID =
0x59102b37de83bdda9f38ac8254e596f0d9ac61d2035c07936675e87342817160;

// Stork contract on Horizen mainnet: 0xacC0a0cF13571d30B4b8637996F5D6D774d4fd62
constructor(address _stork) {
stork = IStork(_stork);
}

function getEthPrice() external view returns (int192) {
IStork.TemporalNumericValue memory val =
stork.getTemporalNumericValueV1(ETH_USD_ID);
return val.quantizedValue; // 18 decimal places
}
}

If your contract needs a tighter freshness window than the chain default (e.g. reject prices older than 60 seconds), call getTemporalNumericValueV1 as normal and add your own require check on timestampNs. If you need a looser or fully custom freshness policy, use getTemporalNumericValueUnsafeV1 instead — it returns the stored value without reverting on staleness.

ResourceURL
Stork Documentationhttps://docs.stork.network
Asset ID Registryhttps://docs.stork.network/resources/asset-id-registry
REST API Referencehttps://docs.stork.network/api-reference/rest-api
EVM Contract APIhttps://docs.stork.network/api-reference/contract-apis/evm
EVM SDK Examplehttps://github.com/Stork-Oracle/stork-external/tree/main/chains/evm/examples/stork