nintypes 0.2.11

Nintondo shared types
Documentation
use std::fmt::Debug;
use std::str::FromStr;

use derive_more::derive::{Deref, DerefMut};
use dutils::error::ContextWrapper;
use itertools::Itertools;
use rand::Rng;

use crate::utils::bytes::{concat_arrays, NinBytes};
use crate::utils::sha256::IntoHash256;

use super::hash::Hash256;

#[derive(
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    bytemuck::Pod,
    bytemuck::Zeroable,
    serde::Serialize,
    serde::Deserialize,
    Default,
    derive_more::Add,
    derive_more::Sub,
    derive_more::Mul,
    derive_more::From,
)]
#[mul(forward)]
#[repr(C)]
#[serde(transparent)]
#[derive(Deref, DerefMut)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct InscriptionNumber(pub u64);

impl num_traits::Bounded for InscriptionNumber {
    fn min_value() -> Self {
        Self(0)
    }

    fn max_value() -> Self {
        Self::MAX
    }
}

impl num_traits::Zero for InscriptionNumber {
    fn zero() -> Self {
        Self(0)
    }

    fn is_zero(&self) -> bool {
        self.0 == 0
    }
}

impl num_traits::One for InscriptionNumber {
    fn one() -> Self {
        Self(1)
    }
}

impl num_traits::SaturatingAdd for InscriptionNumber {
    fn saturating_add(&self, v: &Self) -> Self {
        Self(self.0.saturating_add(v.0))
    }
}

impl Debug for InscriptionNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl InscriptionNumber {
    pub const MAX: Self = Self(4_000_000_000);
}

/// Represents outpoint of an inscription. Describes where inscription is located.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Hash)]
#[repr(C)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Outpoint {
    /// Transaction ID
    pub txid: [u8; 32],
    /// Output number in the transaction
    pub vout: u32,
}

#[cfg(feature = "schema")]
impl schemars::JsonSchema for Outpoint {
    fn schema_name() -> std::borrow::Cow<'static, str> {
        "Outpoint".into()
    }

    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
        schemars::json_schema!({
            "type": "string",
            "pattern": "^[0-9a-fA-F]{64}$i\\d+$",
            "description": "SHA-256 transaction hexadecimal hash"
        })
    }
}

impl Debug for Outpoint {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("Outpoint").field(&self.to_string()).finish()
    }
}

impl Outpoint {
    pub fn randomized() -> Self {
        let mut rnd = rand::thread_rng();
        Self {
            txid: rnd.gen(),
            vout: rnd.gen_range(0..100_000),
        }
    }
}
impl std::str::FromStr for Outpoint {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(['i', ':']).collect();
        if parts.len() != 2 {
            anyhow::bail!("Wrong len");
        }
        let txid = hex::decode(parts[0]).anyhow_with("Not hex")?.into_iter().rev().collect_vec();
        let txid: [u8; 32] = txid.try_into().map_err(|_| "Not array of 32").anyhow()?;
        let vout = parts[1].parse().map_err(|_| "Not u32").anyhow()?;

        Ok(Outpoint { txid, vout })
    }
}

impl<'de> serde::Deserialize<'de> for Outpoint {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let v = <String>::deserialize(deserializer)?;
        let mut iter = v.split(['i', ':']);

        let txid = iter.next();
        let vout = iter.next();

        if iter.next().is_some() {
            return Err(serde::de::Error::custom("Many items"));
        }

        match (txid, vout) {
            (Some(txid), Some(vout)) => {
                let txid = hex::decode(txid).map_err(serde::de::Error::custom)?.into_iter().rev().collect_vec();
                let txid: [u8; 32] = txid.try_into().map_err(|_| serde::de::Error::custom("Can't get txid"))?;
                let vout: u32 = vout.parse().map_err(serde::de::Error::custom)?;

                Ok(Self { txid, vout })
            }
            _ => Err(serde::de::Error::custom("Wrong 'Outpoint' format")),
        }
    }
}

impl serde::Serialize for Outpoint {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_str(self)
    }
}

impl std::fmt::Display for Outpoint {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let txid = hex::encode(self.txid.iter().copied().rev().collect_vec());
        let vout = self.vout;
        write!(f, "{txid}i{vout}")
    }
}

impl<'a> NinBytes<'a> for Outpoint {
    type Bytes = [u8; 36];

    fn as_bytes(&'a self) -> Self::Bytes {
        concat_arrays(self.txid, self.vout.to_be_bytes())
    }

    fn from_bytes(b: Self::Bytes) -> Self {
        let txid: [u8; 32] = b[..32].try_into().unwrap();
        let vout = u32::from_be_bytes(b[32..].try_into().unwrap());
        Self { txid, vout }
    }
}

#[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy, serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, derive_more::From, derive_more::Display, Hash, PartialOrd, Ord)]
#[repr(C)]
#[serde(transparent)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema), schemars(transparent))]
pub struct Genesis(pub Outpoint);

#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, derive_more::From, derive_more::Display, Hash, bytemuck::Pod, bytemuck::Zeroable)]
#[serde(transparent)]
#[repr(C)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AddressHash(pub Hash256);

#[deprecated(note = "Address was renamed to AddressHash to avoid confusion")]
pub type Address = AddressHash;

impl AddressHash {
    pub fn from_original(addr: impl AsRef<str>) -> Self {
        Self(addr.as_ref().hash_256())
    }
}

#[derive(Clone, PartialEq, Eq, Debug, Copy, PartialOrd, Ord, bytemuck::Zeroable)]
#[repr(C)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Location {
    pub genesis: Genesis,
    pub offset: u64,
}

#[cfg(feature = "schema")]
impl schemars::JsonSchema for Location {
    fn schema_name() -> std::borrow::Cow<'static, str> {
        "Location".into()
    }

    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
        schemars::json_schema!({
            "type": "string",
            "pattern": "^[0-9a-fA-F]{64}i\\d+$",
            "description": "Genesis of inscription and it's offest"
        })
    }
}

impl std::fmt::Display for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let outpoint = self.genesis.to_string();
        let offest = self.offset;
        write!(f, "{outpoint}i{offest}")
    }
}
impl<'de> serde::Deserialize<'de> for Location {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let v = <String>::deserialize(deserializer)?;
        let mut iter = v.split(['i', ':']);

        let txid = iter.next();
        let vout = iter.next();
        let offset = iter.next();

        if iter.next().is_some() {
            return Err(serde::de::Error::custom("Many items"));
        }

        match (txid, vout, offset) {
            (Some(txid), Some(vout), Some(offset)) => {
                let offset: u64 = offset.parse().map_err(serde::de::Error::custom)?;
                let outpoint = Outpoint::from_str(&format!("{txid}i{vout}")).map_err(serde::de::Error::custom)?;

                Ok(Self { genesis: Genesis(outpoint), offset })
            }
            _ => Err(serde::de::Error::custom("Wrong 'Location' format")),
        }
    }
}

impl serde::Serialize for Location {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_str(self)
    }
}

#[derive(Debug, Clone, Copy, derive_more::From)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum InscriptionRef {
    Genesis(Genesis),
    Number(InscriptionNumber),
}

impl<'de> serde::Deserialize<'de> for InscriptionRef {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let v = <String>::deserialize(deserializer)?;
        if let Ok(x) = v.parse::<u64>() {
            return Ok(Self::Number(InscriptionNumber(x)));
        }
        if let Ok(x) = v.parse::<Outpoint>() {
            return Ok(Self::Genesis(Genesis(x)));
        }

        Err(serde::de::Error::custom("InscriptionRef accepts only genesis or number"))
    }
}

impl serde::Serialize for InscriptionRef {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let s = match self {
            InscriptionRef::Genesis(x) => x.to_string(),
            InscriptionRef::Number(x) => x.0.to_string(),
        };
        serializer.collect_str(&s)
    }
}