acta-maker-sdk 0.8.0

Rust SDK for the Acta options RFQ venue on Solana: maker and vault-operator WebSocket clients, order signing, chain builders
Documentation
use crate::Address32;
use crate::types::QuoteExpiry;
use crate::types::ids::{
    MarketId, Nonce, OptionTerms, OrderId, PositionType, Price, Quantity, Strike, TakerSide,
    TimeoutSeconds,
};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use strum::IntoStaticStr;
use uuid::Uuid;

use super::client_query::*;
use super::client_vault::*;
use super::common::{MarketDescriptor, WsChannel};

#[derive(Debug, Clone, Serialize, Deserialize, IntoStaticStr)]
#[serde(tag = "type", content = "data")]
#[strum(serialize_all = "snake_case")]
pub enum ClientMessage {
    Hello(HelloData),
    StartAuth(StartAuthData),
    StartVaultAuth(StartVaultAuthData),
    ResumeAuth(ResumeAuthData),
    Logout,
    AuthChallenge(AuthChallengeData),
    Quote(QuoteMessage),
    ReplaceQuote(ReplaceQuoteMessage),
    BatchQuotes(BatchQuotesMessage),
    CancelQuote(CancelQuoteData),
    IndicativePricesResponse(IndicativePricesResponseMessage),
    RfqRequest(RfqRequestMessage),
    AcceptQuote(AcceptQuoteMessage),
    SubmitSignedSponsoredTx(SubmitSignedSponsoredTxData),
    SwapQuote(SwapQuoteData),
    SwapSubmit(SwapSubmitData),
    GetSwapStatus(GetSwapStatusMessage),
    CancelRfq(CancelRfqData),
    GetIndicativePrices(GetIndicativePricesMessage),
    GetPositions(GetPositionsMessage),
    GetPositionSummary(GetPositionSummaryMessage),
    GetMyActiveRfqs(GetMyActiveRfqsMessage),
    GetOrderStatus(GetOrderStatusMessage),
    GetMarkets(GetMarketsMessage),
    GetMarketDescriptors(GetMarketDescriptorsMessage),
    GetExpiries(GetExpiriesMessage),
    GetTokens(GetTokensMessage),
    GetActiveRfqs(GetActiveRfqsMessage),
    GetMakerPositions(GetMakerPositionsMessage),
    GetMyQuotes(GetMyQuotesMessage),
    GetMarketsForMaker(GetMarketsForMakerMessage),
    GetTokenCaps(GetTokenCapsMessage),
    GetMyCaps(GetMyCapsMessage),
    CheckQuote(CheckQuoteMessage),
    GetMyTrades(GetMyTradesMessage),
    GetEarnSummary(GetEarnSummaryMessage),
    GetMmSummary(GetMmSummaryMessage),
    GetTokenMarketsInfo(GetTokenMarketsInfoMessage),
    GetSubscriptions(GetSubscriptionsMessage),
    CancelAllQuotes(CancelAllQuotesMessage),
    Ping,
    Subscribe(SubscribeData),
    Unsubscribe(UnsubscribeData),
    AddMints(AddMintsData),
    RemoveMints(RemoveMintsData),
    AddChannels(AddChannelsData),
    RemoveChannels(RemoveChannelsData),
    RedeemInvite(RedeemInviteData),
    ClaimReferralCode(ClaimReferralCodeData),
    GetMyReferralInfo(GetMyReferralInfoData),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HelloData {
    pub protocol_version: String,
    pub on_disconnect: crate::ws::types::OnDisconnect,
    pub client_name: String,
    pub client_version: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StartAuthData {
    pub pubkey: Address32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResumeAuthData {
    pub session_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthChallengeData {
    pub challenge: String,
    pub signature: String,
    pub pubkey: Address32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelQuoteData {
    pub rfq_id: Uuid,
    pub request_id: Uuid,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmitSignedSponsoredTxData {
    pub request_id: Uuid,
    pub order_id: OrderId,
    pub tx_base64: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelRfqData {
    pub rfq_id: Uuid,
    pub request_id: Uuid,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribeData {
    pub products: crate::ProductSet,
    pub request_id: Uuid,
    pub channels: Vec<WsChannel>,
    pub underlying_mints: Vec<Address32>,
    pub quote_mints: Vec<Address32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnsubscribeData {
    pub request_id: Uuid,
    pub channels: Vec<WsChannel>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddMintsData {
    pub request_id: Uuid,
    pub underlying_mints: Vec<Address32>,
    pub quote_mints: Vec<Address32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoveMintsData {
    pub request_id: Uuid,
    pub underlying_mints: Vec<Address32>,
    pub quote_mints: Vec<Address32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddChannelsData {
    pub request_id: Uuid,
    pub channels: Vec<WsChannel>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemoveChannelsData {
    pub request_id: Uuid,
    pub channels: Vec<WsChannel>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedeemInviteData {
    pub request_id: Uuid,
    pub code: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaimReferralCodeData {
    pub request_id: Uuid,
    pub code: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetMyReferralInfoData {
    pub request_id: Uuid,
}

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuoteMessage {
    pub rfq_id: Uuid,
    pub terms: OptionTerms,
    pub side: TakerSide,
    pub price: Price,
    pub valid_until: QuoteExpiry,
    pub nonce: Nonce,
    pub order_id: OrderId,
    pub signature: String,
}

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReplaceQuoteMessage {
    pub old_order_id: OrderId,
    pub rfq_id: Uuid,
    pub terms: OptionTerms,
    pub side: TakerSide,
    pub price: Price,
    pub valid_until: QuoteExpiry,
    pub nonce: Nonce,
    pub order_id: OrderId,
    pub signature: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchQuotesMessage {
    pub quotes: Vec<QuoteMessage>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RfqRequestMessage {
    /// Also the idempotency key: a repeated `request_id` returns the RFQ it created.
    pub request_id: Uuid,
    pub market: MarketId,
    pub terms: OptionTerms,
    pub side: TakerSide,
    pub quantity: Quantity,
    pub timeout_seconds: TimeoutSeconds,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AcceptQuoteMessage {
    pub request_id: Uuid,
    pub rfq_id: Uuid,
    pub maker: Address32,
    pub order_id: OrderId,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndicativePricesRequestMessage {
    pub request_id: Uuid,
    pub market: MarketDescriptor,
    pub position_type: PositionType,
    pub side: TakerSide,
    pub strikes: Vec<Strike>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndicativePricesResponseMessage {
    pub request_id: Uuid,
    pub market: MarketId,
    pub position_type: PositionType,
    pub side: TakerSide,
    pub prices: Vec<IndicativeStrikePrice>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndicativeStrikePrice {
    pub strike: Strike,
    pub price: Price,
}

impl ClientMessage {
    #[must_use]
    pub const fn request_id(&self) -> Option<Uuid> {
        match self {
            Self::GetPositions(m) => Some(m.request_id),
            Self::GetPositionSummary(m) => Some(m.request_id),
            Self::GetMyActiveRfqs(m) => Some(m.request_id),
            Self::GetOrderStatus(m) => Some(m.request_id),
            Self::GetMarkets(m) => Some(m.request_id),
            Self::GetMarketDescriptors(m) => Some(m.request_id),
            Self::GetExpiries(m) => Some(m.request_id),
            Self::GetTokens(m) => Some(m.request_id),
            Self::GetActiveRfqs(m) => Some(m.request_id),
            Self::GetMakerPositions(m) => Some(m.request_id),
            Self::GetMyQuotes(m) => Some(m.request_id),
            Self::GetMyTrades(m) => Some(m.request_id),
            Self::GetMarketsForMaker(m) => Some(m.request_id),
            Self::GetSubscriptions(m) => Some(m.request_id),
            Self::GetTokenCaps(m) => Some(m.request_id),
            Self::GetMyCaps(m) => Some(m.request_id),
            Self::CheckQuote(m) => Some(m.request_id),
            Self::GetEarnSummary(m) => Some(m.request_id),
            Self::GetMmSummary(m) => Some(m.request_id),
            Self::GetTokenMarketsInfo(m) => Some(m.request_id),
            Self::GetIndicativePrices(m) => Some(m.request_id),
            Self::CancelQuote(m) => Some(m.request_id),
            Self::CancelRfq(m) => Some(m.request_id),
            Self::CancelAllQuotes(m) => Some(m.request_id),
            Self::Subscribe(m) => Some(m.request_id),
            Self::Unsubscribe(m) => Some(m.request_id),
            Self::AddMints(m) => Some(m.request_id),
            Self::RemoveMints(m) => Some(m.request_id),
            Self::AddChannels(m) => Some(m.request_id),
            Self::RemoveChannels(m) => Some(m.request_id),
            Self::GetMyReferralInfo(m) => Some(m.request_id),
            Self::RedeemInvite(m) => Some(m.request_id),
            Self::ClaimReferralCode(m) => Some(m.request_id),
            Self::SwapQuote(m) => Some(m.request_id),
            Self::SwapSubmit(m) => Some(m.request_id),
            Self::GetSwapStatus(m) => Some(m.request_id),
            Self::RfqRequest(m) => Some(m.request_id),
            Self::AcceptQuote(m) => Some(m.request_id),
            Self::SubmitSignedSponsoredTx(m) => Some(m.request_id),

            Self::Hello(_)
            | Self::StartAuth(_)
            | Self::StartVaultAuth(_)
            | Self::ResumeAuth(_)
            | Self::Logout
            | Self::AuthChallenge(_)
            | Self::Quote(_)
            | Self::ReplaceQuote(_)
            | Self::BatchQuotes(_)
            | Self::IndicativePricesResponse(_)
            | Self::Ping => None,
        }
    }
}

/// Transport lane. Control frames must never queue behind quote traffic.
#[cfg(feature = "ws-client")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Lane {
    Control,
    Data,
}

#[cfg(feature = "ws-client")]
#[derive(Debug, Clone)]
pub(crate) enum SubscriptionChange {
    Subscribe(SubscribeData),
    Unsubscribe(UnsubscribeData),
    AddChannels(AddChannelsData),
    RemoveChannels(RemoveChannelsData),
    AddMints(AddMintsData),
    RemoveMints(RemoveMintsData),
}

#[cfg(feature = "ws-client")]
impl SubscriptionChange {
    pub(crate) fn from_message(message: &ClientMessage) -> Option<Self> {
        match message {
            ClientMessage::Subscribe(data) => Some(Self::Subscribe(data.clone())),
            ClientMessage::Unsubscribe(data) => Some(Self::Unsubscribe(data.clone())),
            ClientMessage::AddChannels(data) => Some(Self::AddChannels(data.clone())),
            ClientMessage::RemoveChannels(data) => Some(Self::RemoveChannels(data.clone())),
            ClientMessage::AddMints(data) => Some(Self::AddMints(data.clone())),
            ClientMessage::RemoveMints(data) => Some(Self::RemoveMints(data.clone())),
            _ => None,
        }
    }

    pub(crate) fn request_id(&self) -> Uuid {
        match self {
            Self::Subscribe(data) => data.request_id,
            Self::Unsubscribe(data) => data.request_id,
            Self::AddChannels(data) => data.request_id,
            Self::RemoveChannels(data) => data.request_id,
            Self::AddMints(data) => data.request_id,
            Self::RemoveMints(data) => data.request_id,
        }
    }
}

#[cfg(feature = "ws-client")]
impl ClientMessage {
    pub(crate) const fn requires_ready(&self) -> bool {
        matches!(
            self,
            Self::Quote(_)
                | Self::BatchQuotes(_)
                | Self::ReplaceQuote(_)
                | Self::IndicativePricesResponse(_)
                | Self::CancelQuote(_)
                | Self::CancelAllQuotes(_)
                | Self::RfqRequest(_)
                | Self::AcceptQuote(_)
                | Self::SubmitSignedSponsoredTx(_)
                | Self::CancelRfq(_)
                | Self::SwapQuote(_)
                | Self::SwapSubmit(_)
                | Self::GetSwapStatus(_)
                | Self::GetOrderStatus(_)
                | Self::Subscribe(_)
                | Self::Unsubscribe(_)
                | Self::AddChannels(_)
                | Self::RemoveChannels(_)
                | Self::AddMints(_)
                | Self::RemoveMints(_)
        )
    }

    pub(crate) const fn lane(&self) -> Lane {
        match self {
            Self::Hello(_)
            | Self::StartAuth(_)
            | Self::StartVaultAuth(_)
            | Self::ResumeAuth(_)
            | Self::AuthChallenge(_)
            | Self::Logout
            | Self::Ping
            | Self::CancelRfq(_) => Lane::Control,
            _ => Lane::Data,
        }
    }
}