Skip to content

Silver oracles #329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/src/datapoint_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ mod coingecko;
mod custom_ext_script;
mod erg_btc;
mod erg_usd;
mod erg_xag;
mod erg_xau;
mod ergodex;
mod predef;
mod rsn_xag;

use crate::oracle_types::Rate;
use crate::pool_config::PredefinedDataPointSource;
Expand Down
49 changes: 49 additions & 0 deletions core/src/datapoint_source/bitpanda.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::assets_exchange_rate::AssetsExchangeRate;
use super::assets_exchange_rate::Btc;
use super::assets_exchange_rate::Usd;
use super::erg_xag::KgAg;
use super::erg_xau::KgAu;
use super::DataPointSourceError;

Expand Down Expand Up @@ -48,6 +49,47 @@ pub async fn get_kgau_usd() -> Result<AssetsExchangeRate<KgAu, Usd>, DataPointSo
Ok(rate)
}

#[cfg(not(test))]
pub async fn get_kgag_usd() -> Result<AssetsExchangeRate<KgAg, Usd>, DataPointSourceError> {
let url = "https://api.bitpanda.com/v1/ticker";
let resp = reqwest::get(url).await?;
let json = json::parse(&resp.text().await?)?;
if let Some(p) = json["XAG"]["USD"].as_str() {
// USD price of 1 gram of silver
let p_float = p
.parse::<f64>()
.map_err(|_| DataPointSourceError::JsonMissingField {
field: "XAG.USD as f64".to_string(),
json: json.dump(),
})?;
let usd_per_kgag = KgAg::from_gram(p_float);
let rate = AssetsExchangeRate {
per1: KgAg {},
get: Usd {},
rate: usd_per_kgag,
};
Ok(rate)
} else {
Err(DataPointSourceError::JsonMissingField {
field: "XAG.USD".to_string(),
json: json.dump(),
})
}
}

#[cfg(test)]
pub async fn get_kgag_usd() -> Result<AssetsExchangeRate<KgAg, Usd>, DataPointSourceError> {
// USD price of 1 gram of silver
let p_float = 0.765;
let usd_per_kgag = KgAg::from_gram(p_float);
let rate = AssetsExchangeRate {
per1: KgAg {},
get: Usd {},
rate: usd_per_kgag,
};
Ok(rate)
}

#[cfg(not(test))]
// Get USD/BTC. Can be used as a redundant source for ERG/BTC through ERG/USD and USD/BTC
pub(crate) async fn get_btc_usd() -> Result<AssetsExchangeRate<Btc, Usd>, DataPointSourceError> {
Expand Down Expand Up @@ -97,6 +139,13 @@ mod tests {
let pair: AssetsExchangeRate<KgAu, Usd> = tokio_test::block_on(get_kgau_usd()).unwrap();
assert!(pair.rate > 0.0);
}

#[test]
fn test_kgag_usd_price() {
let pair: AssetsExchangeRate<KgAg, Usd> = tokio_test::block_on(get_kgag_usd()).unwrap();
assert!(pair.rate > 0.0);
}

#[test]
fn test_btc_usd_price() {
let pair: AssetsExchangeRate<Btc, Usd> = tokio_test::block_on(get_btc_usd()).unwrap();
Expand Down
105 changes: 100 additions & 5 deletions core/src/datapoint_source/coingecko.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::datapoint_source::assets_exchange_rate::AssetsExchangeRate;
use crate::datapoint_source::assets_exchange_rate::NanoErg;
use crate::datapoint_source::DataPointSourceError;

use super::ada_usd::Lovelace;
use super::assets_exchange_rate::Btc;
use super::assets_exchange_rate::Usd;
use super::erg_xag::KgAg;
use super::erg_xau::KgAu;
use crate::datapoint_source::assets_exchange_rate::AssetsExchangeRate;
use crate::datapoint_source::assets_exchange_rate::NanoErg;
use crate::datapoint_source::rsn_xag::Rsn;
use crate::datapoint_source::DataPointSourceError;

#[cfg(not(test))]
pub async fn get_kgau_nanoerg() -> Result<AssetsExchangeRate<KgAu, NanoErg>, DataPointSourceError> {
Expand All @@ -32,7 +33,7 @@ pub async fn get_kgau_nanoerg() -> Result<AssetsExchangeRate<KgAu, NanoErg>, Dat

#[cfg(test)]
pub async fn get_kgau_nanoerg() -> Result<AssetsExchangeRate<KgAu, NanoErg>, DataPointSourceError> {
let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / 0.0008162);
let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / 0.0482);
let nanoerg_per_kg = KgAu::from_troy_ounce(nanoerg_per_troy_ounce);
let rate = AssetsExchangeRate {
per1: KgAu {},
Expand All @@ -42,6 +43,41 @@ pub async fn get_kgau_nanoerg() -> Result<AssetsExchangeRate<KgAu, NanoErg>, Dat
Ok(rate)
}

#[cfg(not(test))]
pub async fn get_kgag_nanoerg() -> Result<AssetsExchangeRate<KgAg, NanoErg>, DataPointSourceError> {
let url = "https://api.coingecko.com/api/v3/simple/price?ids=ergo&vs_currencies=XAG";
let resp = reqwest::get(url).await?;
let price_json = json::parse(&resp.text().await?)?;
if let Some(p) = price_json["ergo"]["xag"].as_f64() {
// Convert from price Erg/XAG to nanoErgs per 1 XAG
let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / p);
let nanoerg_per_kg = KgAg::from_troy_ounce(nanoerg_per_troy_ounce);
let rate = AssetsExchangeRate {
per1: KgAg {},
get: NanoErg {},
rate: nanoerg_per_kg,
};
Ok(rate)
} else {
Err(DataPointSourceError::JsonMissingField {
field: "ergo.xag as f64".to_string(),
json: price_json.dump(),
})
}
}

#[cfg(test)]
pub async fn get_kgag_nanoerg() -> Result<AssetsExchangeRate<KgAg, NanoErg>, DataPointSourceError> {
let nanoerg_per_troy_ounce = NanoErg::from_erg(1.0 / 0.0706);
let nanoerg_per_kg = KgAg::from_troy_ounce(nanoerg_per_troy_ounce);
let rate = AssetsExchangeRate {
per1: KgAg {},
get: NanoErg {},
rate: nanoerg_per_kg,
};
Ok(rate)
}

#[cfg(not(test))]
pub async fn get_usd_nanoerg() -> Result<AssetsExchangeRate<Usd, NanoErg>, DataPointSourceError> {
let url = "https://api.coingecko.com/api/v3/simple/price?ids=ergo&vs_currencies=USD";
Expand Down Expand Up @@ -144,6 +180,46 @@ pub async fn get_btc_nanoerg() -> Result<AssetsExchangeRate<Btc, NanoErg>, DataP
Ok(rate)
}

pub async fn get_kgag_rsn() -> Result<AssetsExchangeRate<KgAg, Rsn>, DataPointSourceError> {
let url = "https://api.coingecko.com/api/v3/simple/price?ids=rosen-bridge&vs_currencies=XAG";
let resp = reqwest::get(url).await?;
let price_json = json::parse(&resp.text().await?)?;
if let Some(p) = price_json["rosen-bridge"]["xag"].as_f64() {
// Convert from price RSN/XAG
let rsn_per_ag = KgAg::from_troy_ounce(1.0 / p);
let rate = AssetsExchangeRate {
per1: KgAg {},
get: Rsn {},
rate: rsn_per_ag,
};
Ok(rate)
} else {
Err(DataPointSourceError::JsonMissingField {
field: "rsn.xag as f64".to_string(),
json: price_json.dump(),
})
}
}

pub async fn get_rsn_usd() -> Result<AssetsExchangeRate<Usd, Rsn>, DataPointSourceError> {
let url = "https://api.coingecko.com/api/v3/simple/price?ids=rosen-bridge&vs_currencies=USD";
let resp = reqwest::get(url).await?;
let price_json = json::parse(&resp.text().await?)?;
if let Some(p) = price_json["rosen-bridge"]["usd"].as_f64() {
let rate = AssetsExchangeRate {
per1: Usd {},
get: Rsn {},
rate: 1.0 / p,
};
Ok(rate)
} else {
Err(DataPointSourceError::JsonMissingField {
field: "rsn.usd as f64".to_string(),
json: price_json.dump(),
})
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -155,6 +231,13 @@ mod tests {
assert!(pair.rate > 0.0);
}

#[test]
fn test_erg_xag_price() {
let pair: AssetsExchangeRate<KgAg, NanoErg> =
tokio_test::block_on(get_kgag_nanoerg()).unwrap();
assert!(pair.rate > 0.0);
}

#[test]
fn test_erg_usd_price() {
let pair: AssetsExchangeRate<Usd, NanoErg> =
Expand All @@ -174,4 +257,16 @@ mod tests {
tokio_test::block_on(get_btc_nanoerg()).unwrap();
assert!(pair.rate > 0.0);
}

#[test]
fn test_rsn_xag_price() {
let pair: AssetsExchangeRate<KgAg, Rsn> = tokio_test::block_on(get_kgag_rsn()).unwrap();
assert!(pair.rate > 0.0);
}

#[test]
fn test_rsn_usd_price() {
let pair: AssetsExchangeRate<Usd, Rsn> = tokio_test::block_on(get_rsn_usd()).unwrap();
assert!(pair.rate > 0.0);
}
}
67 changes: 67 additions & 0 deletions core/src/datapoint_source/erg_xag.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Obtains the nanoErg per 1 XAG (troy ounce of silver) rate

use std::pin::Pin;

use futures::Future;

use crate::datapoint_source::aggregator::fetch_aggregated;
use crate::datapoint_source::assets_exchange_rate::{
convert_rate, Asset, AssetsExchangeRate, NanoErg,
};
use crate::datapoint_source::erg_usd::nanoerg_usd_sources;
use crate::datapoint_source::{bitpanda, coingecko, DataPointSourceError};

#[derive(Debug, Clone, Copy)]
pub struct KgAg {}

#[derive(Debug, Clone, Copy)]
pub struct Xag {}

impl Asset for KgAg {}

impl Asset for Xag {}

impl KgAg {
pub fn from_troy_ounce(oz: f64) -> f64 {
// https://en.wikipedia.org/wiki/Gold_bar
// troy ounces per kg
oz * 32.150746568627
}

pub fn from_gram(g: f64) -> f64 {
g * 1000.0
}
}

#[allow(clippy::type_complexity)]
pub fn nanoerg_kgag_sources() -> Vec<
Pin<Box<dyn Future<Output = Result<AssetsExchangeRate<KgAg, NanoErg>, DataPointSourceError>>>>,
> {
vec![
Box::pin(coingecko::get_kgag_nanoerg()),
Box::pin(combined_kgag_nanoerg()),
]
}

pub async fn combined_kgag_nanoerg(
) -> Result<AssetsExchangeRate<KgAg, NanoErg>, DataPointSourceError> {
let kgag_usd_rate = bitpanda::get_kgag_usd().await?;
let aggregated_usd_nanoerg_rate = fetch_aggregated(nanoerg_usd_sources()).await?;
Ok(convert_rate(aggregated_usd_nanoerg_rate, kgag_usd_rate))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_kgag_nanoerg_combined() {
let combined = tokio_test::block_on(combined_kgag_nanoerg()).unwrap();
let coingecko = tokio_test::block_on(coingecko::get_kgag_nanoerg()).unwrap();
let deviation_from_coingecko = (combined.rate - coingecko.rate).abs() / coingecko.rate;
assert!(
deviation_from_coingecko < 0.05,
"up to 5% deviation is allowed"
);
}
}
41 changes: 41 additions & 0 deletions core/src/datapoint_source/ergodex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::datapoint_source::assets_exchange_rate::{AssetsExchangeRate, NanoErg};
use crate::datapoint_source::rsn_xag::Rsn;
use crate::datapoint_source::DataPointSourceError;

pub async fn get_rsn_nanoerg() -> Result<AssetsExchangeRate<NanoErg, Rsn>, DataPointSourceError> {
let url = "https://api.spectrum.fi/v1/amm/pool/1b694b15467c62f0cd4525e368dbdea2329c713aa200b73df4a622e950551b40/stats";
let resp = reqwest::get(url).await?;
let pool_json = json::parse(&resp.text().await?)?;
let locked_erg = pool_json["lockedX"]["amount"].as_f64().ok_or_else(|| {
DataPointSourceError::JsonMissingField {
field: "lockedX.amount as f64".to_string(),
json: pool_json.dump(),
}
})?;

let locked_rsn = pool_json["lockedY"]["amount"].as_f64().ok_or_else(|| {
DataPointSourceError::JsonMissingField {
field: "lockedY.amount as f64".to_string(),
json: pool_json.dump(),
}
})?;
let price = Rsn::from_rsn(Rsn::from_rsn(locked_rsn) / NanoErg::from_erg(locked_erg));
let rate = AssetsExchangeRate {
per1: NanoErg {},
get: Rsn {},
rate: price,
};
Ok(rate)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_rsn_nanoerg_price() {
let pair: AssetsExchangeRate<NanoErg, Rsn> =
tokio_test::block_on(get_rsn_nanoerg()).unwrap();
assert!(pair.rate > 0.0);
}
}
5 changes: 5 additions & 0 deletions core/src/datapoint_source/predef.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::datapoint_source::rsn_xag::rsn_kgag_sources;
use crate::oracle_types::Rate;

use super::ada_usd::usd_lovelace_sources;
Expand Down Expand Up @@ -26,12 +27,16 @@ async fn fetch_predef_source_aggregated(
PredefinedDataPointSource::NanoErgXau => {
fetch_aggregated(nanoerg_kgau_sources()).await?.rate
}
PredefinedDataPointSource::NanoErgXag => {
fetch_aggregated(nanoerg_kgau_sources()).await?.rate
}
PredefinedDataPointSource::NanoAdaUsd => {
fetch_aggregated(usd_lovelace_sources()).await?.rate
}
PredefinedDataPointSource::NanoErgBTC => {
fetch_aggregated(nanoerg_btc_sources()).await?.rate
}
PredefinedDataPointSource::RsnXag => fetch_aggregated(rsn_kgag_sources()).await?.rate,
};
Ok((rate_float as i64).into())
}
Loading
Loading