risq 0.4.1

Re-implementation of Bisq (https://github.com/bisq-network/bisq) in rust
use crate::{
    bisq::SequencedMessageHash,
    domain::{amount::NumberWithPrecision, currency::*, market::Market, price_feed::PriceData},
};
use std::{
    collections::HashMap,
    sync::Arc,
    time::{Duration, SystemTime},
};

const INITIAL_TTL: Duration = Duration::from_secs(12 * 60);
const REFRESH_TTL: Duration = Duration::from_secs(9 * 60);

#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct OfferId(String);
impl From<String> for OfferId {
    fn from(id: String) -> Self {
        OfferId(id)
    }
}
impl From<OfferId> for String {
    fn from(id: OfferId) -> Self {
        id.0
    }
}

#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
pub struct OfferSequence(i32);
impl From<i32> for OfferSequence {
    fn from(s: i32) -> Self {
        OfferSequence(s)
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OfferDirection {
    Buy,
    Sell,
}
impl OfferDirection {
    pub fn oposite(self) -> Self {
        match self {
            OfferDirection::Buy => OfferDirection::Sell,
            OfferDirection::Sell => OfferDirection::Buy,
        }
    }
}

#[derive(Clone, Copy, PartialEq)]
pub enum OfferPrice {
    Fixed(NumberWithPrecision),
    MarketWithMargin(f64),
}

#[derive(Clone, Copy, PartialEq)]
pub struct OfferAmount {
    pub total: NumberWithPrecision,
    pub min: NumberWithPrecision,
}

#[derive(Clone, PartialEq)]
pub struct OpenOffer {
    pub bisq_hash: SequencedMessageHash,
    pub market: &'static Market,
    pub id: OfferId,
    pub direction: OfferDirection,
    pub amount: OfferAmount,
    pub payment_method_id: String,
    pub offer_fee_tx_id: String,
    pub created_at: SystemTime,
    pub display_price: NumberWithPrecision,

    pub(super) latest_sequence: OfferSequence,

    price: OfferPrice,
    expires_at: SystemTime,
}

impl OpenOffer {
    pub fn new(
        bisq_hash: SequencedMessageHash,
        market: &'static Market,
        id: OfferId,
        direction: OfferDirection,
        price: OfferPrice,
        amount: OfferAmount,
        payment_method_id: String,
        offer_fee_tx_id: String,
        created_at: SystemTime,
        sequence: OfferSequence,
    ) -> OpenOffer {
        let display_price = if let OfferPrice::Fixed(price) = price {
            price
        } else {
            NumberWithPrecision::new(0, 0)
        };
        Self {
            bisq_hash,
            market,
            id,
            direction,
            price,
            amount,
            payment_method_id,
            display_price,
            created_at,
            expires_at: created_at + INITIAL_TTL,
            latest_sequence: sequence,
            offer_fee_tx_id,
        }
    }

    pub fn is_expired(&self) -> bool {
        self.expires_at.elapsed().is_ok()
    }

    pub(super) fn update_display_price(
        &mut self,
        price_data: &Arc<HashMap<&'static str, PriceData>>,
    ) {
        if let OfferPrice::MarketWithMargin(margin) = self.price {
            // logic taken from https://github.com/bisq-network/bisq/blob/master/core/src/main/java/bisq/core/offer/Offer.java#L161
            let code: &'static str = &self.market.non_btc_side().code;
            if let Some(data) = price_data.get(code) {
                let factor = match (&data.currency.currency_type, self.direction) {
                    (CurrencyType::Crypto, OfferDirection::Sell)
                    | (CurrencyType::Fiat, OfferDirection::Buy) => 1.0 - margin,
                    _ => 1.0 + margin,
                };
                let display = factor
                    * data.price
                    * 10_f64.powf(data.currency.bisq_internal_precision() as f64);
                self.display_price = NumberWithPrecision::new(
                    display as u64,
                    data.currency.bisq_internal_precision(),
                );
            }
        }
    }

    pub(super) fn would_refresh(&self, sequence: OfferSequence) -> bool {
        sequence > self.latest_sequence
    }
    pub(super) fn refresh(&mut self, sequence: OfferSequence) -> bool {
        if sequence > self.latest_sequence {
            self.expires_at = SystemTime::now() + REFRESH_TTL;
            self.latest_sequence = sequence;
            return true;
        }
        false
    }
}