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
| Module | Asset | Credits | Cache TTL |
|---|---|---|---|
GET_BITCOIN_USD_PRICE | BTC | 2 | 300 s |
GET_LITECOIN_USD_PRICE | LTC | 2 | 300 s |
GET_DOGECOIN_USD_PRICE | DOGE | 2 | 300 s |
GET_DASH_USD_PRICE | DASH | 2 | 300 s |
GET_EVM_USD_PRICE | ERC-20 or native gas token on any EVM chain | 3 | 300 s |
GET_SOLANA_USD_PRICE | SPL token or native SOL | 3 | 300 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)
| Source | Type | Notes |
|---|---|---|
| CoinGecko | Aggregator | Median of major exchanges |
| CoinCap | Aggregator | Volume-weighted average |
| CoinPaprika | Aggregator | Independent of CoinGecko |
| CryptoCompare | Aggregator | Index-style pricing |
| Binance | CEX | Largest spot exchange by volume |
| Coinbase | CEX | US-regulated reference |
| Kraken | CEX | Long-running, US/EU-regulated |
| Bybit | CEX | High liquidity, global |
| OKX | CEX | Major global exchange |
| KuCoin | CEX | Strong long-tail asset coverage |
| Bitfinex | CEX | Long-standing reference market |
| Gate.io | CEX | Wide asset listing |
ERC-20 / EVM token sources (5)
| Source | Type | Notes |
|---|---|---|
| CoinGecko | Aggregator | Dynamic chain registry — supports any EVM chain CoinGecko indexes |
| GeckoTerminal | DEX aggregator | Broader long-tail token coverage |
| DexScreener | DEX aggregator | Highest-liquidity pair per chain |
| DefiLlama | Aggregator | Cross-source oracle data |
| Odos | DEX router | Direct on-chain pool pricing |
Solana SPL token sources (7)
| Source | Type | Notes |
|---|---|---|
| Jupiter | DEX aggregator | Routes across Raydium, Orca, Meteora, etc. |
| Raydium | AMM | Direct pool pricing — strong for newer SPL tokens |
| Birdeye | DEX aggregator | Long-tail SPL coverage |
| CoinGecko | Aggregator | Major SPL tokens |
| GeckoTerminal | DEX aggregator | Long-tail coverage |
| DexScreener | DEX aggregator | Highest-liquidity pair |
| DefiLlama | Aggregator | Cross-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: truerather 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:
- 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.
- Free duplicate calls. When two workflows hit
bitcoinandWBTCwithin 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
| Field | Type | Description |
|---|---|---|
price | number | Median BTC/USD price |
sources | number | Number 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
| Field | Type | Description |
|---|---|---|
price | number | Median LTC/USD price |
sources | number | Number 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
| Field | Type | Description |
|---|---|---|
price | number | Median DOGE/USD price |
sources | number | Number 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
| Field | Type | Description |
|---|---|---|
price | number | Median DASH/USD price |
sources | number | Number 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
| Field | Type | Description |
|---|---|---|
tokenAddress | string | null | ERC-20 contract address (0x + 40 hex). Pass null to get the chain's native gas token price |
chainId | number | EVM chain ID. Any chain ID is accepted; coverage depends on what the upstream sources index |
Outputs
| Field | Type | Description |
|---|---|---|
price | number | Median USD price across all responding sources |
sources | number | Number 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
| Field | Type | Description |
|---|---|---|
tokenMint | string | null | SPL token mint (base58). Pass null for native SOL |
Outputs
| Field | Type | Description |
|---|---|---|
price | number | Median USD price across all responding sources |
sources | number | Number 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();