TEE-Secured Price Oracle

On-Demand Oracle with Sustainable Economics — Based on OutLayer

Key Features

  • ✓ Proactive price pushing — prices always fresh in contract (30-60s updates)
  • ✓ Zero trust — all data processed inside Intel TDX enclave
  • ✓ DAO-governed — all configuration managed through council proposals
  • ✓ TEE-only signing — only keys generated inside TEE can push prices
  • ✓ 10 price sources with median aggregation
  • ✓ Native Pyth-compatible API — migrate from pyth-oracle.near by changing one address
  • ✓ Custom data fetching from any HTTP API
  • ✓ Subsidized mode — free calls when contract has funds
21
Supported Tokens
10
Price Sources
<1s
Response Time

Quick Start

Recommended

Get Prices with Callback

Use oracle_call to request prices. Your contract receives data via oracle_on_call callback.

near call price-oracle.near oracle_call '{
  "receiver_id": "your-contract.near",
  "asset_ids": ["wrap.near", "aurora"],
  "msg": ""
}' --accountId your.near --deposit 0.02 --gas 200000000000000

Direct Price Request (no callback)

For scripts and testing — returns prices directly.

near call price-oracle.near request_price_data '{
  "asset_ids": ["wrap.near"]
}' --accountId your.near --deposit 0.02 --gas 200000000000000
i

About View Methods (get_price_data)

get_price_data is a free view method. Prices are proactively pushed to the contract every 30-60 seconds by TEE workers, so data is always fresh.

You can also call request_price_data to trigger an immediate on-demand update from TEE if needed.

Response Format

{
  "timestamp": "1706889600000000000",
  "recency_duration_sec": 120,
  "prices": [
    {
      "asset_id": "wrap.near",
      "price": { "multiplier": "500000000", "decimals": 8 }
    }
  ]
}
// Price conversion: 500000000 / 10^8 = $5.00

Governance & Security

All Changes Go Through DAO

Every contract state mutation — adding assets, configuring exchanges, registering push signers, upgrading the contract — requires a DAO council proposal with >50% approval. No single key can modify the oracle.

TEE-Only Price Pushing

Prices are pushed to the contract by implicit accounts derived from TEE-generated keys. The private key is created inside the TEE (Intel TDX) and never leaves it — no human, including the project owner, ever sees it.

How PROTECTED_ keys work:

1. Project owner creates a secret (e.g., PROTECTED_KEY_RHEA) in OutLayer dashboard
2. Private key generated INSIDE TEE — never exposed to anyone
3. DAO proposal registers the derived implicit account as trusted oracle
4. Only this account can call report_prices for assigned assets
5. WASI code inside TEE signs transactions with the key

Result: No human holds the signing key. Only verified TEE code can push prices.

DAO Proposal Actions

ActionDescription
AddAsset / RemoveAssetManage tracked assets
SetAssetExchangeConfigConfigure exchange tickers, Pyth/Chainlink feeds per asset
RegisterPushSignerRegister TEE-derived account as trusted price pusher
ConfigureOutlayerSet OutLayer integration parameters
ProposeUpgrade / ExecuteUpgradeTwo-phase contract upgrade via DAO vote

Self-Service for Projects

Third-party projects can operate their own push signers:

  1. Create a TEE secret (PROTECTED_KEY_*) in OutLayer dashboard
  2. DAO proposal to register the key for specific assets
  3. Fund the derived implicit account with NEAR
  4. Scheduler pushes prices autonomously from TEE

Native Pyth Interface

price-oracle.near implements Pyth-compatible view methods natively. DeFi contracts using pyth-oracle.near can migrate by changing one contract address — no code changes needed.

No refresh_prices Needed

Unlike the separate Pyth wrapper contract, the native interface reads directly from contract state which is proactively updated every 30-60 seconds by the scheduler. View methods always return fresh data.

View Methods (free, always fresh)

MethodDescription
get_price(price_identifier)Latest price with staleness check
get_price_unsafe(price_identifier)Latest price without staleness check
get_price_no_older_than(price_id, age)Price only if published within age seconds
get_ema_price(price_id)EMA price with staleness check
get_ema_price_unsafe(price_id)EMA price without staleness check
list_prices(price_ids)Batch: multiple feeds at once
price_feed_exists(price_identifier)Check if feed is configured
get_update_fee_estimate(data)Returns 1 yoctoNEAR (no update needed)

Migration from Pyth

// Before (Pyth)
const ORACLE: &str = "pyth-oracle.near";

// After (Oracle-Ark) — no other changes needed!
const ORACLE: &str = "price-oracle.near";

Response Format

// PythPrice format (same as pyth-oracle.near)
{
  "price": 525000000,      // price * 10^|expo|
  "conf": 0,               // confidence (always 0 for Oracle-Ark)
  "expo": -8,              // exponent: actual_price = price * 10^expo
  "publish_time": 1706900000  // unix timestamp (seconds)
}
// Example: price=525000000, expo=-8 → $5.25

Direct OutLayer Integration

You don't need to use price-oracle.near at all. Your contract can call OutLayer directly to fetch prices or any custom data from TEE.

Why Go Direct?

  • No intermediary contracts — full control over the flow
  • Custom WASI workers — fetch any data you need
  • Lower gas costs — one less cross-contract call
  • Your contract owns the entire integration

Step 1: Call OutLayer request_execution

use near_sdk::{ext_contract, AccountId, NearToken, Promise, serde_json};

#[ext_contract(ext_outlayer)]
pub trait OutLayer {
    fn request_execution(
        &mut self,
        execution_source: serde_json::Value,
        resource_limits: Option<serde_json::Value>,
        input_data: Option<String>,
        secrets_ref: Option<serde_json::Value>,
        response_format: Option<String>,
        payer_account_id: Option<AccountId>,
        callback_receiver_id: Option<AccountId>,
    ) -> Promise;
}

impl Contract {
    pub fn fetch_price(&mut self, token_id: String) -> Promise {
        // Use the deployed price oracle project
        // Mainnet: "price-oracle.near/price-oracle"
        // Testnet: "price-oracle.testnet/price-oracle"
        let execution_source = serde_json::json!({
            "Project": {
                "project_id": "price-oracle.near/price-oracle"
            }
        });

        // Resource limits (recommended)
        let resource_limits = serde_json::json!({
            "max_instructions": 10000000000_u64,
            "max_memory_mb": 128,
            "max_execution_seconds": 60
        });

        // Input data for the WASI worker (see OracleCommand in types.rs)
        let input_data = serde_json::json!({
            "command": "get_prices",
            "tokens": [token_id]
        }).to_string();

        // Call OutLayer directly
        ext_outlayer::ext("outlayer.near".parse().unwrap())
            .with_attached_deposit(NearToken::from_millinear(10)) // 0.01 NEAR
            .with_unused_gas_weight(1)
            .request_execution(
                execution_source,
                Some(resource_limits),          // resource limits
                Some(input_data),               // your request
                None,                           // no secrets needed
                Some("json".to_string()),       // response format
                Some(env::predecessor_account_id()), // payer
                Some(env::current_account_id()), // callback receiver
            )
    }
}

Step 2: Handle the Callback

// OutLayer calls this method with the TEE result
#[private] // Only callable by self (via promise)
pub fn on_outlayer_result(
    &mut self,
    #[callback_result] result: Result<serde_json::Value, near_sdk::PromiseError>,
) {
    match result {
        Ok(data) => {
            // Parse the price data from TEE response
            if let Some(prices) = data.get("prices") {
                // Process your prices here
                log!("Got prices from TEE: {:?}", prices);
            }
        }
        Err(e) => {
            log!("OutLayer call failed: {:?}", e);
        }
    }
}

Architecture: Direct vs Via Oracle Contract

Via price-oracle.near (simpler):
Your Contract → price-oracle.near → OutLayer → TEE → price-oracle.near → Your Contract

Direct OutLayer (more control):
Your Contract → OutLayer → TEE → Your Contract

Both are valid! Use price-oracle.near for quick integration,
or go direct for full customization.
⚠️

Important Notes

  • You need to deploy your own WASI worker or use an existing one (like the price oracle WASI)
  • For price fetching, it's easier to use price-oracle.near — it handles WASI configuration for you
  • Direct integration is best for custom data sources or when you need full control
  • See price-oracle contract source for a complete example

Price Oracle Contract

i

Always-fresh prices

price-oracle.near receives proactive price updates every 30-60 seconds from TEE workers. View methods like get_price_data always return fresh data — no paid call needed.

You can also integrate with OutLayer directly from your own contract (see Direct OutLayer Integration section above).

Contract address: price-oracle.near

This contract recreates the interface (with additions) of the original NEAR Native Price Oracle — existing integrations can migrate with minimal changes.

View Methods (free)

i

Prices are proactively pushed every 30-60 seconds. get_price_data always returns fresh data. You can also call request_price_data for an immediate on-demand update.

MethodArgumentsDescription
get_price_dataasset_ids?: string[]Get cached prices (returns null if cache empty)
can_subsidize_outlayer_callsCheck if contract pays for calls
get_oracle_price_dataaccount_id, asset_ids?Get prices from specific oracle

Call Methods (require deposit)

MethodDepositDescription
request_price_data0.01+ NEARGet prices directly
oracle_call0.01+ NEARGet prices with callback
request_custom_data0.01+ NEARFetch custom external data
custom_call0.01+ NEARCustom data with callback

Data Types

// Price format: multiplier / 10^decimals = USD
struct Price {
    multiplier: u128,  // e.g., 500000000 for $5.00
    decimals: u8,      // usually 8
}

struct PriceData {
    timestamp: u64,              // nanoseconds
    recency_duration_sec: u32,   // max age for "fresh" prices
    prices: Vec<AssetOptionalPrice>,
}

struct AssetOptionalPrice {
    asset_id: String,
    price: Option<Price>,  // None if stale/unavailable
}

Callback Interface

// Your contract must implement this for oracle_call
pub fn oracle_on_call(
    &mut self,
    sender_id: AccountId,
    data: PriceData,
    msg: String,
) {
    // Verify caller is the oracle
    assert_eq!(
        env::predecessor_account_id(),
        "price-oracle.near".parse::<AccountId>().unwrap(),
        "Only oracle can call"
    );
    // Process prices...
}

Integration Example: Wrapper Contract

A complete example showing how to integrate the oracle with the full callback cycle. The wrapper contract self-funds oracle calls and handles callbacks internally.

Contract: price-oracle-wrapper.near | Source on GitHub

How It Works

User calls get_price() on Wrapper
        │
        ▼
Wrapper calls oracle_call() with SELF as receiver_id
(self-funded: 0.02 NEAR attached automatically)
        │
        ▼
Oracle processes request via OutLayer TEE
        │
        ▼
Oracle calls oracle_on_call() on Wrapper
        │
        ▼
Wrapper receives prices in callback, processes them

Key Pattern: Self-Funding Calls

// Wrapper pays for oracle calls itself - users don't need to attach deposits
pub fn get_price(&mut self, token_id: String) -> Promise {
    ext_oracle::ext(self.oracle_contract_id.clone())
        .with_attached_deposit(NearToken::from_millinear(20)) // 0.02 NEAR
        .with_unused_gas_weight(1)
        .oracle_call(
            env::current_account_id(), // callback comes back HERE
            Some(vec![token_id]),
            String::new(),
            None,
        )
}

// Callback handler - called by oracle with price data
pub fn oracle_on_call(
    &mut self,
    sender_id: AccountId,
    data: PriceData,
    msg: String,
) -> Option<Price> {
    // IMPORTANT: verify caller is the oracle!
    assert_eq!(env::predecessor_account_id(), self.oracle_contract_id);

    // Extract price from data
    if let Some(asset) = data.prices.first() {
        return asset.price.clone();
    }
    None
}

Why This Pattern?

  • Self-funding: Users call your contract without deposits — your contract pays for oracle calls
  • Full cycle: Request → TEE → Callback all handled in one user transaction
  • Security: Always verify predecessor_account_id in callbacks
  • Context: Use the msg field to pass context through async chain
i

All example contracts are optional! The contracts we provide (price-oracle.near, price-oracle-wrapper.near, etc.) are just examples. You can integrate with OutLayer directly from your own contract — see the next section.

Legacy Pyth Wrapper

i

Pyth-compatible methods are now built into price-oracle.near directly. The separate price-oracle-pyth.near wrapper is no longer needed.

See the section for migration instructions. Simply change your contract address to price-oracle.near — all Pyth view methods work natively, with always-fresh prices (no refresh_prices call needed).

Custom Data Sources

Fetch data from any HTTP API via TEE using request_custom_data or custom_call.

Request Format

{
  "custom_data_request": [
    {
      "id": "my_data",           // Identifier for the result
      "token_id": "",            // Optional token identifier
      "source": {
        "custom": {
          "url": "https://api.example.com/data",
          "json_path": "result.value",   // Dot notation path
          "value_type": "number",        // "number", "string", "boolean"
          "method": "GET",               // "GET" or "POST"
          "headers": []                  // Optional headers
        }
      }
    }
  ]
}

Examples

Steam Game Price

{
  "url": "https://store.steampowered.com/api/appdetails?appids=1245620",
  "json_path": "1245620.data.price_overview.final_formatted"
}

Account NFTs (FastNEAR)

{
  "url": "https://api.fastnear.com/v1/account/root.near/nft",
  "json_path": "tokens"
}

Weather Data

{
  "url": "https://api.open-meteo.com/v1/forecast?latitude=40.71&longitude=-74.00&current_weather=true",
  "json_path": "current_weather.temperature"
}

Code Examples

Rust Integration

use near_sdk::{ext_contract, AccountId, Gas, NearToken, Promise};

#[ext_contract(ext_oracle)]
pub trait Oracle {
    fn oracle_call(
        &mut self,
        receiver_id: AccountId,
        asset_ids: Option<Vec<String>>,
        msg: String,
        resource_limits: Option<serde_json::Value>,
    ) -> Promise;
}

impl Contract {
    pub fn get_prices_with_callback(&self) -> Promise {
        ext_oracle::ext("price-oracle.near".parse().unwrap())
            .with_attached_deposit(NearToken::from_millinear(20))
            .with_static_gas(Gas::from_tgas(150))
            .oracle_call(
                env::current_account_id(),
                Some(vec!["wrap.near".to_string()]),
                "swap".to_string(),
                None,
            )
    }
}

JavaScript Integration

import { connect, Contract } from 'near-api-js';

const oracle = new Contract(account, 'price-oracle.near', {
  viewMethods: ['get_price_data'],
  changeMethods: ['request_price_data', 'oracle_call'],
});

// View cached prices (free)
const cached = await oracle.get_price_data({
  asset_ids: ['wrap.near', 'aurora'],
});

// Convert price
const price = cached.prices[0].price;
const usd = Number(price.multiplier) / Math.pow(10, price.decimals);
console.log(`NEAR = $${usd}`);

Deposit Requirements

MethodFresh CacheStale (OutLayer)Subsidized
get_price_dataFreeN/AN/A
request_price_dataFree0.01+ NEARFree
oracle_call1 yoctoNEAR0.01+ NEARFree
request_custom_dataN/A0.01+ NEARFree

Subsidized Mode

When contract has >20 NEAR and subsidy is enabled, all OutLayer calls are free. Check with can_subsidize_outlayer_calls().

Try It Out

Use the interactive playground to test oracle methods without writing code.

Open Playground