owned this note
owned this note
Published
Linked with GitHub
# Band Protocol Near Protocol Developer Documentation
Developers building on Near can now leverage Band's decentralized oracle infrastructure. With Band's oracle, they now have access to various cryptocurrency, foreign exchange rates, and commodity price data to integrate into their applications.
## Standard Reference Dataset Contract Info
### Data Available
The various price data made available by Band's oracle originates from [data requests](https://github.com/bandprotocol/bandchain/wiki/System-Overview#oracle-data-request) made on BandChain. Band's [std_reference](https://explorer.testnet.near.org/accounts/std_reference_proxy.bandoracle.testnet) contract on Near then retrieves and stores the results of those requests.
The full list of symbols supported by Band's oracle can be found at https://data.bandprotocol.com
The prices themselves are the median of the values aggregated by BandChain's validators from various sources. These sources includes a number of price aggregators
- [CoinGecko](https://www.coingecko.com/),
- [CryptoCompare](https://cryptocompare.com/)
- [CoinMarketcap](https://coinmarketcap.com/)
as well as numerous reputable exchanges such as
- [CoinbasePro](https://pro.coinbase.com/)
- [Binance](https://binance.com)
- [HuobiPro](https://www.huobi.com)
- [Kraken](https://www.kraken.com/)
The data request is then made by executing Band's oracle script, the code of which is available for inspection through the Cosmoscan [block explorer](https://cosmoscan.io/oracle-script/3).
Along with the price data, developers will also have access to the latest timestamp the price was updated. These parameters are intended to act as security parameters to help anyone using the data to verify that the data they are using is what they expect and, perhaps more importantly, actually valid.
## Retrieving the Price Data
The code below shows an example of a [simple_price_database](https://explorer.testnet.near.org/accounts/simple_price_db.mumu.testnet) contract on Near which retrieve price data from Band's `std_reference_proxy` contract and store it in the contract's state.
```mermaid
sequenceDiagram
participant Simple Price DB
participant Std Reference Proxy
participant Std Reference Basic
Simple Price DB->>Std Reference Proxy: 1. get_reference_data("BTC","USD")
Std Reference Proxy->>Std Reference Basic: 2. get_reference_data("BTC","USD")
Std Reference Basic->>Std Reference Proxy: 3. BTC/USD price,<br/> BTC latest updated timestamp,<br/> USD latest updated timestamp
Std Reference Proxy->>Simple Price DB:<br/><br/>4. BTC/USD price,<br/>BTC latest updated timestamp,<br/> USD latest updated timestamp
```
The contract is able to store exchange rate of any price pair that available on the `std_reference` contract.
```rust=
use borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::UnorderedMap;
use near_sdk::{env, ext_contract, near_bindgen, AccountId};
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[ext_contract(std_proxy)]
pub trait StdProxy {
fn get_reference_data(&self, base: String, quote: String) -> Option<(u128, u64, u64)>;
fn get_reference_data_bulk(
&self,
bases: Vec<String>,
quotes: Vec<String>,
) -> Option<Vec<(u128, u64, u64)>>;
}
#[ext_contract(self_callback)]
pub trait SelfCallback {
fn callback_set_single(
&self,
symbol: String,
#[callback]
value_opt: Option<(u128, u64, u64)>,
);
fn callback_set_multiple(
&self,
symbols: Vec<String>,
#[callback]
values_opt: Vec<Option<(u128, u64, u64)>>,
);
}
#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct SimplePriceDB {
pub oracle: AccountId,
pub prices: UnorderedMap<String, u128>,
}
#[near_bindgen]
impl SimplePriceDB {
#[init]
pub fn new(oracle: AccountId) -> Self {
assert!(!env::state_exists(), "ALREADY_INITIALIZED");
Self { oracle, prices: UnorderedMap::new(b"prices".to_vec()) }
}
pub fn get_oracle(&self) -> AccountId {
self.oracle.clone()
}
pub fn set_oracle(&mut self, new_oracle: AccountId) {
env::log(format!("set oracle address from {} to {}", self.oracle, new_oracle).as_bytes());
self.oracle = new_oracle
}
pub fn get_price(&self, symbol: String) -> Option<u128> {
self.prices.get(&symbol)
}
pub fn set_single(&self, base: String, quote: String) {
let prepaid_gas = env::prepaid_gas();
let this = env::current_account_id();
let remaining_gas = prepaid_gas - env::used_gas();
std_proxy::get_reference_data(
base.clone(),
quote.clone(),
&self.oracle,
0,
2 * remaining_gas / 5
).then(
self_callback::callback_set_single(format!("{}/{}",base, quote), &this, 0, 2 * remaining_gas / 5)
);
}
pub fn set_multiple(
&mut self,
bases: Vec<String>,
quotes: Vec<String>,
) {
assert!(
bases.len() == quotes.len(),
format!("BASES_QUOTES_SIZE_IS_NOT_EQUAL:{}!={}",bases.len(),quotes.len())
);
let prepaid_gas = env::prepaid_gas();
let this = env::current_account_id();
let mut symbols = vec![String::from(""); bases.len()];
for (i, (base, quote)) in bases.iter().zip(quotes.iter()).enumerate() {
symbols[i] = format!("{}/{}", base, quote);
}
let remaining_gas = prepaid_gas - env::used_gas();
std_proxy::get_reference_data_bulk(
bases,
quotes,
&self.oracle,
0,
2 * remaining_gas / 5
).then(
self_callback::callback_set_multiple(symbols, &this, 0, 2 * remaining_gas / 5)
);
}
#[result_serializer(borsh)]
pub fn callback_set_single(
&mut self,
symbol: String,
#[callback]
value_opt: Option<(u128, u64, u64)>,
) {
match value_opt {
Some((rate, _, _)) => {
env::log(format!("Save rate {:?} to state", &rate).as_bytes());
self.prices.insert(&symbol, &rate);
},
None => {
env::log(format!("Got None from the oracle").as_bytes());
}
}
}
#[result_serializer(borsh)]
pub fn callback_set_multiple(
&mut self,
symbols: Vec<String>,
#[callback]
values_opt: Option<Vec<(u128, u64, u64)>>,
) {
match values_opt {
Some(values) => {
for (symbol, (rate, _, _)) in symbols.iter().zip(values.iter()) {
self.prices.insert(&symbol, &rate);
}
env::log(format!("Save rates {:?} to state", values).as_bytes());
},
None => {
env::log(format!("Got None from the oracle").as_bytes());
}
}
}
}
```
### Code Breakdown
The example code above can be broken down into three sections:
- defining the trait for the trait/interface `StdProxy`
- `SelfCallback`
- the `SimplePriceDB` contract code itself.
#### StdProxy
This section consists of two functions, `get_reference_data` and `set_price`. This is the interface that we'll use to query price from Band oracle for the latest price of a token by calling `get_reference_data`. After there is a response from the oracle contract, the callback function `set_price` will be called to set the price from the oracle into `SimplePriceDB`'s state.
#### SelfCallback
This section also consists of two functions, `callback_set_single` and `callback_set_multiple`. These two functions are for the oracle to be recalled after the SimplePriceDB has requested the prices data from the oracle.
#### SimplePriceDB
The `SimplePriceDB` then contains the main logic of our contract. Its purpose is to retrieve the latest price of tokens from oracle to store in its own state.
The actual price data query can then be called through the `get_price` function. Before we can call the method, however, we need to first set the address of the `oracle`. This is done by calling the `set_oracle` method or `new`. After that the price should be set by calling `set_single` or `set_multiple`.
The `set_single` function will simply calling `get_reference_data` from `std_reference_proxy` with base symbol and quote symbol. It then extract the exchange rate from the result and save to the state.
The `set_multiple` function converts the input into an array of base symbol and quote symbol arrays. After that it will call `get_reference_data_bulk` from `std_reference_proxy` with base symbols and quote symbols. It then extract the exchange rates from the results and save all of them to the state.
The full source code for the `SimplePriceDB` score can be found [in this repo](https://github.com/bandprotocol/near-poc/tree/master/simple_price_db) along with the example cli command for sending the `set_proxy`, `set_single`, `set_multiple`. The score itself is also deployed to the testnet at address [simple_price_db.mumu.testnet](https://explorer.testnet.near.org/accounts/simple_price_db.mumu.testnet).