diff --git a/Cargo.lock b/Cargo.lock index dbedae7d..4899f091 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2567,7 +2567,9 @@ version = "0.0.0" dependencies = [ "base64 0.22.1", "bincode", + "email_address", "ethers", + "http 1.2.0", "serde", "serde_json", "serde_with 3.11.0", diff --git a/Cargo.toml b/Cargo.toml index a85a502f..6c91a8bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ solana-sdk = "2.0.13" solana-rpc-client = "2.0.13" solana-transaction-status = "2.0.13" solana-client = "2.0.13" +email_address = "0.2.4" # The curve25519-dalek crate is a dependency of solana-sdk. # This crate relies on a specific version of zeroize that is incompatible with many other packages. diff --git a/auction-server/Cargo.toml b/auction-server/Cargo.toml index d2594e43..02494941 100644 --- a/auction-server/Cargo.toml +++ b/auction-server/Cargo.toml @@ -30,7 +30,7 @@ axum-macros = "0.4.0" sqlx = { version = "0.7.4", features = [ "runtime-tokio", "tls-native-tls", "postgres", "time", "uuid", "bigdecimal", "json" ] } tokio-util = { version = "0.7.10", features = ["rt"] } axum-extra = { version = "0.9.3", features = ["typed-header"] } -email_address = "0.2.4" +email_address = { workspace = true } rand = "0.8.5" base64 = { workspace = true } time = { workspace = true, features = ["serde"] } diff --git a/auction-server/api-types/Cargo.toml b/auction-server/api-types/Cargo.toml index af8d7841..0b8c1078 100644 --- a/auction-server/api-types/Cargo.toml +++ b/auction-server/api-types/Cargo.toml @@ -17,3 +17,5 @@ ethers = { workspace = true } time = { workspace = true, features = ["serde"] } base64 = { workspace = true } bincode = { workspace = true } +email_address = { workspace = true } +http = "1.2.0" diff --git a/auction-server/api-types/src/auction.rs b/auction-server/api-types/src/auction.rs deleted file mode 100644 index b413e3a3..00000000 --- a/auction-server/api-types/src/auction.rs +++ /dev/null @@ -1,3 +0,0 @@ -use uuid::Uuid; - -pub type BidId = Uuid; diff --git a/auction-server/api-types/src/bid.rs b/auction-server/api-types/src/bid.rs new file mode 100644 index 00000000..44dcc792 --- /dev/null +++ b/auction-server/api-types/src/bid.rs @@ -0,0 +1,301 @@ +use { + crate::{ + profile::ProfileId, + ChainId, + PermissionKeyEvm, + PermissionKeySvm, + }, + ethers::types::{ + Address, + Bytes, + H256, + U256, + }, + serde::{ + Deserialize, + Serialize, + }, + serde_with::{ + serde_as, + DisplayFromStr, + }, + solana_sdk::{ + signature::Signature, + transaction::VersionedTransaction, + }, + time::OffsetDateTime, + utoipa::{ + IntoParams, + ToResponse, + ToSchema, + }, + uuid::Uuid, +}; + +pub type BidId = Uuid; +pub type BidAmountSvm = u64; +pub type BidAmountEvm = U256; + +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum BidStatusEvm { + /// The temporary state which means the auction for this bid is pending. + #[schema(title = "Pending")] + Pending, + /// The bid is submitted to the chain, which is placed at the given index of the transaction with the given hash. + /// This state is temporary and will be updated to either lost or won after conclusion of the auction. + #[schema(title = "Submitted")] + Submitted { + #[schema(example = "0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3", value_type = String)] + result: H256, + #[schema(example = 1, value_type = u32)] + index: u32, + }, + /// The bid lost the auction, which is concluded with the transaction with the given hash and index. + /// The result will be None if the auction was concluded off-chain and no auction was submitted to the chain. + /// The index will be None if the bid was not submitted to the chain and lost the auction by off-chain calculation. + /// There are cases where the result is not None and the index is None. + /// It is because other bids were selected for submission to the chain, but not this one. + #[schema(title = "Lost")] + Lost { + #[schema(example = "0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3", value_type = Option)] + result: Option, + #[schema(example = 1, value_type = Option)] + index: Option, + }, + /// The bid won the auction, which is concluded with the transaction with the given hash and index. + #[schema(title = "Won")] + Won { + #[schema(example = "0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3", value_type = String)] + result: H256, + #[schema(example = 1, value_type = u32)] + index: u32, + }, +} + +#[serde_as] +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum BidStatusSvm { + /// The temporary state which means the auction for this bid is pending. + #[schema(title = "Pending")] + Pending, + /// The bid lost the auction. + /// The result will be None if the auction does not result in a transaction being submitted to the chain. + /// The result will be Some if this bid lost to another bid and the winning bid was submitted to the chain. + /// The signature of the transaction for the submitted bid is the result value. + #[schema(title = "Lost")] + Lost { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = Option)] + #[serde(with = "crate::serde::nullable_signature_svm")] + result: Option, + }, + /// The bid won the auction and was submitted to the chain, with the transaction with the signature. + /// This state is temporary and will be updated to either Won or Failed after the transaction is included in a block, or Expired if the transaction expires before it is included. + #[schema(title = "Submitted")] + Submitted { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] + #[serde_as(as = "DisplayFromStr")] + result: Signature, + }, + /// The bid won the auction and was included in a block successfully. + #[schema(title = "Won")] + Won { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] + #[serde_as(as = "DisplayFromStr")] + result: Signature, + }, + /// The bid was submitted on-chain, was included in a block, but resulted in a failed transaction. + #[schema(title = "Failed")] + Failed { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] + #[serde_as(as = "DisplayFromStr")] + result: Signature, + }, + /// The bid was submitted on-chain but expired before it was included in a block. + #[schema(title = "Expired")] + Expired { + #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] + #[serde_as(as = "DisplayFromStr")] + result: Signature, + }, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] +#[serde(untagged)] +pub enum BidStatus { + Evm(BidStatusEvm), + Svm(BidStatusSvm), +} + +#[derive(Serialize, Deserialize, ToResponse, ToSchema, Clone)] +pub struct BidResult { + /// The status of the request. If the bid was placed successfully, the status will be "OK". + #[schema(example = "OK")] + pub status: String, + /// The unique id created to identify the bid. This id can be used to query the status of the bid. + #[schema(example = "beedbeed-58cc-4372-a567-0e02b2c3d479", value_type=String)] + pub id: BidId, +} + +#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] +pub struct BidCoreFields { + /// The unique id for bid. + #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] + pub id: BidId, + /// The chain id for bid. + #[schema(example = "op_sepolia", value_type = String)] + pub chain_id: ChainId, + /// The time server received the bid formatted in rfc3339. + #[schema(example = "2024-05-23T21:26:57.329954Z", value_type = String)] + #[serde(with = "time::serde::rfc3339")] + pub initiation_time: OffsetDateTime, + /// The profile id for the bid owner. + #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] + pub profile_id: Option, +} + +#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] +pub struct BidSvm { + #[serde(flatten)] + #[schema(inline)] + pub core_fields: BidCoreFields, + /// The latest status for bid. + pub status: BidStatusSvm, + /// The transaction of the bid. + #[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)] + #[serde(with = "crate::serde::transaction_svm")] + pub transaction: VersionedTransaction, + /// Amount of bid in lamports. + #[schema(example = "1000", value_type = u64)] + pub bid_amount: BidAmountSvm, + /// The permission key for bid in base64 format. + /// This is the concatenation of the permission account and the router account. + #[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)] + pub permission_key: PermissionKeySvm, +} + +#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] +pub struct BidEvm { + #[serde(flatten)] + #[schema(inline)] + pub core_fields: BidCoreFields, + /// The latest status for bid. + pub status: BidStatusEvm, + /// The contract address to call. + #[schema(example = "0xcA11bde05977b3631167028862bE2a173976CA11", value_type = String)] + pub target_contract: Address, + /// Calldata for the contract call. + #[schema(example = "0xdeadbeef", value_type = String)] + pub target_calldata: Bytes, + /// The gas limit for the contract call. + #[schema(example = "2000000", value_type = String)] + #[serde(with = "crate::serde::u256")] + pub gas_limit: U256, + /// Amount of bid in wei. + #[schema(example = "10", value_type = String)] + #[serde(with = "crate::serde::u256")] + pub bid_amount: BidAmountEvm, + /// The permission key for bid. + #[schema(example = "0xdeadbeef", value_type = String)] + pub permission_key: PermissionKeyEvm, +} + +#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +pub enum Bid { + Evm(BidEvm), + Svm(BidSvm), +} + +impl Bid { + pub fn get_initiation_time(&self) -> OffsetDateTime { + match self { + Bid::Evm(bid) => bid.core_fields.initiation_time, + Bid::Svm(bid) => bid.core_fields.initiation_time, + } + } + + pub fn get_status(&self) -> BidStatus { + match self { + Bid::Evm(bid) => BidStatus::Evm(bid.status.clone()), + Bid::Svm(bid) => BidStatus::Svm(bid.status.clone()), + } + } +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +pub struct BidCreateEvm { + /// The permission key to bid on. + #[schema(example = "0xdeadbeef", value_type = String)] + pub permission_key: PermissionKeyEvm, + /// The chain id to bid on. + #[schema(example = "op_sepolia", value_type = String)] + pub chain_id: ChainId, + /// The contract address to call. + #[schema(example = "0xcA11bde05977b3631167028862bE2a173976CA11", value_type = String)] + pub target_contract: Address, + /// Calldata for the contract call. + #[schema(example = "0xdeadbeef", value_type = String)] + pub target_calldata: Bytes, + /// Amount of bid in wei. + #[schema(example = "10", value_type = String)] + #[serde(with = "crate::serde::u256")] + pub amount: BidAmountEvm, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +pub struct BidCreateSvm { + /// The chain id to bid on. + #[schema(example = "solana", value_type = String)] + pub chain_id: ChainId, + /// The transaction for bid. + #[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)] + #[serde(with = "crate::serde::transaction_svm")] + pub transaction: VersionedTransaction, +} + +#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] +#[serde(untagged)] // Remove tags to avoid key-value wrapping +pub enum BidCreate { + Evm(BidCreateEvm), + Svm(BidCreateSvm), +} + +#[derive(Serialize, Clone, ToSchema, ToResponse)] +pub struct BidStatusWithId { + #[schema(value_type = String)] + pub id: BidId, + pub bid_status: BidStatus, +} + +#[derive(Serialize, Deserialize, IntoParams, Clone)] +pub struct GetBidStatusParams { + #[param(example="op_sepolia", value_type = String)] + pub chain_id: ChainId, + + #[param(example="obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] + pub bid_id: BidId, +} + +#[derive(Serialize, Deserialize, ToResponse, ToSchema, Clone)] +pub struct Bids { + pub items: Vec, +} + +#[derive(Serialize, Deserialize, IntoParams)] +pub struct GetBidsByTimeQueryParams { + #[param(example="2024-05-23T21:26:57.329954Z", value_type = Option)] + #[serde(default, with = "crate::serde::nullable_datetime")] + pub from_time: Option, +} + +impl BidCreate { + pub fn get_chain_id(&self) -> ChainId { + match self { + BidCreate::Evm(bid_create_evm) => bid_create_evm.chain_id.clone(), + BidCreate::Svm(bid_create_svm) => bid_create_svm.chain_id.clone(), + } + } +} diff --git a/auction-server/api-types/src/lib.rs b/auction-server/api-types/src/lib.rs index aff590a4..da8e10ee 100644 --- a/auction-server/api-types/src/lib.rs +++ b/auction-server/api-types/src/lib.rs @@ -1,5 +1,71 @@ -pub mod auction; +use { + ::serde::{ + Deserialize, + Serialize, + }, + ethers::types::Bytes, + serde_with::{ + base64::{ + Base64, + Standard, + }, + formats::Padded, + serde_as, + DeserializeAs, + DisplayFromStr, + SerializeAs, + }, + solana_sdk::hash::Hash, + utoipa::{ + ToResponse, + ToSchema, + }, +}; + +pub mod bid; pub mod opportunity; +pub mod profile; pub mod serde; +pub type MicroLamports = u64; pub type ChainId = String; +pub type PermissionKeyEvm = Bytes; +#[derive(Clone, Debug)] +pub struct PermissionKeySvm(pub [u8; 64]); +impl Serialize for PermissionKeySvm { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + Base64::::serialize_as(&self.0, serializer) + } +} + +impl<'de> Deserialize<'de> for PermissionKeySvm { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + let bytes = Base64::::deserialize_as(deserializer)?; + Ok(PermissionKeySvm(bytes)) + } +} + +#[serde_as] +#[derive(Serialize, Clone, ToSchema, ToResponse)] +pub struct SvmChainUpdate { + #[schema(example = "solana", value_type = String)] + pub chain_id: ChainId, + #[serde_as(as = "DisplayFromStr")] + #[schema(example = "SLxp9LxX1eE9Z5v99Y92DaYEwyukFgMUF6zRerCF12j", value_type = String)] + pub blockhash: Hash, + /// The prioritization fee that the server suggests to use for the next transaction + #[schema(example = "1000", value_type = u64)] + pub latest_prioritization_fee: MicroLamports, +} + +#[derive(ToResponse, ToSchema, Serialize)] +#[response(description = "An error occurred processing the request")] +pub struct ErrorBodyResponse { + pub error: String, +} diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index cc55b0ff..da709b93 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -1,7 +1,8 @@ use { crate::{ - auction::BidId, + bid::BidId, ChainId, + PermissionKeyEvm, }, ethers::types::{ Address, @@ -85,7 +86,7 @@ pub struct OpportunityDeleteV1Svm { pub struct OpportunityDeleteV1Evm { /// The permission key of the opportunity. #[schema(example = "0xdeadbeefcafe", value_type = String)] - pub permission_key: Bytes, + pub permission_key: PermissionKeyEvm, /// The chain id for the opportunity. #[schema(example = "solana", value_type = String)] pub chain_id: ChainId, @@ -138,7 +139,7 @@ pub struct TokenAmountEvm { pub struct OpportunityCreateV1Evm { /// The permission key required for successful execution of the opportunity. #[schema(example = "0xdeadbeefcafe", value_type = String)] - pub permission_key: Bytes, + pub permission_key: PermissionKeyEvm, /// The chain id where the opportunity will be executed. #[schema(example = "op_sepolia", value_type = String)] pub chain_id: String, @@ -397,7 +398,7 @@ pub struct GetOpportunitiesQueryParams { pub mode: OpportunityMode, /// The permission key to filter the opportunities by. Used only in historical mode. #[param(example = "0xdeadbeef", value_type = Option< String >)] - pub permission_key: Option, + pub permission_key: Option, /// The time to get the opportunities from. #[param(example="2024-05-23T21:26:57.329954Z", value_type = Option)] #[serde(default, with = "crate::serde::nullable_datetime")] @@ -412,7 +413,7 @@ pub struct GetOpportunitiesQueryParams { pub struct OpportunityBidEvm { /// The opportunity permission key. #[schema(example = "0xdeadbeefcafe", value_type=String)] - pub permission_key: Bytes, + pub permission_key: PermissionKeyEvm, /// The bid amount in wei. #[schema(example = "1000000000000000000", value_type=String)] #[serde(with = "crate::serde::u256")] diff --git a/auction-server/api-types/src/profile.rs b/auction-server/api-types/src/profile.rs new file mode 100644 index 00000000..8cdb3650 --- /dev/null +++ b/auction-server/api-types/src/profile.rs @@ -0,0 +1,70 @@ +use { + email_address::EmailAddress, + serde::{ + Deserialize, + Serialize, + }, + utoipa::{ + IntoParams, + ToResponse, + ToSchema, + }, + uuid::Uuid, +}; + +pub type ProfileId = Uuid; + +#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse, Debug)] +#[serde(rename_all = "lowercase")] +pub enum ProfileRole { + Searcher, + Protocol, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse, Debug)] +pub struct CreateProfile { + /// The name of the profile to create. + #[schema(example = "John Doe")] + pub name: String, + /// The email of the profile to create. + #[schema(example = "example@example.com", value_type = String)] + pub email: String, + /// The role of the profile to create. + pub role: ProfileRole, +} + +#[derive(Serialize, Deserialize, Clone, Debug, IntoParams)] +pub struct GetProfile { + /// The email of the profile to fetch. + #[param(example = "example@example.com", value_type = String)] + pub email: String, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse)] +pub struct Profile { + /// The id of the profile. + #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] + pub id: ProfileId, + /// The name of the profile. + #[schema(example = "John Doe")] + pub name: String, + /// The email of the profile. + #[schema(example = "example@example.com", value_type = String)] + pub email: EmailAddress, + /// The role of the profile. + pub role: ProfileRole, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse)] +pub struct CreateAccessToken { + /// The id of the profile to create token for. + #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] + pub profile_id: ProfileId, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse)] +pub struct AccessToken { + /// The token for later use. + #[schema(example = "_q9zUYP-tQg8F7kQi2Rfl5c6sSy7xcc2yWh2H-nI-iI", value_type = String)] + pub token: String, +} diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index 445d1a27..2c89c709 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -8,10 +8,7 @@ use { ServerResultResponse, ServerUpdateResponse, }, - auction::api::{ - self as bid, - SvmChainUpdate, - }, + auction::api as bid, config::RunOptions, models, opportunity::api as opportunity, @@ -59,8 +56,10 @@ use { }, clap::crate_version, ethers::types::Bytes, - express_relay_api_types as api_types, - serde::Serialize, + express_relay_api_types::{ + self as api_types, + ErrorBodyResponse, + }, std::sync::{ atomic::Ordering, Arc, @@ -74,8 +73,6 @@ use { }, Modify, OpenApi, - ToResponse, - ToSchema, }, utoipa_redoc::{ Redoc, @@ -166,12 +163,6 @@ impl RestError { } } -#[derive(ToResponse, ToSchema, Serialize)] -#[response(description = "An error occurred processing the request")] -pub struct ErrorBodyResponse { - error: String, -} - impl IntoResponse for RestError { fn into_response(self) -> Response { let (status, msg) = self.to_status_and_message(); @@ -294,19 +285,19 @@ pub async fn start_api(run_options: RunOptions, store: Arc) -> Result< components( schemas( APIResponse, - bid::BidCreate, - bid::BidCreateEvm, - bid::BidCreateSvm, - bid::BidStatus, - bid::BidStatusEvm, - bid::BidStatusSvm, - bid::BidStatusWithId, - bid::BidResult, - bid::Bid, - bid::BidEvm, - bid::BidSvm, - bid::Bids, - SvmChainUpdate, + api_types::bid::BidCreate, + api_types::bid::BidCreateEvm, + api_types::bid::BidCreateSvm, + api_types::bid::BidStatus, + api_types::bid::BidStatusEvm, + api_types::bid::BidStatusSvm, + api_types::bid::BidStatusWithId, + api_types::bid::BidResult, + api_types::bid::Bid, + api_types::bid::BidEvm, + api_types::bid::BidSvm, + api_types::bid::Bids, + api_types::SvmChainUpdate, api_types::opportunity::OpportunityBidEvm, api_types::opportunity::OpportunityBidResult, @@ -350,8 +341,8 @@ pub async fn start_api(run_options: RunOptions, store: Arc) -> Result< responses( ErrorBodyResponse, api_types::opportunity::Opportunity, - bid::BidResult, - bid::Bids, + api_types::bid::BidResult, + api_types::bid::Bids, ), ), tags( diff --git a/auction-server/src/api/profile.rs b/auction-server/src/api/profile.rs index da4c5416..29ca1ed6 100644 --- a/auction-server/src/api/profile.rs +++ b/auction-server/src/api/profile.rs @@ -15,26 +15,17 @@ use { }, Json, }, - email_address::EmailAddress, - serde::{ - Deserialize, - Serialize, + express_relay_api_types::profile::{ + AccessToken, + CreateAccessToken, + CreateProfile, + GetProfile, + Profile, + ProfileRole, }, std::sync::Arc, - utoipa::{ - IntoParams, - ToResponse, - ToSchema, - }, }; -#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse, Debug)] -#[serde(rename_all = "lowercase")] -pub enum ProfileRole { - Searcher, - Protocol, -} - impl From for ProfileRole { fn from(role: models::ProfileRole) -> Self { match role { @@ -53,54 +44,6 @@ impl From for models::ProfileRole { } } -#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse, Debug)] -pub struct CreateProfile { - /// The name of the profile to create. - #[schema(example = "John Doe")] - pub name: String, - /// The email of the profile to create. - #[schema(example = "example@example.com", value_type = String)] - pub email: String, - /// The role of the profile to create. - pub role: ProfileRole, -} - -#[derive(Serialize, Deserialize, Clone, Debug, IntoParams)] -pub struct GetProfile { - /// The email of the profile to fetch. - #[param(example = "example@example.com", value_type = String)] - pub email: String, -} - -#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse)] -pub struct Profile { - /// The id of the profile. - #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] - id: models::ProfileId, - /// The name of the profile. - #[schema(example = "John Doe")] - name: String, - /// The email of the profile. - #[schema(example = "example@example.com", value_type = String)] - email: EmailAddress, - /// The role of the profile. - pub role: ProfileRole, -} - -#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse)] -pub struct CreateAccessToken { - /// The id of the profile to create token for. - #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] - profile_id: models::ProfileId, -} - -#[derive(Serialize, Deserialize, ToSchema, Clone, ToResponse)] -pub struct AccessToken { - /// The token for later use. - #[schema(example = "_q9zUYP-tQg8F7kQi2Rfl5c6sSy7xcc2yWh2H-nI-iI", value_type = String)] - token: String, -} - /// Create a new profile. /// /// Returns the created profile object. diff --git a/auction-server/src/api/ws.rs b/auction-server/src/api/ws.rs index 66b17935..d8885226 100644 --- a/auction-server/src/api/ws.rs +++ b/auction-server/src/api/ws.rs @@ -2,13 +2,7 @@ use { super::Auth, crate::{ auction::{ - api::{ - process_bid, - BidCreate, - BidResult, - BidStatusWithId, - SvmChainUpdate, - }, + api::process_bid, entities::BidId, }, config::ChainId, @@ -34,11 +28,19 @@ use { }, response::IntoResponse, }, - express_relay_api_types::opportunity::{ - Opportunity, - OpportunityBidEvm, - OpportunityDelete, - OpportunityId, + express_relay_api_types::{ + bid::{ + BidCreate, + BidResult, + BidStatusWithId, + }, + opportunity::{ + Opportunity, + OpportunityBidEvm, + OpportunityDelete, + OpportunityId, + }, + SvmChainUpdate, }, futures::{ stream::{ diff --git a/auction-server/src/auction/api.rs b/auction-server/src/auction/api.rs index 8e68935f..e60a9f03 100644 --- a/auction-server/src/auction/api.rs +++ b/auction-server/src/auction/api.rs @@ -4,7 +4,6 @@ use { self, BidChainData, }, - repository::MicroLamports, service::{ get_bid::GetBidInput, get_bids::GetBidsInput, @@ -19,14 +18,11 @@ use { api::{ require_login_middleware, Auth, - ErrorBodyResponse, RestError, }, kernel::entities::{ ChainId, Evm, - PermissionKey, - PermissionKeySvm, Svm, }, login_required, @@ -48,289 +44,33 @@ use { Json, Router, }, - ethers::types::{ - Address, - Bytes, - H256, - U256, - }, - serde::{ - Deserialize, - Serialize, - }, - serde_with::{ - serde_as, - DisplayFromStr, - }, - solana_sdk::{ - hash::Hash, - signature::Signature, - transaction::VersionedTransaction, + express_relay_api_types::{ + bid::{ + Bid, + BidCoreFields, + BidCreate, + BidCreateEvm, + BidCreateSvm, + BidEvm, + BidId, + BidResult, + BidStatus, + BidStatusEvm, + BidStatusSvm, + BidSvm, + Bids, + GetBidStatusParams, + GetBidsByTimeQueryParams, + }, + ErrorBodyResponse, }, sqlx::types::time::OffsetDateTime, std::{ fmt::Debug, sync::Arc, }, - utoipa::{ - IntoParams, - ToResponse, - ToSchema, - }, - uuid::Uuid, }; -// TODO move it to kernel? - -#[serde_as] -#[derive(Serialize, Clone, ToSchema, ToResponse)] -pub struct SvmChainUpdate { - #[schema(example = "solana", value_type = String)] - pub chain_id: ChainId, - #[serde_as(as = "DisplayFromStr")] - #[schema(example = "SLxp9LxX1eE9Z5v99Y92DaYEwyukFgMUF6zRerCF12j", value_type = String)] - pub blockhash: Hash, - /// The prioritization fee that the server suggests to use for the next transaction - #[schema(example = "1000", value_type = u64)] - pub latest_prioritization_fee: MicroLamports, -} - -pub type BidId = Uuid; - -#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum BidStatusEvm { - /// The temporary state which means the auction for this bid is pending. - #[schema(title = "Pending")] - Pending, - /// The bid is submitted to the chain, which is placed at the given index of the transaction with the given hash. - /// This state is temporary and will be updated to either lost or won after conclusion of the auction. - #[schema(title = "Submitted")] - Submitted { - #[schema(example = "0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3", value_type = String)] - result: H256, - #[schema(example = 1, value_type = u32)] - index: u32, - }, - /// The bid lost the auction, which is concluded with the transaction with the given hash and index. - /// The result will be None if the auction was concluded off-chain and no auction was submitted to the chain. - /// The index will be None if the bid was not submitted to the chain and lost the auction by off-chain calculation. - /// There are cases where the result is not None and the index is None. - /// It is because other bids were selected for submission to the chain, but not this one. - #[schema(title = "Lost")] - Lost { - #[schema(example = "0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3", value_type = Option)] - result: Option, - #[schema(example = 1, value_type = Option)] - index: Option, - }, - /// The bid won the auction, which is concluded with the transaction with the given hash and index. - #[schema(title = "Won")] - Won { - #[schema(example = "0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3", value_type = String)] - result: H256, - #[schema(example = 1, value_type = u32)] - index: u32, - }, -} - -#[serde_as] -#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum BidStatusSvm { - /// The temporary state which means the auction for this bid is pending. - #[schema(title = "Pending")] - Pending, - /// The bid lost the auction. - /// The result will be None if the auction does not result in a transaction being submitted to the chain. - /// The result will be Some if this bid lost to another bid and the winning bid was submitted to the chain. - /// The signature of the transaction for the submitted bid is the result value. - #[schema(title = "Lost")] - Lost { - #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = Option)] - #[serde(with = "express_relay_api_types::serde::nullable_signature_svm")] - result: Option, - }, - /// The bid won the auction and was submitted to the chain, with the transaction with the signature. - /// This state is temporary and will be updated to either Won or Failed after the transaction is included in a block, or Expired if the transaction expires before it is included. - #[schema(title = "Submitted")] - Submitted { - #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] - #[serde_as(as = "DisplayFromStr")] - result: Signature, - }, - /// The bid won the auction and was included in a block successfully. - #[schema(title = "Won")] - Won { - #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] - #[serde_as(as = "DisplayFromStr")] - result: Signature, - }, - /// The bid was submitted on-chain, was included in a block, but resulted in a failed transaction. - #[schema(title = "Failed")] - Failed { - #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] - #[serde_as(as = "DisplayFromStr")] - result: Signature, - }, - /// The bid was submitted on-chain but expired before it was included in a block. - #[schema(title = "Expired")] - Expired { - #[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)] - #[serde_as(as = "DisplayFromStr")] - result: Signature, - }, -} - -#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] -#[serde(untagged)] -pub enum BidStatus { - Evm(BidStatusEvm), - Svm(BidStatusSvm), -} - -#[derive(Serialize, Deserialize, ToResponse, ToSchema, Clone)] -pub struct BidResult { - /// The status of the request. If the bid was placed successfully, the status will be "OK". - #[schema(example = "OK")] - pub status: String, - /// The unique id created to identify the bid. This id can be used to query the status of the bid. - #[schema(example = "beedbeed-58cc-4372-a567-0e02b2c3d479", value_type=String)] - pub id: BidId, -} - -#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] -pub struct BidCoreFields { - /// The unique id for bid. - #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] - pub id: BidId, - /// The chain id for bid. - #[schema(example = "op_sepolia", value_type = String)] - pub chain_id: ChainId, - /// The time server received the bid formatted in rfc3339. - #[schema(example = "2024-05-23T21:26:57.329954Z", value_type = String)] - #[serde(with = "time::serde::rfc3339")] - pub initiation_time: OffsetDateTime, - /// The profile id for the bid owner. - #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] - pub profile_id: Option, -} - -#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] -pub struct BidSvm { - #[serde(flatten)] - #[schema(inline)] - pub core_fields: BidCoreFields, - /// The latest status for bid. - pub status: BidStatusSvm, - /// The transaction of the bid. - #[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)] - #[serde(with = "express_relay_api_types::serde::transaction_svm")] - pub transaction: VersionedTransaction, - /// Amount of bid in lamports. - #[schema(example = "1000", value_type = u64)] - pub bid_amount: entities::BidAmountSvm, - /// The permission key for bid in base64 format. - /// This is the concatenation of the permission account and the router account. - #[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)] - pub permission_key: PermissionKeySvm, -} - -#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] -pub struct BidEvm { - #[serde(flatten)] - #[schema(inline)] - pub core_fields: BidCoreFields, - /// The latest status for bid. - pub status: BidStatusEvm, - /// The contract address to call. - #[schema(example = "0xcA11bde05977b3631167028862bE2a173976CA11", value_type = String)] - pub target_contract: Address, - /// Calldata for the contract call. - #[schema(example = "0xdeadbeef", value_type = String)] - pub target_calldata: Bytes, - /// The gas limit for the contract call. - #[schema(example = "2000000", value_type = String)] - #[serde(with = "express_relay_api_types::serde::u256")] - pub gas_limit: U256, - /// Amount of bid in wei. - #[schema(example = "10", value_type = String)] - #[serde(with = "express_relay_api_types::serde::u256")] - pub bid_amount: entities::BidAmountEvm, - /// The permission key for bid. - #[schema(example = "0xdeadbeef", value_type = String)] - pub permission_key: PermissionKey, -} - -#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] -pub enum Bid { - Evm(BidEvm), - Svm(BidSvm), -} - -impl Bid { - pub fn get_initiation_time(&self) -> OffsetDateTime { - match self { - Bid::Evm(bid) => bid.core_fields.initiation_time, - Bid::Svm(bid) => bid.core_fields.initiation_time, - } - } - - pub fn get_status(&self) -> BidStatus { - match self { - Bid::Evm(bid) => BidStatus::Evm(bid.status.clone()), - Bid::Svm(bid) => BidStatus::Svm(bid.status.clone()), - } - } -} - -#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] -pub struct BidCreateEvm { - /// The permission key to bid on. - #[schema(example = "0xdeadbeef", value_type = String)] - pub permission_key: Bytes, - /// The chain id to bid on. - #[schema(example = "op_sepolia", value_type = String)] - pub chain_id: ChainId, - /// The contract address to call. - #[schema(example = "0xcA11bde05977b3631167028862bE2a173976CA11", value_type = String)] - pub target_contract: Address, - /// Calldata for the contract call. - #[schema(example = "0xdeadbeef", value_type = String)] - pub target_calldata: Bytes, - /// Amount of bid in wei. - #[schema(example = "10", value_type = String)] - #[serde(with = "express_relay_api_types::serde::u256")] - pub amount: entities::BidAmountEvm, -} - -#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] -pub struct BidCreateSvm { - /// The chain id to bid on. - #[schema(example = "solana", value_type = String)] - pub chain_id: ChainId, - /// The transaction for bid. - #[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)] - #[serde(with = "express_relay_api_types::serde::transaction_svm")] - pub transaction: VersionedTransaction, -} - -#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] -#[serde(untagged)] // Remove tags to avoid key-value wrapping -pub enum BidCreate { - Evm(BidCreateEvm), - Svm(BidCreateSvm), -} - -#[derive(Serialize, Clone, ToSchema, ToResponse)] -pub struct BidStatusWithId { - #[schema(value_type = String)] - pub id: BidId, - pub bid_status: BidStatus, -} - /// Bid on a specific permission key for a specific chain. /// /// Your bid will be verified by the server. Depending on the outcome of the auction, a transaction @@ -363,15 +103,6 @@ pub async fn process_bid( } } -#[derive(Serialize, Deserialize, IntoParams, Clone)] -pub struct GetBidStatusParams { - #[param(example="op_sepolia", value_type = String)] - pub chain_id: ChainId, - - #[param(example="obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] - pub bid_id: BidId, -} - /// Query the status of a specific bid. #[utoipa::path(get, path = "/v1/{chain_id}/bids/{bid_id}", responses( @@ -421,18 +152,6 @@ pub async fn get_bid_status_deprecated( Err(RestError::BidNotFound) } -#[derive(Serialize, Deserialize, ToResponse, ToSchema, Clone)] -pub struct Bids { - pub items: Vec, -} - -#[derive(Serialize, Deserialize, IntoParams)] -pub struct GetBidsByTimeQueryParams { - #[param(example="2024-05-23T21:26:57.329954Z", value_type = Option)] - #[serde(default, with = "express_relay_api_types::serde::nullable_datetime")] - pub from_time: Option, -} - /// Returns at most 20 bids which were submitted after a specific time and chain. /// If no time is provided, the server will return the first bids. #[utoipa::path(get, path = "/v1/{chain_id}/bids", @@ -573,22 +292,19 @@ impl From for BidStatusSvm { } } -// TODO switch to generic structure format -impl BidCoreFields { - pub fn from_bid(bid: &entities::Bid) -> Self { - BidCoreFields { - id: bid.id, - chain_id: bid.chain_id.clone(), - initiation_time: bid.initiation_time, - profile_id: bid.profile_id, - } +fn get_core_fields(bid: &entities::Bid) -> BidCoreFields { + BidCoreFields { + id: bid.id, + chain_id: bid.chain_id.clone(), + initiation_time: bid.initiation_time, + profile_id: bid.profile_id, } } impl From> for Bid { fn from(bid: entities::Bid) -> Self { Bid::Evm(BidEvm { - core_fields: BidCoreFields::from_bid(&bid), + core_fields: get_core_fields(&bid), status: bid.status.into(), permission_key: bid.chain_data.get_permission_key(), target_contract: bid.chain_data.target_contract, @@ -602,8 +318,10 @@ impl From> for Bid { impl From> for Bid { fn from(bid: entities::Bid) -> Self { Bid::Svm(BidSvm { - core_fields: BidCoreFields::from_bid(&bid), - permission_key: bid.chain_data.get_permission_key(), + core_fields: get_core_fields(&bid), + permission_key: express_relay_api_types::PermissionKeySvm( + bid.chain_data.get_permission_key().0, + ), status: bid.status.into(), transaction: bid.chain_data.transaction, bid_amount: bid.amount, @@ -611,15 +329,6 @@ impl From> for Bid { } } -impl BidCreate { - fn get_chain_id(&self) -> ChainId { - match self { - BidCreate::Evm(bid_create_evm) => bid_create_evm.chain_id.clone(), - BidCreate::Svm(bid_create_svm) => bid_create_svm.chain_id.clone(), - } - } -} - impl From for BidStatus { fn from(bid: entities::BidStatusEvm) -> Self { BidStatus::Evm(bid.into()) diff --git a/auction-server/src/auction/entities/bid.rs b/auction-server/src/auction/entities/bid.rs index 04a09a54..a6cd2c13 100644 --- a/auction-server/src/auction/entities/bid.rs +++ b/auction-server/src/auction/entities/bid.rs @@ -1,10 +1,7 @@ use { super::AuctionId, crate::{ - auction::{ - api, - service::ChainTrait, - }, + auction::service::ChainTrait, kernel::{ contracts::MulticallData, entities::{ @@ -26,6 +23,7 @@ use { H256, U256, }, + express_relay_api_types::bid as api, solana_sdk::{ pubkey::Pubkey, signature::Signature, diff --git a/auction-server/src/auction/service/update_bid_status.rs b/auction-server/src/auction/service/update_bid_status.rs index 78c5fb5b..85d1a910 100644 --- a/auction-server/src/auction/service/update_bid_status.rs +++ b/auction-server/src/auction/service/update_bid_status.rs @@ -8,11 +8,9 @@ use { ws::UpdateEvent, RestError, }, - auction::{ - api::BidStatusWithId, - entities, - }, + auction::entities, }, + express_relay_api_types::bid::BidStatusWithId, }; pub struct UpdateBidStatusInput { diff --git a/auction-server/src/auction/service/workers.rs b/auction-server/src/auction/service/workers.rs index ba0e55e9..03a68136 100644 --- a/auction-server/src/auction/service/workers.rs +++ b/auction-server/src/auction/service/workers.rs @@ -6,10 +6,7 @@ use { }, crate::{ api::ws::UpdateEvent, - auction::{ - api::SvmChainUpdate, - service::conclude_auction::ConcludeAuctionInput, - }, + auction::service::conclude_auction::ConcludeAuctionInput, kernel::entities::{ Evm, Svm, @@ -25,6 +22,7 @@ use { }, axum_prometheus::metrics, ethers::providers::Middleware, + express_relay_api_types::SvmChainUpdate, solana_client::rpc_config::{ RpcTransactionLogsConfig, RpcTransactionLogsFilter, diff --git a/auction-server/src/kernel/entities.rs b/auction-server/src/kernel/entities.rs index f5917573..c9b37478 100644 --- a/auction-server/src/kernel/entities.rs +++ b/auction-server/src/kernel/entities.rs @@ -3,19 +3,6 @@ use { base64::Engine, bincode::serialized_size, ethers::types::Bytes, - serde::{ - Deserialize, - Serialize, - }, - serde_with::{ - base64::{ - Base64, - Standard, - }, - formats::Padded, - DeserializeAs, - SerializeAs, - }, solana_sdk::{ packet::PACKET_DATA_SIZE, transaction::VersionedTransaction, @@ -28,16 +15,6 @@ pub type PermissionKey = Bytes; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PermissionKeySvm(pub [u8; 64]); - -impl Serialize for PermissionKeySvm { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - Base64::::serialize_as(&self.0, serializer) - } -} - impl Display for PermissionKeySvm { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -48,16 +25,6 @@ impl Display for PermissionKeySvm { } } -impl<'de> Deserialize<'de> for PermissionKeySvm { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let bytes = Base64::::deserialize_as(deserializer)?; - Ok(PermissionKeySvm(bytes)) - } -} - #[derive(Debug, PartialEq, Clone)] pub enum ChainType { Evm, diff --git a/auction-server/src/opportunity/api.rs b/auction-server/src/opportunity/api.rs index f1e991d3..5fb333da 100644 --- a/auction-server/src/opportunity/api.rs +++ b/auction-server/src/opportunity/api.rs @@ -13,7 +13,6 @@ use { api::{ require_login_middleware, Auth, - ErrorBodyResponse, RestError, }, login_required, @@ -36,18 +35,21 @@ use { Json, Router, }, - express_relay_api_types::opportunity::{ - GetOpportunitiesQueryParams, - Opportunity, - OpportunityBidEvm, - OpportunityBidResult, - OpportunityCreate, - OpportunityDelete, - OpportunityDeleteSvm, - OpportunityId, - ProgramSvm, - Quote, - QuoteCreate, + express_relay_api_types::{ + opportunity::{ + GetOpportunitiesQueryParams, + Opportunity, + OpportunityBidEvm, + OpportunityBidResult, + OpportunityCreate, + OpportunityDelete, + OpportunityDeleteSvm, + OpportunityId, + ProgramSvm, + Quote, + QuoteCreate, + }, + ErrorBodyResponse, }, std::sync::Arc, time::OffsetDateTime, diff --git a/auction-server/src/state.rs b/auction-server/src/state.rs index 822251c2..1469e1d1 100644 --- a/auction-server/src/state.rs +++ b/auction-server/src/state.rs @@ -1,7 +1,6 @@ use { crate::{ api::{ - profile as ApiProfile, ws::WsState, RestError, }, @@ -186,7 +185,7 @@ impl StoreNew { impl Store { pub async fn create_profile( &self, - create_profile: ApiProfile::CreateProfile, + create_profile: express_relay_api_types::profile::CreateProfile, ) -> Result { let id = Uuid::new_v4(); let role: models::ProfileRole = create_profile.role.clone().into();