1use crate::orders::{OrderPreimageArgs, SignerLike, compute_order_id, sign_order_id_with_signer};
14use crate::signing::{AsyncSignerLike, SigningError};
15use crate::types::ids::{Nonce, OrderId, Price, Quantity, Strike};
16use crate::types::{PositionType, QuoteExpiry};
17use crate::wire::{WireError, decode_base58_32, encode_base58};
18use crate::ws::types::{QuoteMessage, ReplaceQuoteMessage, RfqBroadcastMessage};
19use uuid::Uuid;
20
21#[derive(Debug, thiserror::Error)]
23#[non_exhaustive]
24pub enum QuoteBuildError {
25 #[error("rfq field `{field}` is not a valid base58 32-byte key: {source}")]
26 Identity {
27 field: &'static str,
28 #[source]
29 source: WireError,
30 },
31
32 #[error("strike {requested} is not offered by this rfq; offered: {offered:?}")]
36 StrikeNotOffered {
37 requested: Strike,
38 offered: Vec<Strike>,
39 },
40
41 #[error("quote is missing `{0}`")]
42 Missing(&'static str),
43
44 #[error("signing failed: {0}")]
45 Signing(#[from] SigningError),
46}
47
48#[derive(Debug, Clone)]
53pub struct RfqBinding {
54 rfq_id: Uuid,
55 chain_id: u64,
56 program_id: [u8; 32],
57 market: [u8; 32],
58 taker: [u8; 32],
59 position_type: PositionType,
60 quantity: Quantity,
61 requested_strike: Strike,
62 offered_strikes: Vec<Strike>,
63}
64
65fn identity(field: &'static str, value: &str) -> Result<[u8; 32], QuoteBuildError> {
66 decode_base58_32(value).map_err(|source| QuoteBuildError::Identity { field, source })
67}
68
69impl RfqBinding {
70 pub fn from_broadcast(rfq: &RfqBroadcastMessage) -> Result<Self, QuoteBuildError> {
76 let mut offered_strikes = Vec::with_capacity(1 + rfq.order_options.len());
77 offered_strikes.push(rfq.strike);
78 offered_strikes.extend(
79 rfq.order_options
80 .iter()
81 .map(|option| option.strike)
82 .filter(|strike| *strike != rfq.strike),
83 );
84
85 Ok(Self {
86 rfq_id: rfq.rfq_id,
87 chain_id: rfq.market.chain_id.value(),
88 program_id: identity("market.program_id", &rfq.market.program_id)?,
89 market: identity("market.market_pda", &rfq.market.market_pda)?,
90 taker: identity("taker", &rfq.taker)?,
91 position_type: rfq.position_type,
92 quantity: rfq.quantity,
93 requested_strike: rfq.strike,
94 offered_strikes,
95 })
96 }
97
98 #[must_use]
99 pub const fn rfq_id(&self) -> Uuid {
100 self.rfq_id
101 }
102
103 #[must_use]
106 pub const fn quantity(&self) -> Quantity {
107 self.quantity
108 }
109
110 #[must_use]
111 pub const fn position_type(&self) -> PositionType {
112 self.position_type
113 }
114
115 #[must_use]
117 pub fn offered_strikes(&self) -> &[Strike] {
118 &self.offered_strikes
119 }
120
121 pub const fn quote(&self) -> QuoteBuilder<'_> {
123 QuoteBuilder {
124 binding: self,
125 strike: self.requested_strike,
126 price: None,
127 valid_until: None,
128 nonce: None,
129 maker: None,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy)]
136struct Resolved {
137 strike: Strike,
138 price: Price,
139 valid_until: QuoteExpiry,
140 nonce: Nonce,
141}
142
143#[derive(Debug, Clone)]
145#[must_use = "a builder does nothing until it is signed"]
146pub struct QuoteBuilder<'a> {
147 binding: &'a RfqBinding,
148 strike: Strike,
149 price: Option<Price>,
150 valid_until: Option<QuoteExpiry>,
151 nonce: Option<Nonce>,
152 maker: Option<[u8; 32]>,
153}
154
155impl QuoteBuilder<'_> {
156 pub const fn strike(mut self, strike: Strike) -> Self {
158 self.strike = strike;
159 self
160 }
161
162 pub const fn price(mut self, price: Price) -> Self {
163 self.price = Some(price);
164 self
165 }
166
167 pub const fn valid_until(mut self, valid_until: QuoteExpiry) -> Self {
168 self.valid_until = Some(valid_until);
169 self
170 }
171
172 pub const fn nonce(mut self, nonce: Nonce) -> Self {
173 self.nonce = Some(nonce);
174 self
175 }
176
177 pub const fn maker_owner(mut self, owner: [u8; 32]) -> Self {
184 self.maker = Some(owner);
185 self
186 }
187
188 fn resolve(&self) -> Result<Resolved, QuoteBuildError> {
189 if !self.binding.offered_strikes.contains(&self.strike) {
190 return Err(QuoteBuildError::StrikeNotOffered {
191 requested: self.strike,
192 offered: self.binding.offered_strikes.clone(),
193 });
194 }
195 Ok(Resolved {
196 strike: self.strike,
197 price: self.price.ok_or(QuoteBuildError::Missing("price"))?,
198 valid_until: self
199 .valid_until
200 .ok_or(QuoteBuildError::Missing("valid_until"))?,
201 nonce: self.nonce.ok_or(QuoteBuildError::Missing("nonce"))?,
202 })
203 }
204
205 const fn maker_for(&self, signer_pubkey: [u8; 32]) -> [u8; 32] {
206 match self.maker {
207 Some(owner) => owner,
208 None => signer_pubkey,
209 }
210 }
211
212 fn args_for(&self, resolved: Resolved, maker: [u8; 32]) -> OrderPreimageArgs {
213 OrderPreimageArgs {
214 chain_id: self.binding.chain_id,
215 program_id: self.binding.program_id,
216 is_taker_buy: false,
218 position_type: self.binding.position_type,
219 market: self.binding.market,
220 strike: resolved.strike.value(),
221 quantity: self.binding.quantity.value(),
222 gross_price: resolved.price.value(),
223 valid_until: resolved.valid_until.as_unix_seconds(),
224 maker,
225 taker: self.binding.taker,
226 nonce: resolved.nonce.value(),
227 }
228 }
229
230 pub fn preimage_args(&self, maker: [u8; 32]) -> Result<OrderPreimageArgs, QuoteBuildError> {
239 let resolved = self.resolve()?;
240 Ok(self.args_for(resolved, self.maker_for(maker)))
241 }
242
243 pub fn sign<S: SignerLike>(&self, signer: &S) -> Result<QuoteMessage, QuoteBuildError> {
253 let (resolved, order_id, signature) = self.sign_parts(signer)?;
254 Ok(self.assemble(resolved, order_id, signature))
255 }
256
257 pub async fn sign_with_async_signer<S: AsyncSignerLike + ?Sized>(
265 &self,
266 signer: &S,
267 ) -> Result<QuoteMessage, QuoteBuildError> {
268 let resolved = self.resolve()?;
269 let args = self.args_for(resolved, self.maker_for(signer.pubkey_bytes()));
270 let order_id = compute_order_id(&args);
271 let signature = crate::orders::sign_order_id_with_async_signer(&order_id, signer).await?;
272 Ok(self.assemble(resolved, order_id, signature))
273 }
274
275 fn sign_parts<S: SignerLike>(
276 &self,
277 signer: &S,
278 ) -> Result<(Resolved, [u8; 32], [u8; 64]), QuoteBuildError> {
279 let resolved = self.resolve()?;
280 let args = self.args_for(resolved, self.maker_for(signer.pubkey_bytes()));
281 let order_id = compute_order_id(&args);
282 Ok((
283 resolved,
284 order_id,
285 sign_order_id_with_signer(&order_id, signer),
286 ))
287 }
288
289 fn assemble(
293 &self,
294 resolved: Resolved,
295 order_id: [u8; 32],
296 signature: [u8; 64],
297 ) -> QuoteMessage {
298 QuoteMessage {
299 rfq_id: self.binding.rfq_id,
300 strike: resolved.strike,
301 price: resolved.price,
302 valid_until: resolved.valid_until,
303 nonce: resolved.nonce,
304 order_id: OrderId::new(order_id),
305 signature: encode_base58(&signature),
306 }
307 }
308}
309
310impl QuoteMessage {
311 #[must_use]
317 pub fn into_replacement(self, old_order_id: OrderId) -> ReplaceQuoteMessage {
318 ReplaceQuoteMessage {
319 old_order_id,
320 rfq_id: self.rfq_id,
321 strike: self.strike,
322 price: self.price,
323 valid_until: self.valid_until,
324 nonce: self.nonce,
325 order_id: self.order_id,
326 signature: self.signature,
327 }
328 }
329}
330
331#[cfg(test)]
332#[path = "quote_tests.rs"]
333mod tests;