Skip to content

Commit

Permalink
Add submit quote api (#368)
Browse files Browse the repository at this point in the history
  • Loading branch information
danimhr authored Jan 29, 2025
1 parent e421c2f commit 200ccda
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 26 deletions.
24 changes: 15 additions & 9 deletions auction-server/api-types/src/bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ pub enum Route {
GetBidsByTime,
#[strum(serialize = ":bid_id")]
GetBidStatus,
#[strum(serialize = "submit")]
PostSubmitQuote,
}

#[derive(Clone)]
Expand All @@ -367,19 +369,18 @@ pub enum DeprecatedRoute {

impl Routable for Route {
fn properties(&self) -> crate::RouteProperties {
let full_path = format!(
"{}{}{}",
crate::Route::V1.as_ref(),
crate::Route::Bid.as_ref(),
self.as_ref()
)
.trim_end_matches('/')
.to_string();
let prefix = match self {
Route::PostSubmitQuote => crate::Route::Quote.as_ref(),
_ => crate::Route::Bid.as_ref(),
};
let full_path = format!("{}{}{}", crate::Route::V1.as_ref(), prefix, self.as_ref())
.trim_end_matches('/')
.to_string();

let full_path_with_chain = format!(
"{}{}{}",
crate::Route::V1Chain.as_ref(),
crate::Route::Bid.as_ref(),
prefix,
self.as_ref()
)
.trim_end_matches('/')
Expand All @@ -401,6 +402,11 @@ impl Routable for Route {
access_level: AccessLevel::Public,
full_path: full_path_with_chain,
},
Route::PostSubmitQuote => crate::RouteProperties {
method: http::Method::POST,
access_level: AccessLevel::Public,
full_path: full_path_with_chain,
},
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions auction-server/api-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use {
pub mod bid;
pub mod opportunity;
pub mod profile;
pub mod quote;
pub mod serde;
pub mod ws;

Expand Down Expand Up @@ -83,6 +84,8 @@ pub enum Route {
Bid,
#[strum(serialize = "opportunities")]
Opportunity,
#[strum(serialize = "quotes")]
Quote,
#[strum(serialize = "profiles")]
Profile,
#[strum(serialize = "")]
Expand Down
40 changes: 40 additions & 0 deletions auction-server/api-types/src/quote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use {
crate::bid::BidId,
serde::{
Deserialize,
Serialize,
},
serde_with::{
serde_as,
DisplayFromStr,
},
solana_sdk::{
signature::Signature,
transaction::VersionedTransaction,
},
utoipa::ToSchema,
};

/// Parameters needed to submit a quote from server.
#[serde_as]
#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)]
pub struct SubmitQuote {
/// The reference id for the quote that should be submitted.
#[schema(example = "beedbeed-58cc-4372-a567-0e02b2c3d479", value_type=String)]
pub reference_id: BidId,
/// The signature of the user for the quote.
#[schema(example = "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg", value_type = String)]
#[serde_as(as = "DisplayFromStr")]
pub user_signature: Signature,
}

/// Response to a quote submission.
#[serde_as]
#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)]
pub struct SubmitQuoteResponse {
/// The fully signed versioned transaction that was submitted.
/// The transaction is encoded in base64.
#[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)]
#[serde(with = "crate::serde::transaction_svm")]
pub transaction: VersionedTransaction,
}
5 changes: 5 additions & 0 deletions auction-server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ pub async fn start_api(run_options: RunOptions, store: Arc<StoreNew>) -> Result<
opportunity::post_quote,
opportunity::delete_opportunities,
bid::post_submit_quote,
profile::delete_profile_access_token,
),
components(
Expand Down Expand Up @@ -368,6 +370,9 @@ pub async fn start_api(run_options: RunOptions, store: Arc<StoreNew>) -> Result<
api_types::opportunity::FeeToken,
api_types::opportunity::ReferralFeeInfo,
api_types::quote::SubmitQuote,
api_types::quote::SubmitQuoteResponse,
ErrorBodyResponse,
api_types::ws::ClientRequest,
api_types::ws::ClientMessage,
Expand Down
39 changes: 39 additions & 0 deletions auction-server/src/auction/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use {
get_bid::GetBidInput,
get_bids::GetBidsInput,
handle_bid::HandleBidInput,
submit_quote::SubmitQuoteInput,
verification::Verification,
ChainTrait,
Service,
Expand Down Expand Up @@ -57,6 +58,10 @@ use {
GetBidsByTimeQueryParams,
Route,
},
quote::{
SubmitQuote,
SubmitQuoteResponse,
},
ErrorBodyResponse,
},
sqlx::types::time::OffsetDateTime,
Expand Down Expand Up @@ -227,6 +232,39 @@ pub async fn get_bids_by_time_deprecated(
}
}

/// Signs and submits the transaction for the specified quote.
///
/// Server will verify the quote and checks if the quote is still valid.
/// If the quote is valid, the server will submit the transaction to the blockchain.
#[utoipa::path(post, path = "/v1/{chain_id}/quotes/submit", request_body = SubmitQuote,
responses(
(status = 200, body = SubmitQuoteResponse),
(status = 400, response = ErrorBodyResponse),
),
tag = "quote",
)]
pub async fn post_submit_quote(
State(store): State<Arc<StoreNew>>,
Path(chain_id): Path<ChainId>,
Json(submit_quote): Json<SubmitQuote>,
) -> Result<Json<SubmitQuoteResponse>, RestError> {
let service = store.get_auction_service(&chain_id)?;
match service {
ServiceEnum::Evm(_) => Err(RestError::BadParameters(
"EVM chain_id is not supported for submit_quote".to_string(),
)),
ServiceEnum::Svm(service) => {
let transaction = service
.submit_quote(SubmitQuoteInput {
bid_id: submit_quote.reference_id,
user_signature: submit_quote.user_signature,
})
.await?;
Ok(Json(SubmitQuoteResponse { transaction }))
}
}
}

pub fn get_routes(store: Arc<StoreNew>) -> Router<Arc<StoreNew>> {
#[allow(deprecated)]
WrappedRouter::new(store)
Expand All @@ -241,6 +279,7 @@ pub fn get_routes(store: Arc<StoreNew>) -> Router<Arc<StoreNew>> {
express_relay_api_types::bid::DeprecatedRoute::DeprecatedGetBidStatus,
get_bid_status_deprecated,
)
.route(Route::PostSubmitQuote, post_submit_quote)
.router
}

Expand Down
24 changes: 24 additions & 0 deletions auction-server/src/auction/repository/get_in_memory_bid_by_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use {
super::Repository,
crate::auction::{
entities,
service::ChainTrait,
},
};

impl<T: ChainTrait> Repository<T> {
pub async fn get_in_memory_bid_by_id(
&self,
bid_id: entities::BidId,
) -> Option<entities::Bid<T>> {
let in_memory_bids = self.get_in_memory_bids().await;
for bids in in_memory_bids.values() {
for bid in bids {
if bid.id == bid_id {
return Some(bid.clone());
}
}
}
None
}
}
1 change: 1 addition & 0 deletions auction-server/src/auction/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod add_recent_priotization_fee;
mod conclude_auction;
mod get_bid;
mod get_bids;
mod get_in_memory_bid_by_id;
mod get_in_memory_bids;
mod get_in_memory_bids_by_permission_key;
mod get_in_memory_submitted_auctions;
Expand Down
2 changes: 1 addition & 1 deletion auction-server/src/auction/service/auction_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ impl Service<Svm> {
}

#[tracing::instrument(skip_all, fields(bid_id))]
async fn send_transaction(
pub async fn send_transaction(
&self,
bid: &entities::Bid<Svm>,
) -> solana_client::client_error::Result<Signature> {
Expand Down
1 change: 1 addition & 0 deletions auction-server/src/auction/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub mod handle_auction;
pub mod handle_auctions;
pub mod handle_bid;
pub mod simulator;
pub mod submit_quote;
pub mod update_bid_status;
pub mod update_recent_prioritization_fee;
pub mod update_submitted_auction;
Expand Down
100 changes: 100 additions & 0 deletions auction-server/src/auction/service/submit_quote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use {
super::{
verification::SwapAccounts,
Service,
},
crate::{
api::RestError,
auction::entities::{
self,
BidStatus,
},
kernel::entities::Svm,
},
solana_sdk::{
signature::Signature,
transaction::VersionedTransaction,
},
std::time::Duration,
time::OffsetDateTime,
};

pub struct SubmitQuoteInput {
pub bid_id: entities::BidId,
pub user_signature: Signature,
}

const DEADLINE_BUFFER: Duration = Duration::from_secs(2);

impl Service<Svm> {
pub async fn submit_quote(
&self,
input: SubmitQuoteInput,
) -> Result<VersionedTransaction, RestError> {
let bid: Option<entities::Bid<Svm>> = self.repo.get_in_memory_bid_by_id(input.bid_id).await;
match bid {
Some(mut bid) => {
let swap_instruction = self
.extract_express_relay_instruction(
bid.chain_data.transaction.clone(),
entities::BidPaymentInstructionType::Swap,
)
.map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?;
let SwapAccounts { user_wallet, .. } = self
.extract_swap_accounts(&bid.chain_data.transaction, &swap_instruction)
.await
.map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?;
let swap_args = Self::extract_swap_data(&swap_instruction)
.map_err(|_| RestError::BadParameters("Invalid quote".to_string()))?;

if swap_args.deadline
< (OffsetDateTime::now_utc() - DEADLINE_BUFFER).unix_timestamp()
{
return Err(RestError::BadParameters("Quote is expired".to_string()));
}

if !input.user_signature.verify(
&user_wallet.to_bytes(),
&bid.chain_data.transaction.message.serialize(),
) {
return Err(RestError::BadParameters("Invalid signature".to_string()));
}

let user_signature_pos = bid
.chain_data
.transaction
.message
.static_account_keys()
.iter()
.position(|p| p.eq(&user_wallet))
.expect("User wallet not found in transaction");
bid.chain_data.transaction.signatures[user_signature_pos] = input.user_signature;

// TODO add relayer signature after program update
// self.add_relayer_signature(&mut bid);

// TODO change it to a better state (Wait for user signature)
if bid.status.is_submitted() {
if bid.chain_data.bid_payment_instruction_type
== entities::BidPaymentInstructionType::Swap
{
self.send_transaction(&bid).await.map_err(|e| {
tracing::error!(error = ?e, "Error sending quote transaction to network");
RestError::TemporarilyUnavailable
})?;
Ok(bid.chain_data.transaction)
} else {
Err(RestError::BadParameters("Invalid quote".to_string()))
}
} else {
Err(RestError::BadParameters(
"Quote is not valid anymore".to_string(),
))
}
}
None => Err(RestError::BadParameters(
"Quote is not valid anymore".to_string(),
)),
}
}
}
Loading

0 comments on commit 200ccda

Please sign in to comment.