DeploySEALED
○?▓▓*╱%$□░╳◆!╱╱~!╳╱□╲▓◇▫╳*#□▫╱~?■%░□~◆█▫░@^*▒#◇▫$&##@■▪$◇*╳?*▪▪▪
Transaction ID
Execution Fee
0.00000949 FLOWTransaction Summary
DeployContract deployment
Contract deployment
Script Arguments
0nameString
OracleAggregator
1codeString
import PriceOracle from 0x17ae3b1b0b0d50db
import BandPriceOracle from 0x17ae3b1b0b0d50db
import PoolPriceOracle from 0x17ae3b1b0b0d50db
import SimplePriceOracle from 0x17ae3b1b0b0d50db
/// OracleAggregator: Multi-oracle price aggregation with waterfall fallback
///
/// This contract implements a robust multi-oracle strategy that queries multiple
/// price sources in priority order, ensuring maximum reliability and token coverage.
///
/// **Waterfall Strategy**:
/// 1. **Band Protocol** (Primary) - For major tokens (FLOW, BTC, ETH, USDC, etc.)
/// - Decentralized oracle network
/// - Real-time updates
/// - High reliability
///
/// 2. **Pool Price Oracle** (Secondary) - For DEX-specific tokens (Froth, etc.)
/// - Direct liquidity pool queries
/// - Real-time pool prices
/// - Supports any token with a pool
///
/// 3. **Simple Price Oracle** (Tertiary) - For manual overrides/emergencies
/// - Admin-controlled prices
/// - Fallback when others unavailable
/// - Testing and development
///
/// **Example Usage**:
/// - FLOW: Band Protocol → Pool (if Band fails) → Simple (emergency)
/// - Froth: Pool Price → Simple (if pool query fails)
/// - New Token: Pool Price → Simple (manual price)
///
access(all) contract OracleAggregator: PriceOracle {
/// Price data structure (matches PriceOracle interface)
/// Uses AnyStruct to handle different oracle implementations
access(all) struct PriceData {
access(all) let symbol: String
access(all) let price: UFix64
access(all) let timestamp: UFix64
access(all) let confidence: UFix64?
init(symbol: String, price: UFix64, timestamp: UFix64, confidence: UFix64?) {
self.symbol = symbol
self.price = price
self.timestamp = timestamp
self.confidence = confidence
}
}
/// Oracle priority levels
access(all) enum OraclePriority: UInt8 {
access(all) case band // Highest priority
access(all) case pool // Medium priority
access(all) case simple // Lowest priority (fallback)
}
/// Oracle source tracking for transparency
access(all) struct PriceResult {
access(all) let priceData: PriceData
access(all) let source: String // "Band", "Pool", "Simple"
access(all) let priority: OraclePriority
init(priceData: PriceData, source: String, priority: OraclePriority) {
self.priceData = priceData
self.source = source
self.priority = priority
}
}
/// Storage paths
access(all) let OracleStoragePath: StoragePath
access(all) let OraclePublicPath: PublicPath
access(all) let AdminStoragePath: StoragePath
/// Oracle configuration
access(all) struct OracleConfig {
access(all) let useBand: Bool
access(all) let usePool: Bool
access(all) let useSimple: Bool
init(useBand: Bool, usePool: Bool, useSimple: Bool) {
self.useBand = useBand
self.usePool = usePool
self.useSimple = useSimple
}
}
/// Active oracle configuration
access(all) var oracleConfig: OracleConfig
/// Symbol-specific oracle preferences
/// Allows configuring which oracle to prefer for specific tokens
/// Example: "FROTH" -> OraclePriority.pool (skip Band, go straight to Pool)
access(self) var symbolPreferences: {String: OraclePriority}
/// Price cache (combined from all oracles)
access(self) var aggregateCache: {String: PriceResult}
/// Cache expiry (60 seconds - aligned with Band)
access(all) var cacheExpiry: UFix64
/// Events
access(all) event PriceFetched(
symbol: String,
price: UFix64,
source: String,
priority: UInt8,
timestamp: UFix64
)
access(all) event OracleFallback(
symbol: String,
failedSource: String,
fallbackSource: String
)
access(all) event SymbolPreferenceSet(
symbol: String,
preferredOracle: UInt8
)
access(all) event OracleConfigUpdated(
useBand: Bool,
usePool: Bool,
useSimple: Bool
)
access(all) event CacheExpiryUpdated(newExpiry: UFix64)
access(all) event AdminTransferred(oldAdmin: Address, newAdmin: Address, transferredBy: Address)
/// Admin resource for configuration management
/// Only the contract deployer receives this capability
access(all) resource Admin {
/// Set oracle preference for a specific symbol
/// Example: setSymbolPreference("FROTH", OraclePriority.pool)
/// This will make Froth always query Pool first, skipping Band
access(all) fun setSymbolPreference(symbol: String, priority: OraclePriority) {
pre {
symbol.length > 0: "Symbol cannot be empty"
}
OracleAggregator.symbolPreferences[symbol] = priority
emit SymbolPreferenceSet(symbol: symbol, preferredOracle: priority.rawValue)
}
/// Remove symbol preference (revert to waterfall strategy)
access(all) fun removeSymbolPreference(symbol: String) {
OracleAggregator.symbolPreferences.remove(key: symbol)
}
/// Enable/disable oracle sources
/// At least one oracle must remain enabled
access(all) fun configureOracles(useBand: Bool, usePool: Bool, useSimple: Bool) {
pre {
useBand || usePool || useSimple: "At least one oracle must be enabled"
}
OracleAggregator.oracleConfig = OracleConfig(
useBand: useBand,
usePool: usePool,
useSimple: useSimple
)
emit OracleConfigUpdated(
useBand: useBand,
usePool: usePool,
useSimple: useSimple
)
}
/// Set cache expiry time (how long to cache aggregated prices)
access(all) fun setCacheExpiry(_ newExpiry: UFix64) {
pre {
newExpiry > 0.0 && newExpiry <= 3600.0: "Cache expiry must be between 0 and 1 hour"
}
OracleAggregator.cacheExpiry = newExpiry
emit CacheExpiryUpdated(newExpiry: newExpiry)
}
}
/// Oracle implementation
access(all) resource Oracle: PriceOracle.Oracle {
/// Get current price using waterfall strategy
access(all) fun getPrice(symbol: String): AnyStruct? {
if let result = OracleAggregator.fetchPriceWithFallback(symbol: symbol, maxAge: 300.0) {
return result.priceData as AnyStruct
}
return nil
}
/// Get price with custom staleness check
access(all) fun getPriceWithMaxAge(symbol: String, maxAge: UFix64): AnyStruct? {
if let result = OracleAggregator.fetchPriceWithFallback(symbol: symbol, maxAge: maxAge) {
return result.priceData as AnyStruct
}
return nil
}
/// Check if token is supported by any oracle
access(all) fun supportsToken(symbol: String): Bool {
// Check if any oracle can provide a price for this symbol
if OracleAggregator.oracleConfig.useBand {
if let _ = BandPriceOracle.getPriceWithMaxAge(symbol: symbol, maxAge: 300.0) {
return true
}
}
if OracleAggregator.oracleConfig.usePool {
if let _ = PoolPriceOracle.getPrice(symbol: symbol) {
return true
}
}
if OracleAggregator.oracleConfig.useSimple {
if let _ = SimplePriceOracle.getPrice(symbol: symbol) {
return true
}
}
return false
}
/// Get all supported tokens (union of all oracles)
access(all) fun getSupportedTokens(): [String] {
let symbols: {String: Bool} = {}
if OracleAggregator.oracleConfig.useBand {
// Band supported tokens would be listed here
// For now, add common ones
symbols["FLOW"] = true
symbols["BTC"] = true
symbols["ETH"] = true
symbols["USDC"] = true
symbols["USDT"] = true
}
if OracleAggregator.oracleConfig.usePool {
// Use public function to get supported tokens
let poolSymbols = PoolPriceOracle.getSupportedTokens()
for symbol in poolSymbols {
symbols[symbol] = true
}
}
if OracleAggregator.oracleConfig.useSimple {
// SimplePriceOracle has all symbols in its prices dict
// Would need to access it - simplified for now
}
return symbols.keys
}
}
/// Fetch price with waterfall fallback strategy
access(all) fun fetchPriceWithFallback(symbol: String, maxAge: UFix64): PriceResult? {
let currentTime = getCurrentBlock().timestamp
// Check aggregate cache first
if let cachedResult = self.aggregateCache[symbol] {
let cacheAge = currentTime - cachedResult.priceData.timestamp
if cacheAge <= self.cacheExpiry {
return cachedResult // Cache hit
}
}
// Check for symbol-specific preference
let preference = self.symbolPreferences[symbol]
// If preference set, try that oracle first
if preference != nil {
let result = self.tryOracle(priority: preference!, symbol: symbol, maxAge: maxAge)
if result != nil {
return self.cacheAndReturn(result!)
}
}
// Waterfall: Try each oracle in priority order
// 1. Band Protocol (if enabled)
if self.oracleConfig.useBand {
let result = self.tryOracle(priority: OraclePriority.band, symbol: symbol, maxAge: maxAge)
if result != nil {
return self.cacheAndReturn(result!)
}
}
// 2. Pool Price (if enabled)
if self.oracleConfig.usePool {
let result = self.tryOracle(priority: OraclePriority.pool, symbol: symbol, maxAge: maxAge)
if result != nil {
if self.oracleConfig.useBand {
// Emit fallback event if we tried Band first
emit OracleFallback(
symbol: symbol,
failedSource: "Band",
fallbackSource: "Pool"
)
}
return self.cacheAndReturn(result!)
}
}
// 3. Simple Oracle (if enabled)
if self.oracleConfig.useSimple {
let result = self.tryOracle(priority: OraclePriority.simple, symbol: symbol, maxAge: maxAge)
if result != nil {
emit OracleFallback(
symbol: symbol,
failedSource: self.oracleConfig.usePool ? "Pool" : "Band",
fallbackSource: "Simple"
)
return self.cacheAndReturn(result!)
}
}
// All oracles failed
return nil
}
/// Try to fetch price from a specific oracle
access(self) fun tryOracle(priority: OraclePriority, symbol: String, maxAge: UFix64): PriceResult? {
var priceData: PriceData? = nil
var source = ""
switch priority {
case OraclePriority.band:
// Convert BandPriceOracle.PriceData to OracleAggregator.PriceData
if let bandPriceAny = BandPriceOracle.getPriceWithMaxAge(symbol: symbol, maxAge: maxAge) {
if let bandPrice = bandPriceAny as? BandPriceOracle.PriceData {
priceData = PriceData(
symbol: bandPrice.symbol,
price: bandPrice.price,
timestamp: bandPrice.timestamp,
confidence: bandPrice.confidence
)
}
}
source = "Band"
case OraclePriority.pool:
// Convert PoolPriceOracle.PriceData to OracleAggregator.PriceData
if let poolPriceAny = PoolPriceOracle.getPrice(symbol: symbol) {
if let poolPrice = poolPriceAny as? PoolPriceOracle.PriceData {
priceData = PriceData(
symbol: poolPrice.symbol,
price: poolPrice.price,
timestamp: poolPrice.timestamp,
confidence: poolPrice.confidence
)
}
}
source = "Pool"
case OraclePriority.simple:
// Convert SimplePriceOracle.PriceData to OracleAggregator.PriceData
if let simplePrice = SimplePriceOracle.getPriceWithMaxAge(symbol: symbol, maxAge: maxAge) {
priceData = PriceData(
symbol: simplePrice.symbol,
price: simplePrice.price,
timestamp: simplePrice.timestamp,
confidence: simplePrice.confidence
)
}
source = "Simple"
}
if priceData == nil {
return nil
}
return PriceResult(
priceData: priceData!,
source: source,
priority: priority
)
}
/// Cache result and emit event
access(self) fun cacheAndReturn(_ result: PriceResult): PriceResult {
// Cache the result
self.aggregateCache[result.priceData.symbol] = result
// Emit event
emit PriceFetched(
symbol: result.priceData.symbol,
price: result.priceData.price,
source: result.source,
priority: result.priority.rawValue,
timestamp: result.priceData.timestamp
)
return result
}
/// Public convenience function
access(all) fun getPrice(symbol: String): PriceData? {
let result = self.fetchPriceWithFallback(symbol: symbol, maxAge: 300.0)
return result?.priceData
}
/// Get price with source information
access(all) fun getPriceWithSource(symbol: String): PriceResult? {
return self.fetchPriceWithFallback(symbol: symbol, maxAge: 300.0)
}
/// Create a new Oracle resource
access(all) fun createOracle(): @Oracle {
return <- create Oracle()
}
init() {
self.OracleStoragePath = /storage/OracleAggregator
self.OraclePublicPath = /public/OracleAggregator
self.AdminStoragePath = /storage/OracleAggregatorAdmin
// Enable all oracles by default
self.oracleConfig = OracleConfig(useBand: true, usePool: true, useSimple: true)
// Cache settings
self.cacheExpiry = 60.0 // 60 seconds
// Initialize storage
self.symbolPreferences = {}
self.aggregateCache = {}
// Example: Set Pool as preferred oracle for Flow ecosystem tokens
// self.symbolPreferences["FROTH"] = OraclePriority.pool
// self.symbolPreferences["EMERALD"] = OraclePriority.pool
self.account.storage.save(<-create Admin(), to: self.AdminStoragePath)
}
}
Cadence Script
1transaction(name: String, code: String ) {
2 prepare(signer: auth(AddContract) &Account) {
3 signer.contracts.add(name: name, code: code.utf8 )
4 }
5 }