Price Oracles

Overview

USD price oracles for UTXO coins (BTC, LTC, DOGE, DASH), any ERC-20 token on any EVM chain, and any SPL token on Solana. Aggregates many independent sources and returns the median to prevent single-source manipulation.

Overview

Zafeguard ships 6 price oracle components that aggregate prices from many independent public data sources and return the median across responding sources. The aggregation strategy resists single-source manipulation, individual API outages, and exchange wash trading.

Results are cached for 5 minutes per component to keep credit usage low for high-frequency workflows.


Components

ModuleAssetCreditsCache TTL
GET_BITCOIN_USD_PRICEBTC2300 s
GET_LITECOIN_USD_PRICELTC2300 s
GET_DOGECOIN_USD_PRICEDOGE2300 s
GET_DASH_USD_PRICEDASH2300 s
GET_EVM_USD_PRICEERC-20 or native gas token on any EVM chain3300 s
GET_SOLANA_USD_PRICESPL token or native SOL3300 s

All UTXO and the Solana oracle return at least one price as long as 2 sources agree; the EVM oracle requires the same when a tokenAddress is given, and 1 source when fetching the native gas token (since the universe of native-token endpoints is smaller).


Data sources

Zafeguard queries the following public data sources in parallel across all price oracles. Sources are not pinned to specific components — the same source pool is shared across asset classes wherever the source's coverage allows, so we can rotate, add, or replace providers without breaking workflow contracts.

UTXO coin sources (12)

SourceTypeNotes
CoinGeckoAggregatorMedian of major exchanges
CoinCapAggregatorVolume-weighted average
CoinPaprikaAggregatorIndependent of CoinGecko
CryptoCompareAggregatorIndex-style pricing
BinanceCEXLargest spot exchange by volume
CoinbaseCEXUS-regulated reference
KrakenCEXLong-running, US/EU-regulated
BybitCEXHigh liquidity, global
OKXCEXMajor global exchange
KuCoinCEXStrong long-tail asset coverage
BitfinexCEXLong-standing reference market
Gate.ioCEXWide asset listing

ERC-20 / EVM token sources (5)

SourceTypeNotes
CoinGeckoAggregatorDynamic chain registry — supports any EVM chain CoinGecko indexes
GeckoTerminalDEX aggregatorBroader long-tail token coverage
DexScreenerDEX aggregatorHighest-liquidity pair per chain
DefiLlamaAggregatorCross-source oracle data
OdosDEX routerDirect on-chain pool pricing

Solana SPL token sources (7)

SourceTypeNotes
JupiterDEX aggregatorRoutes across Raydium, Orca, Meteora, etc.
RaydiumAMMDirect pool pricing — strong for newer SPL tokens
BirdeyeDEX aggregatorLong-tail SPL coverage
CoinGeckoAggregatorMajor SPL tokens
GeckoTerminalDEX aggregatorLong-tail coverage
DexScreenerDEX aggregatorHighest-liquidity pair
DefiLlamaAggregatorCross-source oracle data

The median is robust against extreme outliers. If one exchange has stale or wash-traded prices, it cannot drag the result away from the consensus value the way an average would.


Resilience

Price oracles keep returning a sensible answer even when external markets are misbehaving — exchange API outages, rate-limit waves, regional incidents. Here is what you can count on:

  • Many independent sources. Every call pulls from a deep pool of providers — exchanges, DEX aggregators, and on-chain liquidity. Losing any one (or several) does not break the oracle.
  • Smart load distribution. We never hit every provider on every call. Requests are spread across the pool so no single upstream sees our full traffic, which keeps everyone well under their rate limits.
  • Self-healing under throttling. When a provider starts rate-limiting us, we step back from it automatically and return when it recovers. No human intervention, no workflow failures.
  • Graceful degradation. If everything live is briefly unreachable, the oracle returns the most recent good price marked with stale: true rather than failing your workflow. You decide whether to accept it.

Output

Every price oracle component returns:

{
  price: number;    // median USD price
  sources: number;  // upstream sources that responded
  stale: boolean;   // true if the result came from cached fallback
}

Workflows that need strict freshness can branch on stale. Workflows that prioritise availability can ignore it.


Smart routing for wrapped assets

If you ask for the price of a well-known wrapped or bridged asset — for example WBTC on Ethereum, cbBTC on Base, or BTC.b on Avalanche — the oracle automatically delegates to the underlying asset's price feed.

// Ask for WBTC on Ethereum — the oracle returns the bitcoin median price
await workspace.call(ComponentModule.GET_EVM_USD_PRICE, {
  tokenAddress: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
  chainId: 1,
}).promise();

This routing kicks in transparently — no extra configuration on your side — and gives you two benefits:

  1. A more authoritative price. Wrapped tokens trade on far thinner liquidity than the underlying. Routing to the underlying's deep multi-source pool produces a tighter, less manipulable number.
  2. Free duplicate calls. When two workflows hit bitcoin and WBTC within the same cache window, the second call returns instantly. No extra network traffic, no extra rate-limit budget consumed.

The routing is conservative — only well-established, tightly-pegged wrappers are eligible. Stablecoins (USDC, USDT, DAI) are deliberately not routed, since their price needs to reflect actual market conditions during a depeg.


One price per asset across chains

Assets like USDC, USDT, WETH, WBTC exist on many chains under different contract addresses but represent the same underlying asset. The oracle recognises this automatically: pricing USDC on Ethereum, USDC on Arbitrum, USDC on Base, and USDC on Solana inside the same cache window resolves to one upstream lookup and one price.

// All four calls share the same cache entry — only the first one bills.
await workspace.call(ComponentModule.GET_EVM_USD_PRICE, {
  tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC Ethereum
  chainId: 1,
}).promise();
await workspace.call(ComponentModule.GET_EVM_USD_PRICE, {
  tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC Arbitrum
  chainId: 42161,
}).promise();
await workspace.call(ComponentModule.GET_EVM_USD_PRICE, {
  tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC Base
  chainId: 8453,
}).promise();
await workspace.call(ComponentModule.GET_SOLANA_USD_PRICE, {
  tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC Solana
}).promise();

The same effect applies to native gas tokens: ETH on Ethereum and ETH on every L2 (Arbitrum, Optimism, Base, …) share one cache entry, because they are the same asset.

To avoid promoting unknown or spam tokens into a shared cache, only assets sitting comfortably inside the top of CoinGecko's market-cap ranking qualify for cross-chain dedup. Long-tail tokens still get priced — they just keep a per-chain cache entry.


GET_BITCOIN_USD_PRICE

GET_BITCOIN_USD_PRICE Workflow Component

Returns the median BTC/USD spot price across all configured UTXO sources.

Outputs

FieldTypeDescription
pricenumberMedian BTC/USD price
sourcesnumberNumber of price sources that responded successfully

SDK example

import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });

const { price, sources } = await workspace
  .call(ComponentModule.GET_BITCOIN_USD_PRICE, {})
  .promise();

console.log(`BTC: $${price.toLocaleString()} (from ${sources} sources)`);

GET_LITECOIN_USD_PRICE

GET_LITECOIN_USD_PRICE Workflow Component

Returns the median LTC/USD spot price across all configured UTXO sources.

Outputs

FieldTypeDescription
pricenumberMedian LTC/USD price
sourcesnumberNumber of price sources that responded successfully

GET_DOGECOIN_USD_PRICE

GET_DOGECOIN_USD_PRICE Workflow Component

Returns the median DOGE/USD spot price across all configured UTXO sources.

Outputs

FieldTypeDescription
pricenumberMedian DOGE/USD price
sourcesnumberNumber of price sources that responded successfully

GET_DASH_USD_PRICE

GET_DASH_USD_PRICE Workflow Component

Returns the median DASH/USD spot price across all configured UTXO sources.

Outputs

FieldTypeDescription
pricenumberMedian DASH/USD price
sourcesnumberNumber of price sources that responded successfully

GET_EVM_USD_PRICE

GET_EVM_USD_PRICE Workflow Component

Returns the median USD price of an ERC-20 token on any EVM chain — or the chain's native gas token when tokenAddress is null.

The set of supported chains is discovered dynamically at runtime from each upstream data source. You can pass any chain ID — if at least 2 of the configured sources index that chain, you get a price. There is no hard-coded chain allowlist; new chains added to the upstream sources become usable without a Zafeguard release.

For chains that aren't yet listed by your usual upstream, the oracle also runs a fallback discovery step using the chain's native-currency symbol, guarded by a popularity check so spam tokens that copy a legitimate symbol can't take over the price feed.

For native gas tokens (ETH, MATIC, BNB, AVAX, etc.) pass tokenAddress: null and the matching chainId. The oracle resolves the chain's native asset through the same source registries.

Inputs

FieldTypeDescription
tokenAddressstring | nullERC-20 contract address (0x + 40 hex). Pass null to get the chain's native gas token price
chainIdnumberEVM chain ID. Any chain ID is accepted; coverage depends on what the upstream sources index

Outputs

FieldTypeDescription
pricenumberMedian USD price across all responding sources
sourcesnumberNumber of price sources that responded successfully

Errors

  • 503 — Not enough sources responded for the requested chain. For ERC-20s the minimum is 2 sources; for native tokens (tokenAddress: null) the minimum is 1, since fewer endpoints index native gas tokens by chain.

SDK example

import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });

// USDC on Ethereum
const usdc = await workspace
  .call(ComponentModule.GET_EVM_USD_PRICE, {
    tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    chainId: 1,
  })
  .promise();

// Native ETH on Ethereum
const eth = await workspace
  .call(ComponentModule.GET_EVM_USD_PRICE, {
    tokenAddress: null,
    chainId: 1,
  })
  .promise();

// Native MATIC on Polygon
const matic = await workspace
  .call(ComponentModule.GET_EVM_USD_PRICE, {
    tokenAddress: null,
    chainId: 137,
  })
  .promise();

The oracle attempts to resolve any chain ID against the live source registries. If your chain isn't indexed by any public source, the oracle will return a 503 — there is nothing for it to query. Workflow runs that are already targeting a custom RPC (BUILD_EVM_TRANSACTION, READ_EVM_CONTRACT, etc.) work regardless of price oracle coverage.


GET_SOLANA_USD_PRICE

GET_SOLANA_USD_PRICE Workflow Component

Returns the median USD price of any Solana SPL token by mint address — or native SOL when tokenMint is null.

When tokenMint is null, the oracle resolves it to the wrapped-SOL mint (So11111111111111111111111111111111111111112), which every Solana source indexes as the canonical native SOL price.

Inputs

FieldTypeDescription
tokenMintstring | nullSPL token mint (base58). Pass null for native SOL

Outputs

FieldTypeDescription
pricenumberMedian USD price across all responding sources
sourcesnumberNumber of price sources that responded successfully

SDK example

import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });

// Native SOL
const sol = await workspace
  .call(ComponentModule.GET_SOLANA_USD_PRICE, { tokenMint: null })
  .promise();

// USDC on Solana
const usdc = await workspace
  .call(ComponentModule.GET_SOLANA_USD_PRICE, {
    tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
  })
  .promise();

On this page