bullet-rust-sdk 0.0.43

Rust SDK for the Bullet trading platform
//! Trading helpers that construct, sign and submit call messages.

use bullet_exchange_interface::decimals::PositiveDecimal;
use bullet_exchange_interface::message::{AmendOrderArgs, CancelOrderArgs, NewOrderArgs};
use bullet_exchange_interface::types::{MarketId, OrderType, Side};

use crate::generated::types::SubmitTxResponse;
use crate::types::{CallMessage, UserAction};
use crate::{Client, SDKError, SDKResult};

/// Extension constructors for [`NewOrderArgs`].
///
/// For `reduce_only`, `client_order_id` or `pending_tpsl_pair`, construct `NewOrderArgs` directly.
pub trait NewOrderExt {
    /// Create a limit order. Defaults: `reduce_only: false`, no client order ID, no TP/SL.
    fn limit(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self;
    /// Create a post-only (maker) order. Rejected if it would cross the book.
    fn post_only(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self;
    /// Create an immediate-or-cancel order (market-order equivalent).
    ///
    /// Fills what it can at the given price, cancels the rest.
    fn ioc(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self;
}

impl NewOrderExt for NewOrderArgs {
    fn limit(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self {
        new_order(price, size, side, OrderType::Limit)
    }

    fn post_only(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self {
        new_order(price, size, side, OrderType::PostOnly)
    }

    fn ioc(price: PositiveDecimal, size: PositiveDecimal, side: Side) -> Self {
        new_order(price, size, side, OrderType::ImmediateOrCancel)
    }
}

fn new_order(
    price: PositiveDecimal,
    size: PositiveDecimal,
    side: Side,
    order_type: OrderType,
) -> NewOrderArgs {
    NewOrderArgs {
        price,
        size,
        side,
        order_type,
        reduce_only: false,
        client_order_id: None,
        pending_tpsl_pair: None,
    }
}

impl Client {
    /// Place orders on a market. Signs and submits the transaction.
    ///
    /// # Arguments
    ///
    /// * `market_id` — Numeric market ID (resolve via `client.market_id("BTC-USD")`)
    /// * `orders` — One or more orders to place
    /// * `replace` — If `true`, cancel existing orders before placing new ones
    /// * `sub_account_index` — `None` for the main account, `Some(n)` for a sub-account
    pub async fn place_orders(
        &self,
        market_id: MarketId,
        orders: Vec<NewOrderArgs>,
        replace: bool,
        sub_account_index: Option<u8>,
    ) -> SDKResult<SubmitTxResponse> {
        let call_msg = CallMessage::User(UserAction::PlaceOrders {
            market_id,
            orders,
            replace,
            sub_account_index,
        });
        self.send_call_message(call_msg).await
    }

    /// Cancel specific orders on a market. Signs and submits the transaction.
    ///
    /// Cancel by exchange-assigned `OrderId`, client-assigned `ClientOrderId`, or both.
    pub async fn cancel_orders(
        &self,
        market_id: MarketId,
        orders: Vec<CancelOrderArgs>,
        sub_account_index: Option<u8>,
    ) -> SDKResult<SubmitTxResponse> {
        let call_msg = CallMessage::User(UserAction::CancelOrders {
            market_id,
            orders,
            sub_account_index,
        });
        self.send_call_message(call_msg).await
    }

    /// Cancel all orders on a specific market. Signs and submits the transaction.
    pub async fn cancel_market_orders(
        &self,
        market_id: MarketId,
        sub_account_index: Option<u8>,
    ) -> SDKResult<SubmitTxResponse> {
        let call_msg = CallMessage::User(UserAction::CancelMarketOrders {
            market_id,
            sub_account_index,
        });
        self.send_call_message(call_msg).await
    }

    /// Cancel all orders across all markets. Signs and submits the transaction.
    pub async fn cancel_all_orders(
        &self,
        sub_account_index: Option<u8>,
    ) -> SDKResult<SubmitTxResponse> {
        let call_msg = CallMessage::User(UserAction::CancelAllOrders { sub_account_index });
        self.send_call_message(call_msg).await
    }

    /// Get the base58 address derived from the client's keypair.
    ///
    /// Returns `Err(SDKError::MissingKeypair)` if no keypair is configured.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let address = client.address()?;
    /// println!("My address: {address}"); // e.g. "5Hq3...xyz"
    /// ```
    pub fn address(&self) -> SDKResult<String> {
        let kp = self.keypair().ok_or(SDKError::MissingKeypair)?;
        Ok(kp.address())
    }

    /// Query open orders for the client's own account on a symbol.
    pub async fn my_open_orders(
        &self,
        symbol: &str,
    ) -> SDKResult<Vec<crate::generated::types::BinanceOrder>> {
        let address = self.address()?;
        // The generated client takes `Option<symbol>` (None = all markets); keep it per-symbol.
        let resp = self.query_open_orders(&address, Some(symbol)).await?;
        Ok(resp.into_inner())
    }

    /// Query account info (positions, margins) for the client's own account.
    ///
    /// Derives the address from the client's keypair and unwraps the response.
    pub async fn my_account(&self) -> SDKResult<crate::generated::types::Account> {
        let address = self.address()?;
        let resp = self.account_info(&address).await?;
        Ok(resp.into_inner())
    }

    /// Query balances for the client's own account.
    ///
    /// Derives the address from the client's keypair and unwraps the response.
    pub async fn my_balances(&self) -> SDKResult<Vec<crate::generated::types::Balance>> {
        let address = self.address()?;
        let resp = self.account_balance(&address).await?;
        Ok(resp.into_inner())
    }

    /// Amend (cancel + replace) existing orders. Signs and submits the transaction.
    ///
    /// Each [`AmendOrderArgs`] pairs a [`CancelOrderArgs`] with a [`NewOrderArgs`],
    /// atomically replacing the cancelled order with a new one.
    pub async fn amend_orders(
        &self,
        market_id: MarketId,
        orders: Vec<AmendOrderArgs>,
        sub_account_index: Option<u8>,
    ) -> SDKResult<SubmitTxResponse> {
        let call_msg = CallMessage::User(UserAction::AmendOrders {
            market_id,
            orders,
            sub_account_index,
        });
        self.send_call_message(call_msg).await
    }
}