use core::marker::PhantomData;
use alloy_primitives::{Address, U256, keccak256};
use cowprotocol_signing::SignerSync;
use crate::app_data::{
APP_DATA_SIZE_LIMIT, AppDataError, AppDataHash, EMPTY_APP_DATA_HASH, EMPTY_APP_DATA_JSON,
};
use crate::chain::Chain;
use crate::error::{Error, Result};
use crate::order::{BuyTokenDestination, OrderKind, OrderUid, SellTokenSource};
use crate::quote_amounts::{DEFAULT_SLIPPAGE_BPS, OrderCosts, ProtocolFeeBps};
use crate::signing_scheme::{EcdsaSigningScheme, SigningScheme};
use crate::transport::HttpTransport;
use super::api::OrderBookApi;
use super::builder_state::{Missing, Set};
use super::orders::OrderCreation;
use super::quote::{OrderQuoteResponse, QuoteRequest};
use super::types::{AppDataDocument, PriceQuality, QuoteAppData};
impl<T: HttpTransport + Clone> OrderBookApi<T> {
pub fn quote_builder(&self) -> QuoteRequestBuilder<T> {
QuoteRequestBuilder {
api: self.clone(),
parts: QuoteParts::new(OrderCosts {
slippage_bps: DEFAULT_SLIPPAGE_BPS,
..OrderCosts::default()
}),
_state: PhantomData,
}
}
}
#[derive(Clone, Debug)]
struct QuoteParts {
sell_token: Option<Address>,
buy_token: Option<Address>,
from: Option<Address>,
receiver: Option<Address>,
kind: Option<OrderKind>,
sell_amount_before_fee: Option<U256>,
sell_amount_after_fee: Option<U256>,
buy_amount_after_fee: Option<U256>,
valid_to: Option<u32>,
valid_for: Option<u32>,
app_data: Option<QuoteAppData>,
partially_fillable: Option<bool>,
sell_token_balance: Option<SellTokenSource>,
buy_token_balance: Option<BuyTokenDestination>,
signing_scheme: Option<SigningScheme>,
verification_gas_limit: Option<u64>,
onchain_order: Option<bool>,
price_quality: Option<PriceQuality>,
costs: OrderCosts,
}
impl QuoteParts {
const fn new(costs: OrderCosts) -> Self {
Self {
sell_token: None,
buy_token: None,
from: None,
receiver: None,
kind: None,
sell_amount_before_fee: None,
sell_amount_after_fee: None,
buy_amount_after_fee: None,
valid_to: None,
valid_for: None,
app_data: None,
partially_fillable: None,
sell_token_balance: None,
buy_token_balance: None,
signing_scheme: None,
verification_gas_limit: None,
onchain_order: None,
price_quality: None,
costs,
}
}
fn into_request(self) -> QuoteRequest {
let mut request = match self.kind.expect("amount typestate sets kind") {
OrderKind::Sell => {
if let Some(amount) = self.sell_amount_before_fee {
QuoteRequest::sell_before_fee(
self.sell_token.expect("sell token typestate is set"),
self.buy_token.expect("buy token typestate is set"),
self.from.expect("from typestate is set"),
amount,
)
} else {
QuoteRequest::sell_after_fee(
self.sell_token.expect("sell token typestate is set"),
self.buy_token.expect("buy token typestate is set"),
self.from.expect("from typestate is set"),
self.sell_amount_after_fee
.expect("amount typestate set a sell amount"),
)
}
}
OrderKind::Buy => QuoteRequest::buy_after_fee(
self.sell_token.expect("sell token typestate is set"),
self.buy_token.expect("buy token typestate is set"),
self.from.expect("from typestate is set"),
self.buy_amount_after_fee
.expect("amount typestate set the buy amount"),
),
};
request.receiver = self.receiver;
request.valid_to = self.valid_to;
request.valid_for = self.valid_for;
request.app_data = self.app_data;
request.partially_fillable = self.partially_fillable;
request.sell_token_balance = self.sell_token_balance;
request.buy_token_balance = self.buy_token_balance;
request.signing_scheme = self.signing_scheme;
request.verification_gas_limit = self.verification_gas_limit;
request.onchain_order = self.onchain_order;
request.price_quality = self.price_quality;
request
}
}
#[derive(Clone, Debug)]
pub struct QuoteRequestBuilder<
T,
SellToken = Missing,
BuyToken = Missing,
From = Missing,
Amount = Missing,
> {
api: OrderBookApi<T>,
parts: QuoteParts,
_state: PhantomData<(SellToken, BuyToken, From, Amount)>,
}
impl<T, SellToken, BuyToken, From, Amount>
QuoteRequestBuilder<T, SellToken, BuyToken, From, Amount>
{
fn cast<S2, B2, F2, A2>(self) -> QuoteRequestBuilder<T, S2, B2, F2, A2> {
QuoteRequestBuilder {
api: self.api,
parts: self.parts,
_state: PhantomData,
}
}
pub fn with_sell_token(
self,
sell_token: Address,
) -> QuoteRequestBuilder<T, Set, BuyToken, From, Amount> {
let mut next = self.cast::<Set, BuyToken, From, Amount>();
next.parts.sell_token = Some(sell_token);
next
}
pub fn with_buy_token(
self,
buy_token: Address,
) -> QuoteRequestBuilder<T, SellToken, Set, From, Amount> {
let mut next = self.cast::<SellToken, Set, From, Amount>();
next.parts.buy_token = Some(buy_token);
next
}
pub fn with_from(
self,
from: Address,
) -> QuoteRequestBuilder<T, SellToken, BuyToken, Set, Amount> {
let mut next = self.cast::<SellToken, BuyToken, Set, Amount>();
next.parts.from = Some(from);
next
}
pub fn with_sell_amount(
self,
sell_amount: U256,
) -> QuoteRequestBuilder<T, SellToken, BuyToken, From, Set> {
self.with_sell_amount_before_fee(sell_amount)
}
pub fn with_sell_amount_before_fee(
self,
sell_amount: U256,
) -> QuoteRequestBuilder<T, SellToken, BuyToken, From, Set> {
let mut next = self.cast::<SellToken, BuyToken, From, Set>();
next.parts.kind = Some(OrderKind::Sell);
next.parts.sell_amount_before_fee = Some(sell_amount);
next.parts.sell_amount_after_fee = None;
next.parts.buy_amount_after_fee = None;
next
}
pub fn with_sell_amount_after_fee(
self,
sell_amount: U256,
) -> QuoteRequestBuilder<T, SellToken, BuyToken, From, Set> {
let mut next = self.cast::<SellToken, BuyToken, From, Set>();
next.parts.kind = Some(OrderKind::Sell);
next.parts.sell_amount_before_fee = None;
next.parts.sell_amount_after_fee = Some(sell_amount);
next.parts.buy_amount_after_fee = None;
next
}
pub fn with_buy_amount_after_fee(
self,
buy_amount: U256,
) -> QuoteRequestBuilder<T, SellToken, BuyToken, From, Set> {
let mut next = self.cast::<SellToken, BuyToken, From, Set>();
next.parts.kind = Some(OrderKind::Buy);
next.parts.sell_amount_before_fee = None;
next.parts.sell_amount_after_fee = None;
next.parts.buy_amount_after_fee = Some(buy_amount);
next
}
pub const fn with_receiver(mut self, receiver: Address) -> Self {
self.parts.receiver = Some(receiver);
self
}
pub const fn with_valid_to(mut self, valid_to: u32) -> Self {
self.parts.valid_to = Some(valid_to);
self.parts.valid_for = None;
self
}
pub const fn with_valid_for(mut self, valid_for: u32) -> Self {
self.parts.valid_for = Some(valid_for);
self.parts.valid_to = None;
self
}
pub fn with_app_data(mut self, app_data: impl Into<QuoteAppData>) -> Self {
self.parts.app_data = Some(app_data.into());
self
}
pub const fn with_partially_fillable(mut self, partially_fillable: bool) -> Self {
self.parts.partially_fillable = Some(partially_fillable);
self
}
pub const fn with_sell_token_balance(mut self, balance: SellTokenSource) -> Self {
self.parts.sell_token_balance = Some(balance);
self
}
pub const fn with_buy_token_balance(mut self, balance: BuyTokenDestination) -> Self {
self.parts.buy_token_balance = Some(balance);
self
}
pub const fn with_signing_scheme(mut self, signing_scheme: SigningScheme) -> Self {
self.parts.signing_scheme = Some(signing_scheme);
self
}
pub const fn with_verification_gas_limit(mut self, gas_limit: u64) -> Self {
self.parts.verification_gas_limit = Some(gas_limit);
self
}
pub const fn with_onchain_order(mut self, onchain_order: bool) -> Self {
self.parts.onchain_order = Some(onchain_order);
self
}
pub const fn with_price_quality(mut self, price_quality: PriceQuality) -> Self {
self.parts.price_quality = Some(price_quality);
self
}
pub const fn with_slippage_bps(mut self, bps: u32) -> Self {
self.parts.costs.slippage_bps = bps;
self
}
pub const fn with_partner_fee_bps(mut self, bps: u32) -> Self {
self.parts.costs.partner_fee_bps = bps;
self
}
pub const fn with_protocol_fee_bps_override(mut self, value: ProtocolFeeBps) -> Self {
self.parts.costs.protocol_fee_bps_override = Some(value);
self
}
}
impl<T: HttpTransport + Clone> QuoteRequestBuilder<T, Set, Set, Set, Set> {
pub fn into_request(self) -> QuoteRequest {
self.parts.into_request()
}
pub async fn build(self) -> Result<QuotedOrder<T>> {
let Self { api, parts, .. } = self;
let costs = parts.costs;
let request = parts.into_request();
if request.from == Address::ZERO {
return Err(Error::QuoteRequestInvalid {
field: "from",
reason: "must be the order owner, not the zero address; \
the pipeline does not infer it from the signer",
});
}
if let Some(QuoteAppData::Full(json)) = request.app_data.as_ref()
&& json.len() > APP_DATA_SIZE_LIMIT
{
return Err(Error::AppData(AppDataError::DocumentTooLarge {
len: json.len(),
max: APP_DATA_SIZE_LIMIT,
}));
}
let (app_data_hash, app_data_json) = app_data_for_submission(&request);
let response = api.quote(&request).await?;
response.check_response_matches_request(&request, app_data_hash)?;
Ok(QuotedOrder {
api,
request,
response,
app_data_hash,
app_data_json,
costs,
})
}
}
#[derive(Clone, Debug)]
pub struct QuotedOrder<T> {
api: OrderBookApi<T>,
request: QuoteRequest,
response: OrderQuoteResponse,
app_data_hash: AppDataHash,
app_data_json: Option<String>,
costs: OrderCosts,
}
impl<T: HttpTransport + Clone> QuotedOrder<T> {
pub const fn request(&self) -> &QuoteRequest {
&self.request
}
pub const fn response(&self) -> &OrderQuoteResponse {
&self.response
}
pub fn into_response(self) -> OrderQuoteResponse {
self.response
}
pub fn sign<S: SignerSync>(&self, signer: S) -> Result<OrderSubmission<T>> {
let chain = self.api.chain().ok_or(Error::OrderCreationInvalid {
field: "chain",
reason: "the signing domain can only be inferred when OrderBookApi was \
built with a chain; use sign_with",
})?;
self.sign_with(chain, EcdsaSigningScheme::Eip712, signer)
}
pub fn sign_with<S: SignerSync>(
&self,
chain: Chain,
scheme: EcdsaSigningScheme,
signer: S,
) -> Result<OrderSubmission<T>> {
if let Some(api_chain) = self.api.chain()
&& api_chain != chain
{
return Err(Error::ChainMismatch {
client: chain,
api: api_chain,
});
}
let order_data =
self.response
.try_to_order_data(&self.request, self.app_data_hash, &self.costs)?;
let domain = chain.settlement_domain();
let signature = order_data.sign(scheme, &domain, &signer)?;
let app_data_json = self
.app_data_json
.clone()
.ok_or(Error::OrderCreationInvalid {
field: "app_data",
reason: "full app-data JSON is required to submit a quote pinned \
by a non-empty hash",
})?;
let order = OrderCreation::from_signed_order_data(
&order_data,
signature,
self.response.from,
app_data_json,
Some(self.response.id),
)?;
order.verify_owner(&domain)?;
Ok(OrderSubmission {
api: self.api.clone(),
order,
})
}
}
#[derive(Clone, Debug)]
pub struct OrderSubmission<T> {
api: OrderBookApi<T>,
order: OrderCreation,
}
impl<T: HttpTransport + Clone> OrderSubmission<T> {
pub const fn order(&self) -> &OrderCreation {
&self.order
}
pub async fn submit(&self) -> Result<OrderUid> {
if self.order.app_data_hash != EMPTY_APP_DATA_HASH {
self.api
.put_app_data(
&self.order.app_data_hash,
&AppDataDocument {
full_app_data: self.order.app_data.clone(),
},
)
.await?;
}
self.api.post_order(&self.order).await
}
}
fn app_data_for_submission(request: &QuoteRequest) -> (AppDataHash, Option<String>) {
match request.app_data.as_ref() {
Some(QuoteAppData::Hash(hash)) if *hash == EMPTY_APP_DATA_HASH => {
(*hash, Some(EMPTY_APP_DATA_JSON.to_owned()))
}
Some(QuoteAppData::Hash(hash)) => (*hash, None),
Some(QuoteAppData::Full(json)) => (keccak256(json.as_bytes()), Some(json.clone())),
None => (EMPTY_APP_DATA_HASH, Some(EMPTY_APP_DATA_JSON.to_owned())),
}
}