derive-rs 0.1.16

Rust SDK for Derive.xyz
use alloy::{
    hex::encode_prefixed,
    primitives::{Address, U256},
    signers::{SignerSync, local::PrivateKeySigner},
};
use alloy_sol_types::{SolValue, sol};
use anyhow::Result;
use bigdecimal::BigDecimal;
use serde::Deserialize;
use uuid::Uuid;

use crate::{
    actions::{
        ActionData, ModuleData,
        utils::{decimal_to_i256, decimal_to_u256},
    },
    constants::{CLIENT_NAME, REFFERAL_CODE},
    models::{
        AlgoType, CreateOrderRequest as OrderParams, Direction, Instrument, OrderType,
        ReplaceOrderRequest as ReplaceParams, TimeInForce, TriggerPriceType, TriggerType,
    },
    types::Environment,
};

use bon::Builder;

fn default_max_fee() -> BigDecimal {
    BigDecimal::from(1000u64)
}

// use bigdecimal::{BigDecimal, FromPrimitive};
#[derive(Clone, Debug, Deserialize, Builder)]
pub struct OrderArgs {
    #[builder(into)]
    pub amount: BigDecimal,
    #[builder(into)]
    pub limit_price: BigDecimal,
    pub direction: Direction,
    pub time_in_force: TimeInForce,
    pub order_type: OrderType,
    pub label: Option<String>,
    pub mmp: Option<bool>,
    pub instrument_name: String,
    pub reduce_only: Option<bool>,
    pub reject_post_only: Option<bool>,
    pub reject_timestamp: Option<i64>,
    pub trigger_price: Option<BigDecimal>,
    pub trigger_price_type: Option<TriggerPriceType>,
    pub trigger_type: Option<TriggerType>,
    pub extra_fee: Option<BigDecimal>,
    pub algo_duration_sec: Option<i32>,
    pub algo_num_slices: Option<i32>,
    pub algo_type: Option<AlgoType>,
}

#[derive(Clone, Debug, Deserialize, Builder)]
pub struct ReplaceArgs {
    #[builder(into)]
    pub amount: BigDecimal,
    #[builder(into)]
    pub limit_price: BigDecimal,
    pub direction: Direction,
    pub time_in_force: TimeInForce,
    pub order_type: OrderType,
    pub label: Option<String>,
    pub mmp: Option<bool>,
    pub instrument_name: String,
    pub reduce_only: Option<bool>,
    pub reject_post_only: Option<bool>,
    pub nonce_to_cancel: Option<i64>,
    pub order_id_to_cancel: Option<Uuid>,
    pub expected_filled_amount: Option<BigDecimal>,
    pub reject_timestamp: Option<i64>,
    pub trigger_price: Option<BigDecimal>,
    pub trigger_price_type: Option<TriggerPriceType>,
    pub trigger_type: Option<TriggerType>,
    pub extra_fee: Option<BigDecimal>,
    pub algo_duration_sec: Option<i32>,
    pub algo_num_slices: Option<i32>,
    pub algo_type: Option<AlgoType>,
}

pub fn get_reject_millis(time_in_force: &TimeInForce) -> Result<i64> {
    let reject_millis: i64 = 5000;
    let taker_speedbump: i64 = 150;
    let reject_millis = match time_in_force {
        TimeInForce::PostOnly => reject_millis,
        _ => reject_millis + taker_speedbump,
    };
    Ok(reject_millis)
}

sol! {
    #![sol(all_derives)]

    struct TradeData {
        address asset_address;
        uint256 sub_id;
        int256 limit_price;
        int256 amount;
        uint256 max_fee;
        uint256 subaccount_id;
        bool is_bid;
    }
}

impl TradeData {
    pub fn new(
        instrument: &Instrument,
        subaccount_id: u64,
        limit_price: &BigDecimal,
        amount: &BigDecimal,
        is_bid: bool,
    ) -> Result<Self> {
        Ok(Self {
            asset_address: instrument.base_asset_address.parse::<Address>()?,
            sub_id: U256::from(instrument.base_asset_sub_id.parse::<u128>()?),
            limit_price: decimal_to_i256(limit_price)?,
            amount: decimal_to_i256(amount)?,
            max_fee: decimal_to_u256(&default_max_fee())?,
            subaccount_id: U256::try_from(subaccount_id)?,
            is_bid,
        })
    }
}

impl ModuleData for TradeData {
    fn get_action_data(&self) -> Vec<u8> {
        self.abi_encode()
    }
}

impl ActionData {
    pub fn populate_order_params(
        self,
        signer: &PrivateKeySigner,
        args: OrderArgs,
        env: &Environment,
        rounded_price: BigDecimal,
        rounded_amount: BigDecimal,
    ) -> Result<OrderParams> {
        Ok(OrderParams {
            instrument_name: args.instrument_name,
            amount: rounded_amount,
            limit_price: rounded_price,
            direction: args.direction,
            time_in_force: args.time_in_force,
            order_type: args.order_type,
            mmp: args.mmp,
            label: args.label,
            reduce_only: args.reduce_only,
            reject_post_only: args.reject_post_only,
            is_atomic_signing: Some(false),
            subaccount_id: i64::try_from(&self.subaccount_id)?,
            max_fee: default_max_fee(),
            nonce: self.nonce.to_string(),
            signature_expiry_sec: i64::try_from(&self.expiry)?,
            signer: encode_prefixed(self.signer),
            referral_code: Some(REFFERAL_CODE.to_string()),
            signature: format!("{}", signer.sign_hash_sync(&self.hash(env).clone())?),
            client: Some(CLIENT_NAME.to_string()),
            reject_timestamp: args.reject_timestamp,
            trigger_price: args.trigger_price,
            trigger_price_type: args.trigger_price_type,
            trigger_type: args.trigger_type,
            extra_fee: args.extra_fee,
            algo_duration_sec: args.algo_duration_sec,
            algo_num_slices: args.algo_num_slices,
            algo_type: args.algo_type,
        })
    }

    pub fn populate_replace_params(
        self,
        signer: &PrivateKeySigner,
        args: ReplaceArgs,
        env: &Environment,
        rounded_price: BigDecimal,
        rounded_amount: BigDecimal,
    ) -> Result<ReplaceParams> {
        Ok(ReplaceParams {
            instrument_name: args.instrument_name,
            amount: rounded_amount,
            limit_price: rounded_price,
            direction: args.direction,
            time_in_force: args.time_in_force,
            order_type: args.order_type,
            mmp: args.mmp,
            label: args.label,
            reduce_only: args.reduce_only,
            reject_post_only: args.reject_post_only,
            reject_timestamp: args.reject_timestamp,
            is_atomic_signing: Some(false),
            subaccount_id: i64::try_from(&self.subaccount_id)?,
            max_fee: default_max_fee(),
            nonce: self.nonce.to_string(),
            signature_expiry_sec: i64::try_from(&self.expiry)?,
            signer: encode_prefixed(self.signer),
            referral_code: Some(REFFERAL_CODE.to_string()),
            signature: format!("{}", signer.sign_hash_sync(&self.hash(env).clone())?),
            client: Some(CLIENT_NAME.to_string()),
            trigger_price: args.trigger_price,
            trigger_price_type: args.trigger_price_type,
            trigger_type: args.trigger_type,
            extra_fee: args.extra_fee,
            algo_duration_sec: args.algo_duration_sec,
            algo_num_slices: args.algo_num_slices,
            algo_type: args.algo_type,
            nonce_to_cancel: args.nonce_to_cancel,
            order_id_to_cancel: args.order_id_to_cancel,
            expected_filled_amount: args.expected_filled_amount,
        })
    }
}