Smart Contract

BandOracleConnectors

A.e36ef556b8b5d955.BandOracleConnectors

Valid From

134,921,851

Deployed

1w ago
Feb 21, 2026, 03:43:26 PM UTC

Dependents

4 imports
1import Burner from 0xf233dcee88fe0abe
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import BandOracle from 0x6801a6222ebf784a
5
6import DeFiActions from 0x6d888f175c158410
7
8/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
9/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
10/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
11///
12/// BandOracleConnectors
13///
14/// This contract adapts BandOracle's price data oracle contracts for use as a DeFiActions PriceOracle connector
15///
16access(all) contract BandOracleConnectors {
17
18    /// Mapping of asset Types to BandOracle symbols
19    access(all) let assetSymbols: {Type: String}
20    /// StoragePath for the SymbolUpdater tasked with adding Type:SYMBOL pairs
21    access(all) let SymbolUpdaterStoragePath: StoragePath
22
23    /* EVENTS */
24    /// Emitted when a Type:SYMBOL pair is added via the SymbolUpdater resource
25    access(all) event SymbolAdded(symbol: String, asset: String)
26
27    /* CONSTRUCTS */
28
29    /// PriceOracle
30    ///
31    /// A DeFiActions connector that provides price data for a given asset using BandOracle
32    ///
33    access(all) struct PriceOracle : DeFiActions.PriceOracle {
34        /// The token type serving as the price basis - e.g. USD in FLOW/USD
35        access(self) let quote: Type
36        /// A Source providing the FlowToken necessary for BandOracle price data requests
37        access(self) let feeSource: {DeFiActions.Source}
38        /// The amount of seconds beyond which a price is considered stale and a price() call reverts
39        access(self) let staleThreshold: UInt64?
40        /// The unique ID of the PriceOracle
41        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
42
43        init(unitOfAccount: Type, staleThreshold: UInt64?, feeSource: {DeFiActions.Source}, uniqueID: DeFiActions.UniqueIdentifier?) {
44            pre {
45                feeSource.getSourceType() == Type<@FlowToken.Vault>():
46                "Invalid feeSource - given Source must provide FlowToken Vault, but provides \(feeSource.getSourceType().identifier)"
47                unitOfAccount.isSubtype(of: Type<@{FungibleToken.Vault}>()):
48                "Invalid unitOfAccount - \(unitOfAccount.identifier) is not a FungibleToken.Vault implementation"
49                BandOracleConnectors.assetSymbols[unitOfAccount] != nil:
50                "Could not find a BandOracle symbol assigned to unitOfAccount \(unitOfAccount.identifier)"
51            }
52            self.feeSource = feeSource
53            self.quote = unitOfAccount
54            self.staleThreshold = staleThreshold
55            self.uniqueID = uniqueID
56        }
57
58        /// Returns a ComponentInfo struct containing information about this PriceOracle and its inner DFA components
59        ///
60        /// @return a ComponentInfo struct containing information about this component and a list of ComponentInfo for
61        ///     each inner component in the stack.
62        ///
63        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
64            return DeFiActions.ComponentInfo(
65                type: self.getType(),
66                id: self.id(),
67                    innerComponents: [
68                        self.feeSource.getComponentInfo()
69                    ]
70            )
71        }
72        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
73        /// a DeFiActions stack. See DeFiActions.align() for more information.
74        ///
75        /// @return a copy of the struct's UniqueIdentifier
76        ///
77        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
78            return self.uniqueID
79        }
80        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
81        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
82        ///
83        /// @param id: the UniqueIdentifier to set for this component
84        ///
85        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
86            self.uniqueID = id
87        }
88        /// Returns the asset type serving as the price basis - e.g. USD in FLOW/USD
89        access(all) view fun unitOfAccount(): Type {
90            return self.quote
91        }
92        /// Returns the latest price data for a given asset denominated in unitOfAccount(). Since BandOracle requests
93        /// are paid, this implementation reverts if price data has gone stale
94        access(all) fun price(ofToken: Type): UFix64? {
95            // lookup the symbols
96            let baseSymbol = BandOracleConnectors.assetSymbols[ofToken]
97                ?? panic("Base asset type \(ofToken.identifier) does not have an assigned symbol")
98            let quoteSymbol = BandOracleConnectors.assetSymbols[self.unitOfAccount()]!
99            // withdraw the oracle fee & get the price data from BandOracle
100            let fee <- self.feeSource.withdrawAvailable(maxAmount: BandOracle.getFee())
101            let priceData = BandOracle.getReferenceData(baseSymbol: baseSymbol, quoteSymbol: quoteSymbol, payment: <-fee)
102
103            // check price data has not gone stale based on last updated timestamp
104            let now = UInt64(getCurrentBlock().timestamp)
105            if self.staleThreshold != nil {
106                assert(now < priceData.baseTimestamp + self.staleThreshold!, 
107                    message: "Price data's base timestamp \(priceData.baseTimestamp) exceeds the staleThreshold "
108                        .concat("\(priceData.baseTimestamp + self.staleThreshold!) at current timestamp \(now)"))
109                assert(now < priceData.quoteTimestamp + self.staleThreshold!,
110                    message: "Price data's quote timestamp \(priceData.quoteTimestamp) exceeds the staleThreshold "
111                        .concat("\(priceData.quoteTimestamp + self.staleThreshold!) at current timestamp \(now)"))
112            }
113
114            return priceData.fixedPointRate
115        }
116    }
117
118    // SymbolUpdater
119    //
120    /// Resource enabling the addition of new Type:SYMBOL pairings as they are supported by BandOracle's price oracle
121    access(all) resource SymbolUpdater {
122        /// Adds a Type:SYMBOL pairing to the contract's mapping. Reverts if the asset Type is already assigned a symbol
123        access(all) fun addSymbol(_ symbol: String, forAsset: Type) {
124            pre {
125                BandOracleConnectors.assetSymbols[forAsset] == nil:
126                "Asset \(forAsset.identifier) is already assigned symbol \(BandOracleConnectors.assetSymbols[forAsset]!)"
127            }
128            BandOracleConnectors.assetSymbols[forAsset] = symbol
129
130            emit SymbolAdded(symbol: symbol, asset: forAsset.identifier)
131        }
132    }
133
134    init() {
135        self.assetSymbols = {
136            Type<@FlowToken.Vault>(): "FLOW"
137        }
138        self.SymbolUpdaterStoragePath = StoragePath(identifier: "BandOracleConnectorSymbolUpdater_\(self.account.address)")!
139        self.account.storage.save(<-create SymbolUpdater(), to: self.SymbolUpdaterStoragePath)
140    }
141}
142