diff --git a/Cargo.lock b/Cargo.lock index c65a3c9..7cf002d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1875,6 +1875,7 @@ dependencies = [ "serde_json", "siwe", "sqlx", + "sstr", "thiserror", "time", "tokio", @@ -5144,6 +5145,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "sstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769151c8aac7e5b1c576fbb573e1bc139dc92cbd4c83e3fb8ebc2c42a131b220" +dependencies = [ + "serde", + "sqlx", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index dec05d9..00fce4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ serde_json = "1.0.140" tokio-tungstenite = { version = "0.29.0", features = ["native-tls"] } http = "1.3.1" futures-util = "0.3.31" +sstr = {version = "0.3.0", features = ["sqlx-postgres", "serde"]} [dev-dependencies] alloy = {version = "2.0", features = ["node-bindings", "network", "rpc-types", "signer-local"]} diff --git a/benches/bench.rs b/benches/bench.rs index 6eb20f4..03c18c8 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,13 +1,16 @@ +use std::sync::LazyLock; + use axum::body::Body; use criterion::{AxisScale, BatchSize, PlotConfiguration}; use criterion::{Criterion, criterion_group, criterion_main}; use http::{HeaderValue, header::CONTENT_TYPE}; use reqwest::Client; use serde_json::json; -pub mod types; -async fn relay_benchmark(body: Vec, provider: String, client: Client) { - let byte_stream = client +pub static PROXY_CLIENT: LazyLock = LazyLock::new(Client::new); + +async fn relay_benchmark(body: Vec, provider: String) { + let byte_stream = PROXY_CLIENT .post(&provider) .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) .body(body) @@ -17,11 +20,10 @@ async fn relay_benchmark(body: Vec, provider: String, client: Client) { .error_for_status() .expect("") .bytes_stream(); - let _ = Body::from_stream(byte_stream); } -fn generate_body() -> (Vec, String, Client) { +fn generate_body() -> (Vec, String) { let body = json!({ "jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id": 1 }) @@ -31,9 +33,8 @@ fn generate_body() -> (Vec, String, Client) { let key = dotenvy::var("D_D_CLOUD_API_KEY").expect("D_D_CLOUD_API_KEY"); let provider = format!("https://api.cloud.developerdao.com/rpc/base/{}", &key); - let client = Client::new(); - (body, provider, client) + (body, provider) } fn test_relay_pocket(c: &mut Criterion) { @@ -46,7 +47,7 @@ fn test_relay_pocket(c: &mut Criterion) { group.bench_function("RPC Benchmark", move |b| { b.to_async(&runtime).iter_batched( generate_body, - async move |data| relay_benchmark(data.0, data.1, data.2).await, + async move |data| relay_benchmark(data.0, data.1).await, BatchSize::SmallInput, ) }); diff --git a/benches/types.rs b/benches/types.rs deleted file mode 100644 index 2dba403..0000000 --- a/benches/types.rs +++ /dev/null @@ -1,330 +0,0 @@ -use reqwest::Client; -use serde::Serialize; -use std::{ - fmt::{self, Display, Formatter}, - future::Future, - str::FromStr, - sync::LazyLock, -}; - -pub static GATEWAY_ENDPOINT: LazyLock<&'static str> = LazyLock::new(|| { - if cfg!(test) { - "http://localhost:3069/v1" - } else { - format!("{}/v1", dotenvy::var("GATEWAY_URL").unwrap()).leak() - } -}); - -pub trait Relayer { - fn relay_transaction(&self, body: &T) -> impl Future>; -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum PoktChains { - #[cfg(test)] - Anvil, - ArbitrumOne, - ArbitrumSepoliaTestnet, - Avalanche, - AvalancheDFK, - Base, - BaseSepoliaTestnet, - Bitcoin, - Blast, - BNBChain, - Boba, - CelestiaConsensus, - CelestiaConsensusTestnet, - CelestiaDA, - CelestiaDATestnet, - Celo, - Ethereum, - EthereumHoleskyTestnet, - EthereumSepoliaTestnet, - Evmos, - Fantom, - Fraxtal, - Fuse, - Gnosis, - Harmony0, - IoTeX, - Kaia, - Kava, - Metis, - Moonbeam, - Moonriver, - Near, - OasysMainnet, - OpBNB, - Optimism, - OptimismSepolia, - Osmosis, - PocketNetwork, - Polygon, - PolygonAmoyTestnet, - PolygonzkEVM, - Radix, - Scroll, - Solana, - Sui, - Taiko, - TaikoHeklaTestnet, - ZkLink, - ZkSyncEra, -} - -impl fmt::Display for PoktChains { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PoktChains::ArbitrumOne => write!(f, "F001"), - PoktChains::ArbitrumSepoliaTestnet => write!(f, "F002"), - PoktChains::Avalanche => write!(f, "F003"), - PoktChains::AvalancheDFK => write!(f, "F004"), - PoktChains::Base => write!(f, "F005"), - PoktChains::BaseSepoliaTestnet => write!(f, "F006"), - PoktChains::Bitcoin => write!(f, "F007"), - PoktChains::Blast => write!(f, "F008"), - PoktChains::BNBChain => write!(f, "F009"), - PoktChains::Boba => write!(f, "F00A"), - PoktChains::CelestiaConsensus => write!(f, "A0CB"), - PoktChains::CelestiaConsensusTestnet => write!(f, "A0CC"), - PoktChains::CelestiaDA => write!(f, "A0CA"), - PoktChains::CelestiaDATestnet => write!(f, "A0CD"), - PoktChains::Celo => write!(f, "F00B"), - PoktChains::Ethereum => write!(f, "F00C"), - PoktChains::EthereumHoleskyTestnet => write!(f, "F00D"), - PoktChains::EthereumSepoliaTestnet => write!(f, "F00E"), - PoktChains::Evmos => write!(f, "F00F"), - PoktChains::Fantom => write!(f, "F010"), - PoktChains::Fraxtal => write!(f, "F011"), - PoktChains::Fuse => write!(f, "F012"), - PoktChains::Gnosis => write!(f, "F013"), - PoktChains::Harmony0 => write!(f, "F014"), - PoktChains::IoTeX => write!(f, "F015"), - PoktChains::Kaia => write!(f, "F016"), - PoktChains::Kava => write!(f, "F017"), - PoktChains::Metis => write!(f, "F018"), - PoktChains::Moonbeam => write!(f, "F019"), - PoktChains::Moonriver => write!(f, "F01A"), - PoktChains::Near => write!(f, "F01B"), - PoktChains::OasysMainnet => write!(f, "F01C"), - PoktChains::OpBNB => write!(f, "F01F"), - PoktChains::Optimism => write!(f, "F01D"), - PoktChains::OptimismSepolia => write!(f, "F01E"), - PoktChains::Osmosis => write!(f, "F020"), - PoktChains::PocketNetwork => write!(f, "F000"), - PoktChains::Polygon => write!(f, "F021"), - PoktChains::PolygonAmoyTestnet => write!(f, "F022"), - PoktChains::PolygonzkEVM => write!(f, "F029"), - PoktChains::Radix => write!(f, "F023"), - PoktChains::Scroll => write!(f, "F024"), - PoktChains::Solana => write!(f, "F025"), - PoktChains::Sui => write!(f, "F026"), - PoktChains::Taiko => write!(f, "F027"), - PoktChains::TaikoHeklaTestnet => write!(f, "F028"), - PoktChains::ZkLink => write!(f, "F02A"), - PoktChains::ZkSyncEra => write!(f, "F02B"), - #[cfg(test)] - PoktChains::Anvil => write!(f, "anvil"), - } - } -} - -impl PoktChains { - pub const fn id(&self) -> &'static str { - match self { - #[cfg(test)] - PoktChains::Anvil => "anvil", - PoktChains::ArbitrumOne => "F001", - PoktChains::ArbitrumSepoliaTestnet => "F002", - PoktChains::Avalanche => "F003", - PoktChains::AvalancheDFK => "F004", - PoktChains::Base => "F005", - PoktChains::BaseSepoliaTestnet => "F006", - PoktChains::Bitcoin => "F007", - PoktChains::Blast => "F008", - PoktChains::BNBChain => "F009", - PoktChains::Boba => "F00A", - PoktChains::CelestiaConsensus => "A0CB", - PoktChains::CelestiaConsensusTestnet => "A0CC", - PoktChains::CelestiaDA => "A0CA", - PoktChains::CelestiaDATestnet => "A0CD", - PoktChains::Celo => "F00B", - PoktChains::Ethereum => "F00C", - PoktChains::EthereumHoleskyTestnet => "F00D", - PoktChains::EthereumSepoliaTestnet => "F00E", - PoktChains::Evmos => "F00F", - PoktChains::Fantom => "F010", - PoktChains::Fraxtal => "F011", - PoktChains::Fuse => "F012", - PoktChains::Gnosis => "F013", - PoktChains::Harmony0 => "F014", - PoktChains::IoTeX => "F015", - PoktChains::Kaia => "F016", - PoktChains::Kava => "F017", - PoktChains::Metis => "F018", - PoktChains::Moonbeam => "F019", - PoktChains::Moonriver => "F01A", - PoktChains::Near => "F01B", - PoktChains::OasysMainnet => "F01C", - PoktChains::OpBNB => "F01F", - PoktChains::Optimism => "F01D", - PoktChains::OptimismSepolia => "F01E", - PoktChains::Osmosis => "F020", - PoktChains::PocketNetwork => "F000", - PoktChains::Polygon => "F021", - PoktChains::PolygonAmoyTestnet => "F022", - PoktChains::PolygonzkEVM => "F029", - PoktChains::Radix => "F023", - PoktChains::Scroll => "F024", - PoktChains::Solana => "F025", - PoktChains::Sui => "F026", - PoktChains::Taiko => "F027", - PoktChains::TaikoHeklaTestnet => "F028", - PoktChains::ZkLink => "F02A", - PoktChains::ZkSyncEra => "F02B", - } - } -} - -impl Relayer for PoktChains -where - T: Serialize, -{ - async fn relay_transaction(&self, body: &T) -> Result { - Ok(Client::new() - .post(*GATEWAY_ENDPOINT) - .header("target-service-id", self.id()) - .json(body) - .send() - .await? - .text() - .await?) - } -} - -impl FromStr for PoktChains { - type Err = RelayErrors; - fn from_str(value: &str) -> Result { - match value.to_lowercase().as_ref() { - #[cfg(test)] - "anvil" => Ok(PoktChains::Anvil), - "arbitrum" => Ok(PoktChains::ArbitrumOne), - "arbitrumsepolia" => Ok(PoktChains::ArbitrumSepoliaTestnet), - "avalanche" => Ok(PoktChains::Avalanche), - "avalanchedfk" => Ok(PoktChains::AvalancheDFK), - "base" => Ok(PoktChains::Base), - "basesepolia" => Ok(PoktChains::BaseSepoliaTestnet), - "bitcoin" => Ok(PoktChains::Bitcoin), - "blast" => Ok(PoktChains::Blast), - "bsc" => Ok(PoktChains::BNBChain), - "boba" => Ok(PoktChains::Boba), - "celestiaconsensus" => Ok(PoktChains::CelestiaConsensus), - "celestiaconsensustestnet" => Ok(PoktChains::CelestiaConsensusTestnet), - "celestia" => Ok(PoktChains::CelestiaDA), - "celestiatestnet" => Ok(PoktChains::CelestiaDATestnet), - "celo" => Ok(PoktChains::Celo), - "ethereum" => Ok(PoktChains::Ethereum), - "holesky" => Ok(PoktChains::EthereumHoleskyTestnet), - "sepolia" => Ok(PoktChains::EthereumSepoliaTestnet), - "evmos" => Ok(PoktChains::Evmos), - "fantom" => Ok(PoktChains::Fantom), - "fraxtal" => Ok(PoktChains::Fraxtal), - "fuse" => Ok(PoktChains::Fuse), - "gnosis" => Ok(PoktChains::Gnosis), - "harmony0" => Ok(PoktChains::Harmony0), - "iotex" => Ok(PoktChains::IoTeX), - "kaia" => Ok(PoktChains::Kaia), - "kava" => Ok(PoktChains::Kava), - "metis" => Ok(PoktChains::Metis), - "moonbeam" => Ok(PoktChains::Moonbeam), - "moonriver" => Ok(PoktChains::Moonriver), - "near" => Ok(PoktChains::Near), - "oasysmainnet" => Ok(PoktChains::OasysMainnet), - "opbnb" => Ok(PoktChains::OpBNB), - "optimism" => Ok(PoktChains::Optimism), - "optimismsepolia" => Ok(PoktChains::OptimismSepolia), - "osmosis" => Ok(PoktChains::Osmosis), - "pocketnetwork" => Ok(PoktChains::PocketNetwork), - "polygon" => Ok(PoktChains::Polygon), - "polygonamoytestnet" => Ok(PoktChains::PolygonAmoyTestnet), - "polygonzkevm" => Ok(PoktChains::PolygonzkEVM), - "radix" => Ok(PoktChains::Radix), - "scroll" => Ok(PoktChains::Scroll), - "solana" => Ok(PoktChains::Solana), - "sui" => Ok(PoktChains::Sui), - "taiko" => Ok(PoktChains::Taiko), - "taikoheklatestnet" => Ok(PoktChains::TaikoHeklaTestnet), - "zklink" => Ok(PoktChains::ZkLink), - "zksyncera" => Ok(PoktChains::ZkSyncEra), - _ => Err(RelayErrors::PoktChainIdParsingError), - } - } -} - -#[derive(Debug)] -pub enum RelayErrors { - PoktRelayError(reqwest::Error), - PoktChainIdParsingError, -} - -impl From for RelayErrors { - fn from(value: reqwest::Error) -> Self { - RelayErrors::PoktRelayError(value) - } -} - -impl Display for RelayErrors { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - RelayErrors::PoktRelayError(_) => { - write!(f, "Failed to submit transaction or parse the response") - } - RelayErrors::PoktChainIdParsingError => write!(f, "Could not identify chain by id"), - } - } -} - -impl std::error::Error for RelayErrors { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - RelayErrors::PoktRelayError(e) => Some(e), - RelayErrors::PoktChainIdParsingError => None, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum PoktTestnetChains { - Holesky, - PocketTestnet, -} - -impl fmt::Display for PoktTestnetChains { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PoktTestnetChains::Holesky => write!(f, "0081"), - PoktTestnetChains::PocketTestnet => write!(f, "0002"), - } - } -} - -impl PoktTestnetChains { - pub fn id(&self) -> &'static str { - match self { - PoktTestnetChains::Holesky => "0081", - PoktTestnetChains::PocketTestnet => "0002", - } - } -} - -impl FromStr for PoktTestnetChains { - type Err = RelayErrors; - fn from_str(value: &str) -> Result { - match value.to_lowercase().as_ref() { - "holesky" => Ok(PoktTestnetChains::Holesky), - "pockettestnet" => Ok(PoktTestnetChains::PocketTestnet), - _ => Err(RelayErrors::PoktChainIdParsingError), - } - } -} diff --git a/src/database/types.rs b/src/database/types.rs index 61f3ce1..22a0ee2 100644 --- a/src/database/types.rs +++ b/src/database/types.rs @@ -27,11 +27,11 @@ impl Database { } #[derive(FromRow, Debug)] -pub struct Customers<'a> { - pub email: EmailAddress<'a>, +pub struct Customers { + pub email: EmailAddress, pub wallet: Option, pub role: Role, - pub password: Password<'a>, + pub password: Password, pub verificationcode: String, pub activated: bool, } @@ -45,8 +45,8 @@ pub struct PaymentInfo { } #[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct Payments<'a> { - pub customeremail: EmailAddress<'a>, +pub struct Payments { + pub customeremail: EmailAddress, pub transactionhash: String, pub asset: Asset, pub amount: String, @@ -57,8 +57,8 @@ pub struct Payments<'a> { } #[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct Api<'a> { - pub customeremail: EmailAddress<'a>, +pub struct Api { + pub customeremail: EmailAddress, pub apikey: String, } diff --git a/src/main.rs b/src/main.rs index 151a7a5..5e61a3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,9 @@ use crate::middleware::jwt_auth::verify_jwt; #[cfg(not(feature = "dev"))] use crate::middleware::rpc_service::validate_subscription_and_update_user_calls; use crate::routes::payment::{cancel, downgrade, upgrade}; +use crate::routes::relayer::router::{ + route_arb, route_base, route_bsc, route_eth, route_op, route_poly, route_solana, route_sui, +}; use crate::routes::relayer::websockets::ws_handler; use crate::routes::token_queries::{ aggregate_balances, aggregate_single_token_bals, aggregate_token_bals_for_user, @@ -15,7 +18,6 @@ use crate::routes::{ login::user_login, recovery::{recover_password_email, update_password}, register::register_user, - relayer::router::route_call, }; use axum::extract::DefaultBodyLimit; use axum::http::HeaderValue; @@ -71,7 +73,14 @@ async fn main() { .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION, header::COOKIE]); let relayer = Router::new() - .route("/rpc/{chain}/{api_key}", post(route_call)) + .route("/rpc/poly/{api_key}", post(route_poly)) + .route("/rpc/bsc/{api_key}", post(route_bsc)) + .route("/rpc/arb-one/{api_key}", post(route_arb)) + .route("/rpc/base/{api_key}", post(route_base)) + .route("/rpc/op/{api_key}", post(route_op)) + .route("/rpc/eth/{api_key}", post(route_eth)) + .route("/rpc/solana/{api_key}", post(route_solana)) + .route("/rpc/sui/{api_key}", post(route_sui)) .route("/ws/{chain}/{api_key}", axum::routing::any(ws_handler)); #[cfg(not(feature = "dev"))] diff --git a/src/middleware/rpc_service.rs b/src/middleware/rpc_service.rs index 588702a..39d0d37 100644 --- a/src/middleware/rpc_service.rs +++ b/src/middleware/rpc_service.rs @@ -11,16 +11,16 @@ use thiserror::Error; use time::OffsetDateTime; use tracing::{info, warn}; -pub struct Credits<'a> { +pub struct Credits { calls: i64, - email: EmailAddress<'a>, + email: EmailAddress, plan: Plan, expires: OffsetDateTime, } #[tracing::instrument(skip(request))] pub async fn validate_subscription_and_update_user_calls( - Path(key): Path<[String; 2]>, + Path(key): Path>, request: Request, next: Next, ) -> Result { @@ -33,7 +33,7 @@ pub async fn validate_subscription_and_update_user_calls( WHERE email = (SELECT customerEmail FROM Api WHERE apiKey = $1) "#, - key.get(1).ok_or_else(|| RpcAuthErrors::InvalidApiKey)? + &key ) .fetch_optional(db_connection) .await? @@ -68,8 +68,8 @@ pub async fn validate_subscription_and_update_user_calls( } #[derive(Debug)] -pub struct RenewInfo<'a> { - email: EmailAddress<'a>, +pub struct RenewInfo { + email: EmailAddress, plan: Plan, balance: i64, downgradeto: Option, diff --git a/src/routes/activate.rs b/src/routes/activate.rs index 8319b13..dd7e643 100644 --- a/src/routes/activate.rs +++ b/src/routes/activate.rs @@ -5,8 +5,8 @@ use thiserror::Error; #[derive(Serialize, Deserialize, Debug)] pub struct ActivationRequest { - pub email: String, - pub code: String, + pub email: sstr::Str<256>, + pub code: sstr::Str<8>, } #[derive(Serialize, Deserialize, Debug)] @@ -33,7 +33,7 @@ pub async fn activate_account( Err(ActivationError::AlreadyActivated)? } - if payload.code != code.verificationcode { + if payload.code.as_str() != code.verificationcode.as_str() { Err(ActivationError::InvalidCode)? } diff --git a/src/routes/api_keys.rs b/src/routes/api_keys.rs index d3be65e..37d6c13 100644 --- a/src/routes/api_keys.rs +++ b/src/routes/api_keys.rs @@ -6,7 +6,10 @@ use axum::{ response::IntoResponse, }; use jwt_simple::claims::JWTClaims; -use rand::{RngExt, rng}; +use rand::{ + distr::{Distribution, Uniform}, + rng, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -16,7 +19,7 @@ pub struct KeygenLimit { } pub async fn generate_api_keys( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, ) -> Result { let keys = sqlx::query_as!( KeygenLimit, @@ -51,7 +54,7 @@ pub struct Keys { #[tracing::instrument] pub async fn get_all_api_keys( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, ) -> Result { let keys: Vec = sqlx::query_as!( Keys, @@ -97,17 +100,14 @@ impl IntoResponse for ApiKeyError { #[inline] pub fn generate_api_key(size: usize) -> String { - rng() - .random_iter() + let range = Uniform::new_inclusive(97, 122).unwrap(); + let mut rng = rng(); + + range + .sample_iter(&mut rng) .take(size) - // 32-126 are the printable ASCII characters - .map(|e: u8| (e % 94) + 32) .fold(String::with_capacity(size), |mut acc, e| { - acc.push(e as char); + acc.push(char::from_u32(e).unwrap()); acc }) } - -// limits for API key generation to avoid abuse -// -// maybe scope api key permissions in the future diff --git a/src/routes/login.rs b/src/routes/login.rs index 86900cb..1ef1726 100644 --- a/src/routes/login.rs +++ b/src/routes/login.rs @@ -1,6 +1,6 @@ use super::{ siwe::Siwe, - types::{Claims, EmailAddress, JWT_KEY, Password, SiweNonce}, + types::{Claims, EmailAddress, JWT_KEY, SiweNonce}, }; use crate::{ database::{ @@ -22,23 +22,24 @@ use siwe::{Message, VerificationError, VerificationOpts}; use sqlx::prelude::FromRow; use thiserror::Error; use time::OffsetDateTime; +use sstr::Str; #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct LoginRequest<'a> { - pub(crate) email: EmailAddress<'a>, - pub(crate) password: Password<'a>, +pub struct LoginRequest { + pub(crate) email: Str<256>, + pub(crate) password: Str<256>, } #[derive(FromRow)] -pub struct SiweLogin<'a> { - pub email: EmailAddress<'a>, +pub struct SiweLogin { + pub email: EmailAddress, pub wallet: Option, pub role: Role, - pub nonce: SiweNonce<'a>, + pub nonce: SiweNonce, } pub async fn refresh( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, ) -> Result { let user = sqlx::query_as!( Customers, @@ -95,7 +96,7 @@ pub async fn user_login_siwe(Json(payload): Json) -> Result) -> Result>, + Json(payload): Json, ) -> Result { let db = RELATIONAL_DATABASE.get().unwrap(); let mut tx = db.begin().await?; @@ -202,6 +203,7 @@ impl IntoResponse for LoginError { pub mod tests { use hex::ToHex; use jwt_simple::algorithms::{HS256Key, MACLike}; + use sstr::Str; #[test] fn get_key() -> Result<(), Box> { @@ -220,7 +222,7 @@ pub mod tests { activate::{ActivationRequest, activate_account}, api_keys::generate_api_keys, login::LoginRequest, - types::{EmailAddress, Password, RegisterUser}, + types::RegisterUser, }, user_login, }; @@ -250,8 +252,8 @@ pub mod tests { reqwest::Client::new() .post("http://localhost:3030/api/register") .json(&RegisterUser { - email: "abc@aol.com".to_string(), - password: "test".to_string(), + email: Str::new("abc@aol.com"), + password: Str::new("test"), }) .send() .await @@ -276,8 +278,8 @@ pub mod tests { tx.commit().await.unwrap(); let ar = ActivationRequest { - code: code.verificationcode, - email: "abc@aol.com".to_string(), + code: Str::new(&code.verificationcode), + email: Str::new("abc@aol.com"), }; reqwest::Client::new() @@ -288,8 +290,8 @@ pub mod tests { .unwrap(); let lr = LoginRequest { - email: EmailAddress("abc@aol.com".into()), - password: Password("test".into()), + email: Str::new("abc@aol.com"), + password: Str::new("test"), }; let ddrpc_client = reqwest::Client::builder() diff --git a/src/routes/payment.rs b/src/routes/payment.rs index 8fe8580..e2cd59c 100644 --- a/src/routes/payment.rs +++ b/src/routes/payment.rs @@ -151,8 +151,8 @@ pub struct UserBalances { balance: i64, } -pub async fn get_calls_and_balance<'a>( - Extension(jwt): Extension>>, +pub async fn get_calls_and_balance( + Extension(jwt): Extension>, ) -> Result { let res = sqlx::query_as!( UserBalances, @@ -170,8 +170,8 @@ pub async fn get_calls_and_balance<'a>( } #[derive(Debug, Serialize, Deserialize)] -pub struct PaymentData<'a> { - pub customeremail: EmailAddress<'a>, +pub struct PaymentData { + pub customeremail: EmailAddress, pub transactionhash: String, pub asset: Asset, pub amount: String, @@ -181,9 +181,9 @@ pub struct PaymentData<'a> { pub decimals: i32, } -pub async fn get_payments<'a>( +pub async fn get_payments( Query(params): Query, - Extension(jwt): Extension>>, + Extension(jwt): Extension>, ) -> Result { let res: Vec = sqlx::query_as!( Payments, @@ -223,7 +223,7 @@ pub struct Cancel { /// cancels a user's subscription pub async fn cancel( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, ) -> Result { let mut tx = RELATIONAL_DATABASE.get().unwrap().begin().await?; @@ -256,7 +256,7 @@ pub struct RpcPlan { /// Downgrades the service tier for active plan next cycle pub async fn downgrade( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, Json(payload): Json, ) -> Result { let mut tx = RELATIONAL_DATABASE.get().unwrap().begin().await?; @@ -290,7 +290,7 @@ pub async fn downgrade( } pub async fn upgrade( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, Json(payload): Json, ) -> Result { let mut tx = RELATIONAL_DATABASE.get().unwrap().begin().await?; @@ -358,7 +358,7 @@ pub static D_D_CLOUD_API_KEY: LazyLock<&'static str> = #[tracing::instrument] pub async fn process_ethereum_payment( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, Json(payload): Json, ) -> Result { #[cfg(not(test))] @@ -583,7 +583,7 @@ async fn stablecoin_fixedpoint_conversion( Ok(value) } -async fn insert_payment(payment: &Payments<'_>) -> Result<(), PaymentError> { +async fn insert_payment(payment: &Payments) -> Result<(), PaymentError> { // append only -- isolated let db_connection = RELATIONAL_DATABASE.get().unwrap(); sqlx::query!( @@ -604,7 +604,7 @@ async fn insert_payment(payment: &Payments<'_>) -> Result<(), PaymentError> { } async fn credit_account( - email: &EmailAddress<'_>, + email: &EmailAddress, mut amount: i64, plan: Option, ) -> Result<(), PaymentError> { @@ -713,7 +713,7 @@ mod tests { activate::{ActivationRequest, activate_account}, api_keys::generate_api_keys, login::LoginRequest, - types::{EmailAddress, Password, RegisterUser}, + types::RegisterUser, }, user_login, }; @@ -721,6 +721,7 @@ mod tests { use axum::{Router, middleware::from_fn, routing::post}; use dotenvy::dotenv; use std::time::Duration; + use sstr::Str; #[tokio::test] async fn test_payment() { @@ -790,8 +791,8 @@ mod tests { let _reg_res = reqwest::Client::new() .post("http://localhost:3072/api/register") .json(&RegisterUser { - email: "cloud@developerdao.com".to_string(), - password: "test".to_string(), + email: Str::new("cloud@developerdao.com"), + password: Str::new("test"), }) .send() .await @@ -816,8 +817,8 @@ mod tests { .unwrap(); let ar = ActivationRequest { - code: code.verificationcode, - email: "cloud@developerdao.com".to_string(), + code: Str::new(&code.verificationcode), + email: Str::new("cloud@developerdao.com"), }; println!("Signer Address: {}", signer.address()); sqlx::query!( @@ -837,8 +838,8 @@ mod tests { .unwrap(); let lr = LoginRequest { - email: EmailAddress("cloud@developerdao.com".into()), - password: Password("test".into()), + email: Str::new("cloud@developerdao.com"), + password: Str::new("test"), }; let ddrpc_client = reqwest::Client::builder() diff --git a/src/routes/register.rs b/src/routes/register.rs index dd4786a..6e62671 100644 --- a/src/routes/register.rs +++ b/src/routes/register.rs @@ -208,8 +208,8 @@ pub mod test { let res = reqwest::Client::new() .post("http://localhost:3111/api/register") .json(&RegisterUser { - email: to.to_string(), - password: "test".to_string(), + email: sstr::Str::new(&to), + password: sstr::Str::new("test"), }) .send() .await diff --git a/src/routes/relayer/router.rs b/src/routes/relayer/router.rs index 124ac77..528224d 100644 --- a/src/routes/relayer/router.rs +++ b/src/routes/relayer/router.rs @@ -1,28 +1,35 @@ use super::types::RelayErrors; use crate::routes::relayer::types::{PoktChains, Relayer}; -use axum::{body::Bytes, extract::Path, http::StatusCode, response::IntoResponse}; +use axum::{body::Bytes, http::StatusCode, response::IntoResponse}; use thiserror::Error; -pub async fn route_call( - Path(route_info): Path<[String; 2]>, - body: Bytes, -) -> Result { - let raw_destination = route_info - .first() - .ok_or_else(|| RouterErrors::DestinationError)?; - let dest = raw_destination.parse::()?; - let result = dest.relay_transaction(body).await?; - Ok((StatusCode::OK, result)) +macro_rules! route { + ($($i:ident = $t:expr)+) => { + $( + pub async fn $i( + body: Bytes, + ) -> Result { + Ok((StatusCode::OK, $t.relay_transaction(body).await?)) + } + )+ + } +} + +route! { + route_arb = PoktChains::ArbOne + route_op = PoktChains::Op + route_sui = PoktChains::Sui + route_poly = PoktChains::Poly + route_solana = PoktChains::Solana + route_base = PoktChains::Base + route_eth = PoktChains::Eth + route_bsc = PoktChains::Bsc } #[derive(Debug, Error)] pub enum RouterErrors { - #[error("Could not parse destination from the first Path parameter")] - DestinationError, #[error(transparent)] Relay(#[from] RelayErrors), - #[error("malformed payload")] - NotJsonRpc, } impl IntoResponse for RouterErrors { @@ -47,7 +54,7 @@ pub mod test { .as_bytes(); let bytes = axum::body::Bytes::from_static(body); - let chain = "anvil"; + let chain = "base"; let dest = chain.parse::().unwrap(); let res = dest.relay_transaction(bytes).await; assert!(res.is_ok()); diff --git a/src/routes/relayer/types.rs b/src/routes/relayer/types.rs index c8a30a0..eb62f14 100644 --- a/src/routes/relayer/types.rs +++ b/src/routes/relayer/types.rs @@ -8,6 +8,8 @@ use std::{ str::FromStr, sync::LazyLock, }; +use thiserror::Error; + pub static GATEWAY_ENDPOINT: LazyLock<&'static str> = LazyLock::new(|| { format!( "{}/v1", @@ -15,6 +17,7 @@ pub static GATEWAY_ENDPOINT: LazyLock<&'static str> = LazyLock::new(|| { ) .leak() }); +pub static PROXY_CLIENT: LazyLock = LazyLock::new(Client::new); pub trait Relayer { fn relay_transaction(&self, body: Bytes) -> impl Future>; @@ -77,9 +80,14 @@ impl PoktChains { impl Relayer for PoktChains { async fn relay_transaction(&self, body: Bytes) -> Result { if cfg!(test) || cfg!(feature = "dev") { - let provider = dotenvy::var("SEPOLIA_PROVIDER").expect("SEPOLIA_PROVIDER not found"); - let byte_stream = Client::new() - .post(&provider) + let api_key = dotenvy::var("D_D_CLOUD_API_KEY").expect("D_D_CLOUD_API_KEY"); + let provider = format!( + "https://api.cloud.developerdao.com/rpc/{}/{}", + self.id(), + &api_key + ); + let byte_stream = PROXY_CLIENT + .post(provider) .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) .body(body) .send() @@ -89,7 +97,7 @@ impl Relayer for PoktChains { let body = Body::from_stream(byte_stream); Ok(body) } else { - let byte_stream = Client::new() + let byte_stream = PROXY_CLIENT .post(*GATEWAY_ENDPOINT) .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) .header("target-service-id", self.id()) @@ -123,18 +131,13 @@ impl FromStr for PoktChains { } } -#[derive(Debug)] +#[derive(Error, Debug)] pub enum RelayErrors { - PoktRelayError(reqwest::Error), + #[error(transparent)] + PoktRelayError(#[from] reqwest::Error), PoktChainIdParsingError, } -impl From for RelayErrors { - fn from(value: reqwest::Error) -> Self { - RelayErrors::PoktRelayError(value) - } -} - impl Display for RelayErrors { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -145,12 +148,3 @@ impl Display for RelayErrors { } } } - -impl std::error::Error for RelayErrors { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - RelayErrors::PoktRelayError(e) => Some(e), - RelayErrors::PoktChainIdParsingError => None, - } - } -} diff --git a/src/routes/siwe.rs b/src/routes/siwe.rs index 6908e18..32e2b86 100644 --- a/src/routes/siwe.rs +++ b/src/routes/siwe.rs @@ -19,13 +19,13 @@ pub struct Siwe { pub signature: Vec, } -pub struct Nonce<'a> { - nonce: SiweNonce<'a>, +pub struct Nonce { + nonce: SiweNonce, } #[tracing::instrument] pub async fn siwe_add_wallet( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, Json(payload): Json, ) -> Result { let msg: Message = payload.message.parse()?; @@ -49,7 +49,7 @@ pub async fn siwe_add_wallet( let verification_opts = VerificationOpts { domain: Some(domain.parse().unwrap()), - nonce: Some(nonce.to_string()), + nonce: Some(nonce.0), timestamp: Some(OffsetDateTime::now_utc()), rpc_provider: Some(rpc), }; @@ -89,7 +89,7 @@ pub async fn get_siwe_nonce( #[tracing::instrument] pub async fn jwt_get_siwe_nonce( - Extension(jwt): Extension>>, + Extension(jwt): Extension>, ) -> Result { let nonce = generate_nonce(); let mut tx = RELATIONAL_DATABASE.get().unwrap().begin().await?; diff --git a/src/routes/token_queries.rs b/src/routes/token_queries.rs index 4dd6272..38f0b55 100644 --- a/src/routes/token_queries.rs +++ b/src/routes/token_queries.rs @@ -162,11 +162,18 @@ pub async fn aggregate_single_token_bals( Ok((StatusCode::OK, serde_json::to_string(&res)?).into_response()) } +#[derive(Serialize, Deserialize)] +pub struct NftCollectionPagination { + collection: Address, + offset: u16, + limit: u16 +} + pub async fn get_batch_nft_info( Path((chain, api_key)): Path<(PoktChains, String)>, - Query((collection, offset, limit)): Query<(Address, u16, u16)>, + Json(query): Json, ) -> Result { - if limit > 10000 { + if query.limit > 10000 { Err(QueryError::NFTQueryLimit)? } @@ -186,7 +193,7 @@ pub async fn get_batch_nft_info( let contract = TokenQueryContractInstance::new(c_addr, provider); let res = contract - .getBatchNftInfo(collection, U256::from(offset), U256::from(limit)) + .getBatchNftInfo(query.collection, U256::from(query.offset), U256::from(query.limit)) .call() .await?; diff --git a/src/routes/types.rs b/src/routes/types.rs index a07d989..bc360cb 100644 --- a/src/routes/types.rs +++ b/src/routes/types.rs @@ -3,127 +3,53 @@ use alloy::primitives::Address; use axum::{http::StatusCode, response::IntoResponse}; use jwt_simple::algorithms::HS256Key; use serde::{Deserialize, Serialize}; -use std::borrow::Cow; +use sstr::Str; use std::error::Error; use std::sync::OnceLock; + pub static JWT_KEY: OnceLock = OnceLock::new(); pub static SERVER_EMAIL: OnceLock = OnceLock::new(); -/// Does not support generic or regular structs -/// Works for new type structs with one inner type regardless of lifetimes -/// Inner types must implement sqlx::Type -/// Supports doc comments and derive macros -macro_rules! PgNewType { - ($($(#[doc = $doc:expr])* $(#[derive($($trait:ty),+)])? $visibility:vis struct $newtype:ident$(<$lt:lifetime>)?($inner_vis:vis $type:ty);)*) => { - use sqlx::{Type, Decode}; - use std::fmt::Display; - $( - $(#[doc = $doc])* - $(#[derive($($trait,)+)])? - $visibility struct $newtype$(<$lt>)?($inner_vis $type); - - impl$(<$lt>)? Display for $newtype$(<$lt>)? { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } - } - - impl<'a, DB> sqlx::Decode<'a, DB> for $newtype$(<$lt>)? - where DB: sqlx::Database, - $type: Decode<'a, DB> - { - fn decode(arg: ::ValueRef<'a>) -> Result> { - <$type as sqlx::Decode>::decode(arg).map(|t| $newtype(t)) - } - } - - impl<'a, DB> sqlx::Encode<'a, DB> for $newtype$(<$lt>)? - where - DB: sqlx::Database, - $type: sqlx::Encode<'a, DB>, - { - fn encode( - self, - buf: &mut ::ArgumentBuffer<'a>, - ) -> Result - where - Self: Sized, - { - <$type as sqlx::Encode>::encode(self.0, buf) - } - - fn encode_by_ref( - &self, - buf: &mut ::ArgumentBuffer<'a>, - ) -> Result { - <$type as sqlx::Encode>::encode(self.0.clone(), buf) - } - } - - impl<$($lt,)?DB> Type for $newtype$(<$lt>)? - where DB: sqlx::Database, - $type: Type - { - fn type_info() -> ::TypeInfo { - <$type as Type>::type_info() - } - } - - )+ - } -} - -PgNewType! { - #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] - pub struct EmailAddress<'a>(pub Cow<'a, str>); +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone, sqlx::Type)] +pub struct EmailAddress(pub String); - #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] - pub struct Password<'a>(pub Cow<'a, str>); +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone, sqlx::Type)] +pub struct Password(pub String); - #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] - pub struct SiweNonce<'a>(pub Cow<'a, str>); -} +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone, sqlx::Type)] +pub struct SiweNonce(pub String); -// impl From for PraiseKek { -// fn from(value: String) -> Self { -// PraiseKek( -// OffsetDateTime::parse(&value, &Rfc3339) -// .expect("Date is not Rfc3339 compliant"), -// ) -// } -// } - -impl<'a> Password<'a> { - pub fn as_bytes(&'a self) -> &'a [u8] { +impl Password { + pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } - pub fn as_str(&'a self) -> &'a str { + pub fn as_str(&self) -> &str { &self.0 } } /// an email address that is not guarateed to be valid -impl<'a> EmailAddress<'a> { +impl EmailAddress { pub fn as_str(&self) -> &str { &self.0 } } -impl<'a> From for EmailAddress<'a> { +impl From for EmailAddress { fn from(value: String) -> Self { - EmailAddress(value.into()) + EmailAddress(value) } } -impl<'a> From for Password<'a> { +impl From for Password { fn from(value: String) -> Self { - Password(value.into()) + Password(value) } } -impl<'a> From for SiweNonce<'a> { +impl From for SiweNonce { fn from(value: String) -> Self { - SiweNonce(value.into()) + SiweNonce(value) } } @@ -139,9 +65,9 @@ impl JWTKey { } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Claims<'a> { +pub struct Claims { pub role: Role, - pub email: EmailAddress<'a>, + pub email: EmailAddress, pub wallet: Option
, } @@ -165,8 +91,8 @@ impl EmailLogin { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RegisterUser { - pub email: String, - pub password: String, + pub email: Str<256>, + pub password: Str<256>, } impl IntoResponse for RegisterUser {