use minicbor::{
data::Tag, data::Type, decode::Error, encode::Write, Decode, Decoder, Encode, Encoder,
};
#[doc(alias("crypto-coininfo"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CoinInfo {
pub coin_type: CoinType,
pub network: u64,
}
impl CoinInfo {
pub const TAG: Tag = Tag::new(40305);
pub const NETWORK_MAINNET: u64 = 0;
pub const NETWORK_BTC_TESTNET: u64 = 1;
pub const BTC_MAINNET: Self = Self {
coin_type: CoinType::BTC,
network: Self::NETWORK_MAINNET,
};
pub const fn new(coin_type: CoinType, network: u64) -> Self {
Self { coin_type, network }
}
pub fn is_default(&self) -> bool {
self.coin_type == CoinType::BTC && self.network == Self::NETWORK_MAINNET
}
}
impl<'b, C> Decode<'b, C> for CoinInfo {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
let mut coin_type = None;
let mut network = None;
let mut len = d.map()?;
loop {
match len {
Some(0) => break,
Some(n) => len = Some(n - 1),
None => {
if d.datatype()? == Type::Break {
break;
}
}
}
match d.u32()? {
1 => coin_type = Some(CoinType::decode(d, ctx)?),
2 => network = Some(d.u64()?),
_ => return Err(Error::message("unknown map entry")),
}
}
Ok(Self {
coin_type: coin_type.unwrap_or(CoinType::BTC),
network: network.unwrap_or(0),
})
}
}
impl<C> Encode<C> for CoinInfo {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
let is_not_default_coin_type = self.coin_type != CoinType::BTC;
let is_not_default_network = self.network != 0;
let len = is_not_default_coin_type as u64 + is_not_default_network as u64;
e.map(len)?;
if is_not_default_coin_type {
e.u8(1)?;
self.coin_type.encode(e, ctx)?;
}
if is_not_default_network {
e.u8(2)?.u64(self.network)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct CoinType(pub(crate) u32);
impl CoinType {
pub const BTC: Self = CoinType(0x00);
pub fn new(value: u32) -> Self {
Self(value)
}
pub fn get(self) -> u32 {
self.0
}
}
impl<'b, C> Decode<'b, C> for CoinType {
fn decode(d: &mut Decoder<'b>, _ctx: &mut C) -> Result<Self, Error> {
let n = d.u32()?;
if n >= 1 << 31 {
return Err(Error::message("coin type out of range"));
}
Ok(CoinType(n))
}
}
impl<C> Encode<C> for CoinType {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
_ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
e.u32(self.get())?;
Ok(())
}
}
impl From<u32> for CoinType {
fn from(n: u32) -> Self {
CoinType(n)
}
}
impl From<CoinType> for u32 {
fn from(coin_type: CoinType) -> Self {
coin_type.get()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crypto_coininfo_roundtrip() {
let crypto_coininfo = CoinInfo::BTC_MAINNET;
let cbor = minicbor::to_vec(&crypto_coininfo).unwrap();
let decoded = minicbor::decode(&cbor).unwrap();
assert_eq!(crypto_coininfo, decoded);
}
#[test]
fn test_coin_type_roundtrip() {
let coin_type = CoinType::BTC;
let cbor = minicbor::to_vec(coin_type).unwrap();
assert_eq!(cbor, &[0x00]);
let decoded = minicbor::decode(&cbor).unwrap();
assert_eq!(coin_type, decoded);
}
}