use std::fmt::{Debug, Display};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
use rust_decimal::Decimal;
pub trait Unit {
const PRECISION: u8;
const SUFFIX: &str;
const ONE_BTC: u64 = 10u64.pow(Self::PRECISION as u32);
const ONE_BTC_DEC: Decimal = Decimal::from_parts(Self::ONE_BTC as u32, 0, 0, false, 0);
const BTC_PER_UNIT: Decimal = Decimal::from_parts(1, 0, 0, false, Self::PRECISION as u32);
const SAT_PER_UNIT: u64 = 10u64.pow(8 - Self::PRECISION as u32);
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Btc;
impl Unit for Btc {
const PRECISION: u8 = 0;
const SUFFIX: &str = "BTC";
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Sat;
impl Unit for Sat {
const PRECISION: u8 = 8;
const SUFFIX: &str = "SAT";
}
pub type AmountDenom = Sat;
#[derive(Default, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Amount(pub u64);
impl<X: Into<u64>> From<X> for Amount {
fn from(value: X) -> Self {
Self(value.into())
}
}
impl Debug for Amount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple(&format!("Amount<{}>", AmountDenom::SUFFIX)).field(&self.0).finish()
}
}
impl Display for Amount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Amount {
pub const ZERO: Self = Self(0);
pub const ONE: Self = Self(1);
pub const ONE_BTC: Self = Self(AmountDenom::ONE_BTC);
pub const MIN: Self = Self(0);
pub const MAX: Self = Self(21_000_000 * Sat::ONE_BTC);
pub fn as_decimal(self) -> Decimal {
self.0.into()
}
pub fn try_from_decimal(v: Decimal) -> Option<Self> {
Some(Self(v.try_into().ok()?))
}
pub fn from_decimal(v: Decimal) -> Self {
Self(v.try_into().expect("Amount must be in range 0..u64::MAX"))
}
pub const fn as_sats(self) -> u64 {
self.0 * AmountDenom::SAT_PER_UNIT
}
pub fn as_btc(self) -> Decimal {
Decimal::from(self.0) * AmountDenom::BTC_PER_UNIT
}
pub fn try_from_btc(v: Decimal) -> Option<Self> {
Self::try_from_decimal(v * AmountDenom::ONE_BTC_DEC)
}
pub fn from_btc(v: Decimal) -> Self {
Self::from_decimal(v * AmountDenom::ONE_BTC_DEC)
}
pub const fn from_sat(v: u64) -> Self {
Self(v / AmountDenom::SAT_PER_UNIT)
}
pub const fn min(self, other: Self) -> Self {
Self(if self.0 <= other.0 { self.0 } else { other.0 })
}
pub const fn max(self, other: Self) -> Self {
Self(if self.0 >= other.0 { self.0 } else { other.0 })
}
pub const fn is_zero(self) -> bool {
self.0 == 0
}
}
impl Add for Amount {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl AddAssign for Amount {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl Sub for Amount {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl SubAssign for Amount {
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
impl<X> Mul<X> for Amount
where
Decimal: TryFrom<X>,
{
type Output = Self;
fn mul(self, rhs: X) -> Self::Output {
Amount::from_btc(self.as_btc() * Decimal::try_from(rhs).unwrap_or_default())
}
}
impl<X> MulAssign<X> for Amount
where
Decimal: TryFrom<X>,
{
fn mul_assign(&mut self, rhs: X) {
*self = Amount::from_btc(self.as_btc() * Decimal::try_from(rhs).unwrap_or_default());
}
}
impl<X> Div<X> for Amount
where
Decimal: TryFrom<X>,
{
type Output = Self;
fn div(self, rhs: X) -> Self::Output {
Amount::from_btc(self.as_btc() / Decimal::try_from(rhs).unwrap_or_default())
}
}
impl<X> DivAssign<X> for Amount
where
Decimal: TryFrom<X>,
{
fn div_assign(&mut self, rhs: X) {
*self = Amount::from_btc(self.as_btc() / Decimal::try_from(rhs).unwrap_or_default());
}
}