Skip to content
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

feat: onchain publishers endpoint #41

Merged
merged 11 commits into from
May 29, 2024
8 changes: 8 additions & 0 deletions infra/pragma-node/postgres_migrations/01-init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ CREATE TABLE vrf_requests (
_cursor bigint,
data_id character varying(255)
);

CREATE TABLE publishers (
name VARCHAR NOT NULL,
website_url VARCHAR NOT NULL,
mainnet_address VARCHAR,
testnet_address VARCHAR,
publisher_type INTEGER NOT NULL CHECK (publisher_type IN (0, 1)) -- 0 = first party, 1 = 3rd party
);
51 changes: 51 additions & 0 deletions infra/pragma-node/postgres_migrations/02-add-publishers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
INSERT INTO publishers (name, website_url, mainnet_address, testnet_address, publisher_type)
VALUES
(
'PRAGMA',
'https://www.pragma.build/',
'0x06707675CD7DD9256667ECA8284E46F4546711EE0054BC2DD02F0CE572056CF4',
'0x04C1D9DA136846AB084AE18CF6CE7A652DF7793B666A16CE46B1BF5850CC739D',
1
),
(
'AVNU',
'https://www.avnu.fi/',
'0x00D8219CFB9927C3BABA540AB6684E94A58844EAE0C170F568BA4620BC10050F',
'0x0279FDE026E3E6CCEACB9C263FECE0C8D66A8F59E8448F3DA5A1968976841C62',
0
),
(
'FOURLEAF',
'https://www.fourleaf.network/',
'0x073335CC71C93FE46C04C14E09E7CDE7CA7F6147BB36C72DEE7968EC3ABAF70D',
'0x037A10F2808C05F4A328BDAC9A9344358547AE4676EBDDC005E24FF887B188FD',
0
),
(
'SPACESHARD',
'https://www.spaceshard.io/',
'0x035DD30E84F7D61586C6B152524F3F2519DFC11B4DCB9998176B1DE9CFF9A6EA',
null,
1
),
(
'SKYNET_TRADING',
'https://skynettrading.com/',
'0x0155E28E1947350DAC90112F3129B74E3A58D38132C8C26F8552002D78C3656E',
null,
0
),
(
'FLOWDESK',
'https://www.flowdesk.co/',
'0x077567C3F2B43FA349EF2CCDF3F928D53A7FC4EE38C2411E8330F0E558568BB9',
null,
0
),
(
'EQUILIBRIUM',
'https://equilibrium.io/',
null,
'',
0
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE INDEX idx_spot_entry_publisher_pair_id ON spot_entry (publisher, pair_id);
CREATE INDEX idx_mainnet_spot_entry_publisher_pair_id ON mainnet_spot_entry (publisher, pair_id);
CREATE INDEX idx_future_entry_publisher_pair_id ON future_entry (publisher, pair_id);
CREATE INDEX idx_mainnet_future_entry_publisher_pair_id ON mainnet_future_entry (publisher, pair_id);

CREATE INDEX idx_spot_entry_publisher_pair_id_timestamp ON spot_entry (publisher, pair_id, timestamp DESC);
CREATE INDEX idx_mainnet_spot_entry_publisher_pair_id_timestamp ON mainnet_spot_entry (publisher, pair_id, timestamp DESC);
CREATE INDEX idx_future_entry_publisher_pair_id_timestamp ON future_entry (publisher, pair_id, timestamp DESC);
CREATE INDEX idx_mainnet_future_entry_publisher_pair_id_timestamp ON mainnet_future_entry (publisher, pair_id, timestamp DESC);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE INDEX idx_publisher_name ON publishers (name);
9 changes: 9 additions & 0 deletions pragma-common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,12 @@ pub enum Network {
#[serde(rename = "mainnet")]
Mainnet,
}

#[derive(Default, Debug, Deserialize, ToSchema, Clone, Copy)]
pub enum DataType {
#[serde(rename = "spot_entry")]
#[default]
SpotEntry,
#[serde(rename = "future_entry")]
FutureEntry,
}
11 changes: 9 additions & 2 deletions pragma-entities/src/models/currency.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::DieselResult;
use crate::schema::currencies;
use bigdecimal::BigDecimal;
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use utoipa::ToSchema;
use uuid::Uuid;
Expand All @@ -8,9 +9,9 @@ use uuid::Uuid;
pub struct Currency {
pub id: Uuid,
pub name: String,
pub decimals: u64,
pub decimals: BigDecimal,
pub is_abstract: bool,
pub ethereum_address: String,
pub ethereum_address: Option<String>,
}

impl Currency {
Expand All @@ -24,4 +25,10 @@ impl Currency {
.filter(currencies::abstract_.eq(true))
.get_results(conn)
}

pub fn get_decimals_all(conn: &mut PgConnection) -> DieselResult<Vec<(String, BigDecimal)>> {
currencies::table
.select((currencies::name, currencies::decimals))
.get_results::<(String, BigDecimal)>(conn)
}
}
51 changes: 49 additions & 2 deletions pragma-node/src/handlers/entries/get_onchain/publishers.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,49 @@
// TODO(akhercha): publishers endpoint
// https://buildonpragma.notion.site/Pragma-API-fc14ba680030470cab61ee58098b135f
use axum::extract::{Query, State};
use axum::Json;

use pragma_entities::EntryError;

use crate::handlers::entries::{GetOnchainPublishersParams, GetOnchainPublishersResponse};
use crate::infra::repositories::entry_repository::get_all_currencies_decimals;
use crate::infra::repositories::onchain_repository::{
get_publishers, get_publishers_with_components,
};
use crate::AppState;

#[utoipa::path(
get,
path = "/node/v1/onchain/publishers",
responses(
(status = 200, description = "Get the onchain publishers", body = GetOnchainPublishersResponse)
),
params(
("network" = Network, Query, description = "Network"),
("data_type" = DataType, Query, description = "Data type"),
),
)]
pub async fn get_onchain_publishers(
State(state): State<AppState>,
Query(params): Query<GetOnchainPublishersParams>,
) -> Result<Json<GetOnchainPublishersResponse>, EntryError> {
let publishers = get_publishers(&state.postgres_pool, params.network)
.await
.map_err(|e| e.to_entry_error(&"".to_string()))?;

let currencies_decimals = get_all_currencies_decimals(&state.timescale_pool)
.await
.map_err(|e| e.to_entry_error(&"".to_string()))?;

let publishers_with_components = get_publishers_with_components(
&state.postgres_pool,
params.network,
params.data_type,
currencies_decimals,
publishers,
)
.await
.map_err(|e| e.to_entry_error(&"".to_string()))?;

Ok(Json(GetOnchainPublishersResponse(
publishers_with_components,
)))
}
50 changes: 40 additions & 10 deletions pragma-node/src/handlers/entries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use starknet::core::types::FieldElement;
use utoipa::{IntoParams, ToSchema};

use pragma_common::types::{AggregationMode, Interval, Network};
use pragma_common::types::{AggregationMode, DataType, Interval, Network};

pub use create_entry::create_entries;
pub use get_entry::get_entry;
Expand Down Expand Up @@ -67,15 +67,6 @@ pub struct GetVolatilityResponse {
decimals: u32,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct OnchainEntry {
pub publisher: String,
pub source: String,
pub price: String,
pub tx_hash: String,
pub timestamp: u64,
}

#[derive(Debug, Deserialize, IntoParams, ToSchema)]
pub struct GetOnchainParams {
pub network: Network,
Expand All @@ -93,6 +84,15 @@ impl Default for GetOnchainParams {
}
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct OnchainEntry {
pub publisher: String,
pub source: String,
pub price: String,
pub tx_hash: String,
pub timestamp: u64,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct GetOnchainResponse {
pair_id: String,
Expand Down Expand Up @@ -140,6 +140,36 @@ pub struct GetEntryParams {
pub aggregation: Option<AggregationMode>,
}

#[derive(Debug, Default, Deserialize, IntoParams, ToSchema)]
pub struct GetOnchainPublishersParams {
pub network: Network,
pub data_type: DataType,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct PublisherEntry {
pub pair_id: String,
pub last_updated_timestamp: u64,
pub price: String,
pub source: String,
pub decimals: u32,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct Publisher {
pub publisher: String,
pub website_url: String,
pub last_updated_timestamp: u64,
pub r#type: u32,
pub nb_feeds: u32,
pub daily_updates: u32,
pub total_updates: u32,
pub components: Vec<PublisherEntry>,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct GetOnchainPublishersResponse(pub Vec<Publisher>);

impl Default for GetEntryParams {
fn default() -> Self {
Self {
Expand Down
28 changes: 28 additions & 0 deletions pragma-node/src/handlers/entries/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bigdecimal::{BigDecimal, ToPrimitive};
use chrono::NaiveDateTime;
use std::collections::HashMap;

use crate::infra::repositories::entry_repository::MedianEntry;

Expand All @@ -12,6 +13,33 @@ pub(crate) fn currency_pair_to_pair_id(base: &str, quote: &str) -> String {
format!("{}/{}", base.to_uppercase(), quote.to_uppercase())
}

/// Converts a pair_id to a currency pair.
///
/// e.g "BTC/USD" to ("BTC", "USD")
/// TODO: handle possible errors
pub(crate) fn pair_id_to_currency_pair(pair_id: &str) -> (String, String) {
let parts: Vec<&str> = pair_id.split('/').collect();
(parts[0].to_string(), parts[1].to_string())
}

/// From a map of currencies and their decimals, returns the number of decimals for a given pair.
/// If the currency is not found in the map, the default value is 8.
pub(crate) fn get_decimals_for_pair(
currencies: &HashMap<String, BigDecimal>,
pair_id: &str,
) -> u32 {
let (base, quote) = pair_id_to_currency_pair(pair_id);
let base_decimals = match currencies.get(&base) {
Some(decimals) => decimals.to_u32().unwrap_or_default(),
None => 8,
};
let quote_decimals = match currencies.get(&quote) {
Some(decimals) => decimals.to_u32().unwrap_or_default(),
None => 8,
};
std::cmp::min(base_decimals, quote_decimals)
}

/// Computes the median price and time from a list of entries.
/// The median price is computed as the median of the median prices of each entry.
/// The median time is computed as the median of the times of each entry.
Expand Down
19 changes: 19 additions & 0 deletions pragma-node/src/infra/repositories/entry_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use chrono::{DateTime, NaiveDateTime};
use diesel::prelude::QueryableByName;
use diesel::{ExpressionMethods, QueryDsl, Queryable, RunQueryDsl};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use pragma_common::types::{AggregationMode, Interval};
use pragma_entities::dto;
Expand Down Expand Up @@ -228,6 +229,24 @@ async fn get_price_decimals(
Ok((entry, decimals))
}

pub async fn get_all_currencies_decimals(
pool: &deadpool_diesel::postgres::Pool,
) -> Result<HashMap<String, BigDecimal>, InfraError> {
let conn = pool.get().await.map_err(adapt_infra_error)?;
let result_vec = conn
.interact(Currency::get_decimals_all)
.await
.map_err(adapt_infra_error)?
.map_err(adapt_infra_error)?;

let mut currencies_decimals_map = HashMap::new();
for (name, decimals) in result_vec {
currencies_decimals_map.insert(name, decimals);
}

Ok(currencies_decimals_map)
}

pub async fn get_twap_price(
pool: &deadpool_diesel::postgres::Pool,
pair_id: String,
Expand Down
Loading
Loading