From 6973c38acb151fbc5f0908b6fed70709d5c54ef8 Mon Sep 17 00:00:00 2001 From: Iris Date: Wed, 6 Mar 2024 09:39:25 +0100 Subject: [PATCH 1/6] feat: add get_altcoin_quote endpoint --- config.template.toml | 10 ++ src/config.rs | 10 ++ src/endpoints/avnu/get_altcoin_quote.rs | 112 ++++++++++++++++++++++ src/endpoints/avnu/mod.rs | 1 + src/endpoints/mod.rs | 1 + src/endpoints/stats/count_club_domains.rs | 103 ++++++++++++-------- 6 files changed, 196 insertions(+), 41 deletions(-) create mode 100644 src/endpoints/avnu/get_altcoin_quote.rs create mode 100644 src/endpoints/avnu/mod.rs diff --git a/config.template.toml b/config.template.toml index 3ffc035..4c37d2d 100644 --- a/config.template.toml +++ b/config.template.toml @@ -27,3 +27,13 @@ api_key = "xxxxxx" [solana] rpc_url = "https://xxxxxxx.solana-mainnet.quiknode.pro/xxxxxxx" private_key = "xxxxxxx" + +[token_support] +whitelisted_tokens = [ + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", + "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", + "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", +] +max_validity = 1000 # in seconds +private_key="123" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 81ccd13..3b4757e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,13 @@ pub_struct!(Clone, Deserialize; Solana { private_key: FieldElement, }); +pub_struct!(Clone, Deserialize; TokenSupport { + avnu_api: String, + whitelisted_tokens: Vec, + max_validity: i64, + private_key: FieldElement +}); + #[derive(Deserialize)] struct RawConfig { server: Server, @@ -52,6 +59,7 @@ struct RawConfig { starkscan: Starkscan, custom_resolvers: HashMap>, solana: Solana, + token_support: TokenSupport, } pub_struct!(Clone, Deserialize; Config { @@ -62,6 +70,7 @@ pub_struct!(Clone, Deserialize; Config { custom_resolvers: HashMap>, reversed_resolvers: HashMap, solana: Solana, + token_support: TokenSupport, }); impl From for Config { @@ -80,6 +89,7 @@ impl From for Config { custom_resolvers: raw.custom_resolvers, reversed_resolvers, solana: raw.solana, + token_support: raw.token_support, } } } diff --git a/src/endpoints/avnu/get_altcoin_quote.rs b/src/endpoints/avnu/get_altcoin_quote.rs new file mode 100644 index 0000000..3485440 --- /dev/null +++ b/src/endpoints/avnu/get_altcoin_quote.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use axum_auto_routes::route; +use chrono::Duration; +use serde::Deserialize; +use serde_json::json; +use starknet::core::{crypto::{ecdsa_sign, pedersen_hash}, types::FieldElement}; + +use crate::{models::AppState, utils::get_error}; + +#[derive(Deserialize)] +pub struct AddrQuery { + erc20_addr: FieldElement, +} + +#[derive(Deserialize, Debug)] +pub struct AvnuApiResult { + address: FieldElement, + currentPrice: f64, +} + +lazy_static::lazy_static! { + static ref QUOTE_STR: FieldElement = FieldElement::from_dec_str("724720344857006587549020016926517802128122613457935427138661").unwrap(); +} + +#[route(get, "/get_altcoin_quote", crate::endpoints::avnu::get_altcoin_quote)] +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + // check is erc20_addr is whitelist + if !state + .conf + .token_support + .whitelisted_tokens + .contains(&query.erc20_addr) + { + return get_error("Token not supported".to_string()); + } + + let url = format!( + "{}/tokens/short?in=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + state.conf.token_support.avnu_api + ); + let client = reqwest::Client::new(); + match client.get(&url).send().await { + Ok(response) => match response.text().await { + Ok(text) => match serde_json::from_str::>(&text) { + Ok(res) => { + let result = res + .iter() + .find(|&api_response| api_response.address == query.erc20_addr); + println!("result: {:?}", result); + match result { + Some(data) => { + // compute message hash + let now = chrono::Utc::now(); + let max_validity_timestamp = (now + + Duration::seconds(state.conf.token_support.max_validity)) + .timestamp(); + // convert current price to wei and return an integer as AVNU api can use more than 18 decimals + let current_price_wei = (data.currentPrice * (10u128.pow(18) as f64)) as i64; + println!("current_price_wei: {:?}", current_price_wei); + let message_hash = pedersen_hash( + &pedersen_hash( + &pedersen_hash( + &query.erc20_addr, + &FieldElement::from_dec_str( + current_price_wei.to_string().as_str(), + ) + .unwrap(), + ), + &FieldElement::from_dec_str(max_validity_timestamp.to_string().as_str()).unwrap(), + ), + "E_STR, + ); + println!("message_hash: {:?}", message_hash); + match ecdsa_sign(&state.conf.token_support.private_key.clone(), &message_hash) { + Ok(signature) => (StatusCode::OK, Json(json!({ + "quote": current_price_wei, + "r": signature.r, + "s": signature.s, + "max_validity": max_validity_timestamp + }))).into_response(), + Err(e) => get_error(format!( + "Error while generating Starknet signature: {}", + e + )), + } + }, + None => get_error("Token address not found".to_string()), + } + } + Err(e) => get_error(format!( + "Failed to deserialize result from AVNU API: {} for response: {}", + e, text + )), + }, + Err(e) => get_error(format!( + "Failed to get JSON response while fetching token quote: {}", + e + )), + }, + Err(e) => get_error(format!("Failed to fetch quote from AVNU api: {}", e)), + } +} diff --git a/src/endpoints/avnu/mod.rs b/src/endpoints/avnu/mod.rs new file mode 100644 index 0000000..e6007e2 --- /dev/null +++ b/src/endpoints/avnu/mod.rs @@ -0,0 +1 @@ +pub mod get_altcoin_quote; diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 4adf95e..c025ee6 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -5,6 +5,7 @@ pub mod addr_to_external_domains; pub mod addr_to_full_ids; pub mod addr_to_token_id; pub mod addrs_to_domains; +pub mod avnu; pub mod crosschain; pub mod data_to_ids; pub mod domain_to_addr; diff --git a/src/endpoints/stats/count_club_domains.rs b/src/endpoints/stats/count_club_domains.rs index 87278da..4e9fe5c 100644 --- a/src/endpoints/stats/count_club_domains.rs +++ b/src/endpoints/stats/count_club_domains.rs @@ -9,8 +9,8 @@ use axum_auto_routes::route; use futures::TryStreamExt; use mongodb::bson::{self, doc, Bson}; use serde::{Deserialize, Serialize}; -use std::sync::Arc; use std::collections::HashMap; +use std::sync::Arc; #[derive(Serialize)] pub struct CountClubDomainsData { @@ -23,7 +23,11 @@ pub struct CountClubDomainsQuery { since: i64, } -#[route(get, "/stats/count_club_domains", crate::endpoints::stats::count_club_domains)] +#[route( + get, + "/stats/count_club_domains", + crate::endpoints::stats::count_club_domains +)] pub async fn handler( State(state): State>, Query(query): Query, @@ -31,7 +35,9 @@ pub async fn handler( let mut headers = HeaderMap::new(); headers.insert("Cache-Control", HeaderValue::from_static("max-age=60")); - let domain_collection = state.starknetid_db.collection::("domains"); + let domain_collection = state + .starknetid_db + .collection::("domains"); let subdomain_collection = state .starknetid_db .collection::("custom_resolutions"); @@ -162,51 +168,66 @@ pub async fn handler( } ], None).await.unwrap().try_collect::>().await.unwrap(); - let mut count_99 = 0; - let mut count_999 = 0; - let mut count_10k = 0; - - let mut output: Vec> = Vec::new(); - let mut output_map: HashMap = HashMap::new(); + let mut count_99 = 0; + let mut count_999 = 0; + let mut count_10k = 0; - for doc in &db_output { - if let Ok(club) = doc.get_str("club") { - match club { - "99" => count_99 = doc.get_i32("count").unwrap_or_default(), - "999" => count_999 = doc.get_i32("count").unwrap_or_default(), - "10k" => count_10k = doc.get_i32("count").unwrap_or_default(), - _ => (), - } + let mut output: Vec> = Vec::new(); + let mut output_map: HashMap = HashMap::new(); + + for doc in &db_output { + if let Ok(club) = doc.get_str("club") { + match club { + "99" => count_99 = doc.get_i32("count").unwrap_or_default(), + "999" => count_999 = doc.get_i32("count").unwrap_or_default(), + "10k" => count_10k = doc.get_i32("count").unwrap_or_default(), + _ => (), } } + } - for doc in db_output { - if let Ok(club) = doc.get_str("club") { - match club { - "two_letters" => { - output_map.insert(club.to_string(), doc.get_i32("count").unwrap_or_default() + count_99); - } - "three_letters" => { - output_map.insert(club.to_string(), doc.get_i32("count").unwrap_or_default() + count_999); - } - "four_letters" => { - output_map.insert(club.to_string(), doc.get_i32("count").unwrap_or_default() + count_10k); - } - _ => { - output_map.insert(club.to_string(), doc.get_i32("count").unwrap_or_default()); - } + for doc in db_output { + if let Ok(club) = doc.get_str("club") { + match club { + "two_letters" => { + output_map.insert( + club.to_string(), + doc.get_i32("count").unwrap_or_default() + count_99, + ); + } + "three_letters" => { + output_map.insert( + club.to_string(), + doc.get_i32("count").unwrap_or_default() + count_999, + ); + } + "four_letters" => { + output_map.insert( + club.to_string(), + doc.get_i32("count").unwrap_or_default() + count_10k, + ); + } + _ => { + output_map.insert(club.to_string(), doc.get_i32("count").unwrap_or_default()); } } - output.push(output_map.clone()); - output_map.clear(); } + output.push(output_map.clone()); + output_map.clear(); + } - for doc in subdomain_output { - output_map.insert(doc.get_str("club").unwrap_or_default().to_string(), doc.get_i32("count").unwrap_or_default()); - output_map.insert(doc.get_str("club").unwrap_or_default().to_string(), doc.get_i32("count").unwrap_or_default()); - output.push(output_map.clone()); - output_map.clear(); - } + for doc in subdomain_output { + output_map.insert( + doc.get_str("club").unwrap_or_default().to_string(), + doc.get_i32("count").unwrap_or_default(), + ); + output_map.insert( + doc.get_str("club").unwrap_or_default().to_string(), + doc.get_i32("count").unwrap_or_default(), + ); + output.push(output_map.clone()); + output_map.clear(); + } - (StatusCode::OK, headers, Json(output)).into_response() + (StatusCode::OK, headers, Json(output)).into_response() } From e79750ed0328887261d00e903291f1d64340e9f8 Mon Sep 17 00:00:00 2001 From: Iris Date: Wed, 6 Mar 2024 15:26:06 +0100 Subject: [PATCH 2/6] fix: remove logs --- src/endpoints/avnu/get_altcoin_quote.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/endpoints/avnu/get_altcoin_quote.rs b/src/endpoints/avnu/get_altcoin_quote.rs index 3485440..cbbeb99 100644 --- a/src/endpoints/avnu/get_altcoin_quote.rs +++ b/src/endpoints/avnu/get_altcoin_quote.rs @@ -34,7 +34,7 @@ pub async fn handler( State(state): State>, Query(query): Query, ) -> impl IntoResponse { - // check is erc20_addr is whitelist + // check if erc20_addr is whitelisted if !state .conf .token_support @@ -44,6 +44,7 @@ pub async fn handler( return get_error("Token not supported".to_string()); } + // fetch quote from avnu api let url = format!( "{}/tokens/short?in=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", state.conf.token_support.avnu_api @@ -56,7 +57,6 @@ pub async fn handler( let result = res .iter() .find(|&api_response| api_response.address == query.erc20_addr); - println!("result: {:?}", result); match result { Some(data) => { // compute message hash @@ -66,7 +66,6 @@ pub async fn handler( .timestamp(); // convert current price to wei and return an integer as AVNU api can use more than 18 decimals let current_price_wei = (data.currentPrice * (10u128.pow(18) as f64)) as i64; - println!("current_price_wei: {:?}", current_price_wei); let message_hash = pedersen_hash( &pedersen_hash( &pedersen_hash( @@ -80,7 +79,6 @@ pub async fn handler( ), "E_STR, ); - println!("message_hash: {:?}", message_hash); match ecdsa_sign(&state.conf.token_support.private_key.clone(), &message_hash) { Ok(signature) => (StatusCode::OK, Json(json!({ "quote": current_price_wei, From e2a59692f565316db8e87a2ea77f4cd6496cff3f Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 7 Mar 2024 10:16:36 +0100 Subject: [PATCH 3/6] fix: quote computation --- src/endpoints/avnu/get_altcoin_quote.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/endpoints/avnu/get_altcoin_quote.rs b/src/endpoints/avnu/get_altcoin_quote.rs index cbbeb99..2cac922 100644 --- a/src/endpoints/avnu/get_altcoin_quote.rs +++ b/src/endpoints/avnu/get_altcoin_quote.rs @@ -64,8 +64,9 @@ pub async fn handler( let max_validity_timestamp = (now + Duration::seconds(state.conf.token_support.max_validity)) .timestamp(); + let quote = 1.0 / data.currentPrice; // convert current price to wei and return an integer as AVNU api can use more than 18 decimals - let current_price_wei = (data.currentPrice * (10u128.pow(18) as f64)) as i64; + let current_price_wei = (quote * (10u128.pow(18) as f64)).to_string(); let message_hash = pedersen_hash( &pedersen_hash( &pedersen_hash( From 8711465e30920a151a54b3d748ec9eb23afc5267 Mon Sep 17 00:00:00 2001 From: Thomas Marchand Date: Fri, 8 Mar 2024 16:26:53 +0000 Subject: [PATCH 4/6] feat: add token names --- config.template.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config.template.toml b/config.template.toml index 4c37d2d..de21099 100644 --- a/config.template.toml +++ b/config.template.toml @@ -30,10 +30,14 @@ private_key = "xxxxxxx" [token_support] whitelisted_tokens = [ - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + # ETH + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + # STRK "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", + # USDC "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", + # USDT "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ] max_validity = 1000 # in seconds -private_key="123" \ No newline at end of file +private_key = "123" From cca1769ff229ec93ac1a40b6b25548302a8be55a Mon Sep 17 00:00:00 2001 From: Thomas Marchand Date: Fri, 8 Mar 2024 16:28:15 +0000 Subject: [PATCH 5/6] fix: rename max_validity to max_quote_validity --- config.template.toml | 2 +- src/config.rs | 2 +- src/endpoints/avnu/get_altcoin_quote.rs | 37 +++++++++++++++++-------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/config.template.toml b/config.template.toml index de21099..cca38c2 100644 --- a/config.template.toml +++ b/config.template.toml @@ -39,5 +39,5 @@ whitelisted_tokens = [ # USDT "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", ] -max_validity = 1000 # in seconds +max_quote_validity = 1000 # in seconds private_key = "123" diff --git a/src/config.rs b/src/config.rs index 3b4757e..4e52777 100644 --- a/src/config.rs +++ b/src/config.rs @@ -47,7 +47,7 @@ pub_struct!(Clone, Deserialize; Solana { pub_struct!(Clone, Deserialize; TokenSupport { avnu_api: String, whitelisted_tokens: Vec, - max_validity: i64, + max_quote_validity: i64, private_key: FieldElement }); diff --git a/src/endpoints/avnu/get_altcoin_quote.rs b/src/endpoints/avnu/get_altcoin_quote.rs index 2cac922..9c912ab 100644 --- a/src/endpoints/avnu/get_altcoin_quote.rs +++ b/src/endpoints/avnu/get_altcoin_quote.rs @@ -10,7 +10,10 @@ use axum_auto_routes::route; use chrono::Duration; use serde::Deserialize; use serde_json::json; -use starknet::core::{crypto::{ecdsa_sign, pedersen_hash}, types::FieldElement}; +use starknet::core::{ + crypto::{ecdsa_sign, pedersen_hash}, + types::FieldElement, +}; use crate::{models::AppState, utils::get_error}; @@ -61,8 +64,8 @@ pub async fn handler( Some(data) => { // compute message hash let now = chrono::Utc::now(); - let max_validity_timestamp = (now - + Duration::seconds(state.conf.token_support.max_validity)) + let max_quote_validity_timestamp = (now + + Duration::seconds(state.conf.token_support.max_quote_validity)) .timestamp(); let quote = 1.0 / data.currentPrice; // convert current price to wei and return an integer as AVNU api can use more than 18 decimals @@ -76,23 +79,33 @@ pub async fn handler( ) .unwrap(), ), - &FieldElement::from_dec_str(max_validity_timestamp.to_string().as_str()).unwrap(), + &FieldElement::from_dec_str( + max_quote_validity_timestamp.to_string().as_str(), + ) + .unwrap(), ), "E_STR, ); - match ecdsa_sign(&state.conf.token_support.private_key.clone(), &message_hash) { - Ok(signature) => (StatusCode::OK, Json(json!({ - "quote": current_price_wei, - "r": signature.r, - "s": signature.s, - "max_validity": max_validity_timestamp - }))).into_response(), + match ecdsa_sign( + &state.conf.token_support.private_key.clone(), + &message_hash, + ) { + Ok(signature) => ( + StatusCode::OK, + Json(json!({ + "quote": current_price_wei, + "r": signature.r, + "s": signature.s, + "max_quote_validity": max_quote_validity_timestamp + })), + ) + .into_response(), Err(e) => get_error(format!( "Error while generating Starknet signature: {}", e )), } - }, + } None => get_error("Token address not found".to_string()), } } From 6f44c5c82ead90f58f18e313d9051a1753cf06ed Mon Sep 17 00:00:00 2001 From: Iris Date: Mon, 11 Mar 2024 10:27:13 +0100 Subject: [PATCH 6/6] fix: update altcoins config data --- config.template.toml | 41 +++++++++--- src/config.rs | 66 ++++++++++++++++--- src/endpoints/avnu/mod.rs | 1 - src/endpoints/{avnu => }/get_altcoin_quote.rs | 57 ++++++++++------ src/endpoints/mod.rs | 2 +- 5 files changed, 128 insertions(+), 39 deletions(-) delete mode 100644 src/endpoints/avnu/mod.rs rename src/endpoints/{avnu => }/get_altcoin_quote.rs (63%) diff --git a/config.template.toml b/config.template.toml index 4c37d2d..6bd712e 100644 --- a/config.template.toml +++ b/config.template.toml @@ -28,12 +28,35 @@ api_key = "xxxxxx" rpc_url = "https://xxxxxxx.solana-mainnet.quiknode.pro/xxxxxxx" private_key = "xxxxxxx" -[token_support] -whitelisted_tokens = [ - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", - "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", - "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", -] -max_validity = 1000 # in seconds -private_key="123" \ No newline at end of file +[altcoins] +avnu_api = "https://starknet.impulse.avnu.fi/v1" +private_key = "123" + +# Ethereum (ETH) is not enabled for the moment as it is already supported by default buy. +# [altcoins.ETH] +# address = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" +# min_price = 1 +# max_price = 1 +# decimals = 18 +# max_quote_validity = 3600 # in seconds for ETH + +[altcoins.STRK] +address = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" +min_price = 500 +max_price = 5000 +decimals = 18 +max_quote_validity = 300 # it moves faster so we reduce the quote validity + +[altcoins.USDC] +address = "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" +min_price = 2000 +max_price = 10000 +decimals = 6 # not sure really +max_quote_validity = 600 + +[altcoins.USDT] +address = "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8" +min_price = 2000 +max_price = 10000 +decimals = 18 +max_quote_validity = 600 \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 3b4757e..ad6f38c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use starknet::core::types::FieldElement; use std::collections::HashMap; use std::env; @@ -44,11 +44,26 @@ pub_struct!(Clone, Deserialize; Solana { private_key: FieldElement, }); -pub_struct!(Clone, Deserialize; TokenSupport { +pub_struct!(Clone, Debug, Deserialize; AltcoinData { + address: FieldElement, + min_price: u64, + max_price: u64, + decimals: u32, + max_quote_validity: i64 +}); + +#[derive(Debug, Deserialize)] +struct TempAltcoins { + avnu_api: String, + private_key: FieldElement, + #[serde(flatten)] + data: HashMap, +} + +pub_struct!(Clone, Debug; Altcoins { avnu_api: String, - whitelisted_tokens: Vec, - max_validity: i64, - private_key: FieldElement + private_key: FieldElement, + data: HashMap, }); #[derive(Deserialize)] @@ -59,7 +74,7 @@ struct RawConfig { starkscan: Starkscan, custom_resolvers: HashMap>, solana: Solana, - token_support: TokenSupport, + altcoins: Altcoins, } pub_struct!(Clone, Deserialize; Config { @@ -70,9 +85,44 @@ pub_struct!(Clone, Deserialize; Config { custom_resolvers: HashMap>, reversed_resolvers: HashMap, solana: Solana, - token_support: TokenSupport, + altcoins: Altcoins, }); +impl Altcoins { + fn new(temp: TempAltcoins) -> Self { + let data: HashMap = temp + .data + .into_values() + .map(|val| { + let altcoin_data = AltcoinData { + address: val.address, + min_price: val.min_price, + max_price: val.max_price, + decimals: val.decimals, + max_quote_validity: val.max_quote_validity, + }; + (val.address, altcoin_data) + }) + .collect(); + + Altcoins { + avnu_api: temp.avnu_api, + private_key: temp.private_key, + data, + } + } +} + +impl<'de> Deserialize<'de> for Altcoins { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let temp = TempAltcoins::deserialize(deserializer)?; + Ok(Altcoins::new(temp)) + } +} + impl From for Config { fn from(raw: RawConfig) -> Self { let mut reversed_resolvers = HashMap::new(); @@ -89,7 +139,7 @@ impl From for Config { custom_resolvers: raw.custom_resolvers, reversed_resolvers, solana: raw.solana, - token_support: raw.token_support, + altcoins: raw.altcoins, } } } diff --git a/src/endpoints/avnu/mod.rs b/src/endpoints/avnu/mod.rs deleted file mode 100644 index e6007e2..0000000 --- a/src/endpoints/avnu/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod get_altcoin_quote; diff --git a/src/endpoints/avnu/get_altcoin_quote.rs b/src/endpoints/get_altcoin_quote.rs similarity index 63% rename from src/endpoints/avnu/get_altcoin_quote.rs rename to src/endpoints/get_altcoin_quote.rs index 2cac922..d13b59e 100644 --- a/src/endpoints/avnu/get_altcoin_quote.rs +++ b/src/endpoints/get_altcoin_quote.rs @@ -10,7 +10,10 @@ use axum_auto_routes::route; use chrono::Duration; use serde::Deserialize; use serde_json::json; -use starknet::core::{crypto::{ecdsa_sign, pedersen_hash}, types::FieldElement}; +use starknet::core::{ + crypto::{ecdsa_sign, pedersen_hash}, + types::FieldElement, +}; use crate::{models::AppState, utils::get_error}; @@ -29,25 +32,21 @@ lazy_static::lazy_static! { static ref QUOTE_STR: FieldElement = FieldElement::from_dec_str("724720344857006587549020016926517802128122613457935427138661").unwrap(); } -#[route(get, "/get_altcoin_quote", crate::endpoints::avnu::get_altcoin_quote)] +#[route(get, "/get_altcoin_quote", crate::endpoints::get_altcoin_quote)] pub async fn handler( State(state): State>, Query(query): Query, ) -> impl IntoResponse { // check if erc20_addr is whitelisted - if !state - .conf - .token_support - .whitelisted_tokens - .contains(&query.erc20_addr) - { + if !state.conf.altcoins.data.contains_key(&query.erc20_addr) { return get_error("Token not supported".to_string()); } + let altcoin_data = state.conf.altcoins.data.get(&query.erc20_addr).unwrap(); // fetch quote from avnu api let url = format!( "{}/tokens/short?in=0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - state.conf.token_support.avnu_api + state.conf.altcoins.avnu_api ); let client = reqwest::Client::new(); match client.get(&url).send().await { @@ -62,11 +61,19 @@ pub async fn handler( // compute message hash let now = chrono::Utc::now(); let max_validity_timestamp = (now - + Duration::seconds(state.conf.token_support.max_validity)) + + Duration::seconds(altcoin_data.max_quote_validity)) .timestamp(); let quote = 1.0 / data.currentPrice; + // check if quote is within the valid range + if quote < altcoin_data.min_price as f64 + || quote > altcoin_data.max_price as f64 + { + return get_error("Quote out of range".to_string()); + } // convert current price to wei and return an integer as AVNU api can use more than 18 decimals - let current_price_wei = (quote * (10u128.pow(18) as f64)).to_string(); + let current_price_wei = + ((quote * (10u128.pow(altcoin_data.decimals) as f64)) as u128) + .to_string(); let message_hash = pedersen_hash( &pedersen_hash( &pedersen_hash( @@ -76,23 +83,33 @@ pub async fn handler( ) .unwrap(), ), - &FieldElement::from_dec_str(max_validity_timestamp.to_string().as_str()).unwrap(), + &FieldElement::from_dec_str( + max_validity_timestamp.to_string().as_str(), + ) + .unwrap(), ), "E_STR, ); - match ecdsa_sign(&state.conf.token_support.private_key.clone(), &message_hash) { - Ok(signature) => (StatusCode::OK, Json(json!({ - "quote": current_price_wei, - "r": signature.r, - "s": signature.s, - "max_validity": max_validity_timestamp - }))).into_response(), + match ecdsa_sign( + &state.conf.altcoins.private_key.clone(), + &message_hash, + ) { + Ok(signature) => ( + StatusCode::OK, + Json(json!({ + "quote": current_price_wei, + "r": signature.r, + "s": signature.s, + "max_validity": max_validity_timestamp + })), + ) + .into_response(), Err(e) => get_error(format!( "Error while generating Starknet signature: {}", e )), } - }, + } None => get_error("Token address not found".to_string()), } } diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index c025ee6..42d14df 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -5,7 +5,7 @@ pub mod addr_to_external_domains; pub mod addr_to_full_ids; pub mod addr_to_token_id; pub mod addrs_to_domains; -pub mod avnu; +pub mod get_altcoin_quote; pub mod crosschain; pub mod data_to_ids; pub mod domain_to_addr;