DeploySEALED
█╲&□╱◆~▪○?!~▒▫░▓◇╲□○╳!●○○╱&▓╲●$~*◇!█■█&□~╲░$^$&╳▪&■#@░▓*▫○□╱▫╱%%
Transaction ID
Execution Fee
0.00000849 FLOWTransaction Summary
DeployContract deployment
Contract deployment
Script Arguments
0nameString
BandPriceOracle
1codeString
import PriceOracle from 0x17ae3b1b0b0d50db
/// BandPriceOracle: Production-ready oracle using Band Protocol
///
/// This oracle fetches real-time price data from Band Protocol's decentralized
/// oracle network on Flow blockchain.
///
/// Band Protocol provides:
/// - Decentralized price feeds from multiple data sources
/// - Real-time price updates
/// - High reliability and accuracy
/// - Multiple cryptocurrency and forex pairs
///
/// Official Documentation:
/// - Flow DeFi Contracts: https://developers.flow.com/ecosystem/defi-liquidity/defi-contracts-mainnet
/// - Band Protocol: https://docs.bandchain.org/integration/band-standard-dataset/using-band-dataset/
///
/// Note: BandOracle at 0x6801a6222ebf784a wraps StdReference. We use StdReference directly.
///
access(all) contract BandPriceOracle: PriceOracle {
/// Price data structure (matches PriceOracle interface)
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
}
}
/// Band Protocol StdReference contract interface
/// This interface defines how to interact with Band's oracle
access(all) resource interface StdReference {
access(all) fun getReferenceData(base: String, quote: String): ReferenceData
access(all) fun getReferenceDataBulk(bases: [String], quotes: [String]): [ReferenceData]
}
/// Reference data structure from Band Protocol
access(all) struct ReferenceData {
access(all) let rate: UInt256 // Price * 10^18
access(all) let lastUpdatedBase: UInt256
access(all) let lastUpdatedQuote: UInt256
init(rate: UInt256, lastUpdatedBase: UInt256, lastUpdatedQuote: UInt256) {
self.rate = rate
self.lastUpdatedBase = lastUpdatedBase
self.lastUpdatedQuote = lastUpdatedQuote
}
}
/// Storage paths
access(all) let OracleStoragePath: StoragePath
access(all) let OraclePublicPath: PublicPath
access(all) let AdminStoragePath: StoragePath
/// Band Protocol StdReference contract address
access(all) var bandReferenceAddress: Address
/// Price cache to reduce Band Protocol calls
/// Maps symbol -> cached price data
access(self) var priceCache: {String: PriceData}
/// Cache expiry time (60 seconds default)
access(all) var cacheExpiry: UFix64
/// Maximum price age to accept from Band (5 minutes)
access(all) var maxPriceAge: UFix64
/// Symbol mapping: DCA symbol -> Band symbol
/// Example: "FLOW" -> "FLOW", "USDC" -> "USDC"
access(self) var symbolMapping: {String: String}
/// Events
access(all) event PriceFetched(symbol: String, price: UFix64, timestamp: UFix64, source: String)
access(all) event PriceCached(symbol: String, price: UFix64, expiresAt: UFix64)
access(all) event PriceFeedStale(symbol: String, age: UFix64, maxAge: UFix64)
access(all) event BandReferenceUpdated(oldAddress: Address, newAddress: Address)
access(all) event SymbolMappingUpdated(dcaSymbol: String, bandSymbol: String)
access(all) event CacheExpiryUpdated(newExpiry: UFix64)
access(all) event MaxPriceAgeUpdated(newMaxAge: 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 {
/// Update Band Protocol reference address
/// Use this to switch between testnet/mainnet or update to new Band contract
access(all) fun setBandReferenceAddress(_ newAddress: Address) {
pre {
newAddress != Address(0x0): "Invalid Band reference address"
}
let oldAddress = BandPriceOracle.bandReferenceAddress
BandPriceOracle.bandReferenceAddress = newAddress
emit BandReferenceUpdated(oldAddress: oldAddress, newAddress: newAddress)
}
/// Add symbol mapping between DCA symbol and Band symbol
/// Example: addSymbolMapping("FLOW", "FLOW") or addSymbolMapping("USDC.e", "USDC")
access(all) fun addSymbolMapping(dcaSymbol: String, bandSymbol: String) {
pre {
dcaSymbol.length > 0: "DCA symbol cannot be empty"
bandSymbol.length > 0: "Band symbol cannot be empty"
}
BandPriceOracle.symbolMapping[dcaSymbol] = bandSymbol
emit SymbolMappingUpdated(dcaSymbol: dcaSymbol, bandSymbol: bandSymbol)
}
/// Set cache expiry time (how long to cache prices before refetching)
access(all) fun setCacheExpiry(_ newExpiry: UFix64) {
pre {
newExpiry > 0.0 && newExpiry <= 3600.0: "Cache expiry must be between 0 and 1 hour"
}
BandPriceOracle.cacheExpiry = newExpiry
emit CacheExpiryUpdated(newExpiry: newExpiry)
}
/// Set max price age (how old a price can be before considered stale)
access(all) fun setMaxPriceAge(_ newMaxAge: UFix64) {
pre {
newMaxAge > 0.0 && newMaxAge <= 3600.0: "Max price age must be between 0 and 1 hour"
}
BandPriceOracle.maxPriceAge = newMaxAge
emit MaxPriceAgeUpdated(newMaxAge: newMaxAge)
}
}
/// Oracle implementation
access(all) resource Oracle: PriceOracle.Oracle {
/// Get current price for a token from Band Protocol
access(all) fun getPrice(symbol: String): AnyStruct? {
if let priceData = BandPriceOracle.fetchPrice(symbol: symbol, maxAge: BandPriceOracle.maxPriceAge) {
return priceData as AnyStruct
}
return nil
}
/// Get price with custom staleness check
access(all) fun getPriceWithMaxAge(symbol: String, maxAge: UFix64): AnyStruct? {
if let priceData = BandPriceOracle.fetchPrice(symbol: symbol, maxAge: maxAge) {
return priceData as AnyStruct
}
return nil
}
/// Check if token is supported
access(all) fun supportsToken(symbol: String): Bool {
return BandPriceOracle.symbolMapping.containsKey(symbol)
}
/// Get all supported tokens
access(all) fun getSupportedTokens(): [String] {
return BandPriceOracle.symbolMapping.keys
}
}
/// Fetch price from Band Protocol with caching
access(all) fun fetchPrice(symbol: String, maxAge: UFix64): PriceData? {
let currentTime = getCurrentBlock().timestamp
// Check cache first
if let cachedPrice = self.priceCache[symbol] {
let cacheAge = currentTime - cachedPrice.timestamp
if cacheAge <= self.cacheExpiry {
// Cache hit - return cached price
return cachedPrice
}
}
// Cache miss or expired - fetch from Band Protocol
return self.fetchFromBand(symbol: symbol, maxAge: maxAge)
}
/// Fetch price directly from Band Protocol
access(self) fun fetchFromBand(symbol: String, maxAge: UFix64): PriceData? {
// Get Band symbol mapping
let bandSymbol = self.symbolMapping[symbol]
if bandSymbol == nil {
return nil
}
// Get Band StdReference capability
let bandReference = getAccount(self.bandReferenceAddress)
.capabilities.get<&{StdReference}>(/public/BandStdReference)
.borrow()
if bandReference == nil {
// Band Protocol not available - return cached if available
return self.priceCache[symbol]
}
// Fetch price from Band: symbol/USD
let refData = bandReference!.getReferenceData(
base: bandSymbol!,
quote: "USD"
)
// Convert Band's rate (UInt256 with 18 decimals) to UFix64
// Band rate is price * 10^18, we need UFix64 price
let price = self.convertBandRate(refData.rate)
// Check staleness
let currentTime = getCurrentBlock().timestamp
let bandUpdateTime = UFix64(refData.lastUpdatedBase)
let age = currentTime - bandUpdateTime
if age > maxAge {
emit PriceFeedStale(symbol: symbol, age: age, maxAge: maxAge)
return nil
}
// Create PriceData
let priceData = PriceData(
symbol: symbol,
price: price,
timestamp: bandUpdateTime,
confidence: nil // Band doesn't provide confidence intervals
)
// Cache the price
self.priceCache[symbol] = priceData
emit PriceCached(
symbol: symbol,
price: price,
expiresAt: currentTime + self.cacheExpiry
)
emit PriceFetched(
symbol: symbol,
price: price,
timestamp: bandUpdateTime,
source: "Band Protocol"
)
return priceData
}
/// Convert Band's UInt256 rate (with 18 decimals) to UFix64 price
/// Band rate format: price * 10^18
/// Example: FLOW at $1.25 = 1250000000000000000
access(self) fun convertBandRate(_ rate: UInt256): UFix64 {
// Convert UInt256 to UFix64
// Divide by 10^18 to get actual price
// Note: This is a simplified conversion
// In production, use proper decimal handling
let rateString = rate.toString()
let rateLength = rateString.length
// If rate is less than 18 digits, price is less than 1
if rateLength <= 18 {
// Pad with leading zeros
let padding = "000000000000000000".slice(from: 0, upTo: 18 - rateLength)
let fullRate = padding.concat(rateString)
let integerPart = "0"
let decimalPart = fullRate
// Construct UFix64 string
let priceString = integerPart.concat(".").concat(decimalPart)
return UFix64.fromString(priceString) ?? 0.0
}
// Extract integer and decimal parts
let splitPoint = rateLength - 18
let integerPart = rateString.slice(from: 0, upTo: splitPoint)
let decimalPart = rateString.slice(from: splitPoint, upTo: rateLength)
// Construct UFix64 string
let priceString = integerPart.concat(".").concat(decimalPart)
return UFix64.fromString(priceString) ?? 0.0
}
/// Public function to get price (convenience)
access(all) fun getPrice(symbol: String): PriceData? {
return self.fetchPrice(symbol: symbol, maxAge: self.maxPriceAge)
}
/// Public function to get price with staleness check
access(all) fun getPriceWithMaxAge(symbol: String, maxAge: UFix64): PriceData? {
return self.fetchPrice(symbol: symbol, maxAge: maxAge)
}
/// Create a new Oracle resource
access(all) fun createOracle(): @Oracle {
return <- create Oracle()
}
init() {
self.OracleStoragePath = /storage/BandPriceOracle
self.OraclePublicPath = /public/BandPriceOracle
self.AdminStoragePath = /storage/BandPriceOracleAdmin
// Set Band Protocol StdReference address
// Testnet: 0x9f857c97e8c50809
// Mainnet: 0x1a94aed0e4e6c2a7
self.bandReferenceAddress = 0x9f857c97e8c50809 // Testnet default
// Cache settings
self.cacheExpiry = 60.0 // 60 seconds
self.maxPriceAge = 300.0 // 5 minutes
// Initialize price cache
self.priceCache = {}
// Initialize symbol mappings (Band symbol names)
self.symbolMapping = {
"FLOW": "FLOW",
"USDC": "USDC",
"USDT": "USDT",
"BTC": "BTC",
"ETH": "ETH",
"FUSD": "FUSD"
}
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 }