electrum_streaming_client 0.4.0

Experimental but sane electrum client by @evanlinjin.
Documentation
use bitcoin::{
    consensus::{deserialize_partial, encode::deserialize_hex},
    hex::FromHex,
};
use serde::{
    de::{Error, Unexpected},
    Deserialize, Deserializer,
};
use serde_json::Value;

use crate::{CowStr, Version, JSONRPC_VERSION_2_0};

pub fn from_consensus_hex<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: bitcoin::consensus::encode::Decodable,
    D: Deserializer<'de>,
{
    let hex_str = String::deserialize(deserializer)?;
    deserialize_hex(&hex_str).map_err(serde::de::Error::custom)
}

pub fn from_cancat_consensus_hex<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
    T: bitcoin::consensus::encode::Decodable,
    D: Deserializer<'de>,
{
    let hex_str = String::deserialize(deserializer)?;
    let data = Vec::<u8>::from_hex(&hex_str).map_err(serde::de::Error::custom)?;

    let mut items = Vec::<T>::new();
    let mut read_start = 0_usize;
    while read_start < data.len() {
        let (item, read_count) =
            deserialize_partial::<T>(&data[read_start..]).map_err(serde::de::Error::custom)?;
        read_start += read_count;
        items.push(item);
    }
    Ok(items)
}

pub fn feerate_opt_from_btc_per_kb<'de, D>(
    deserializer: D,
) -> Result<Option<bitcoin::FeeRate>, D::Error>
where
    D: Deserializer<'de>,
{
    let btc_per_kvb = f32::deserialize(deserializer)?;
    if btc_per_kvb.is_sign_negative() {
        return Ok(None);
    }
    let sat_per_kwu = btc_per_kvb * (100_000_000.0 / 4.0);
    Ok(Some(bitcoin::FeeRate::from_sat_per_kwu(sat_per_kwu as _)))
}

pub fn feerate_from_sat_per_byte<'de, D>(deserializer: D) -> Result<bitcoin::FeeRate, D::Error>
where
    D: Deserializer<'de>,
{
    let sat_per_vb = f32::deserialize(deserializer)?;
    let sat_per_kwu = sat_per_vb * (1000.0 / 4.0);
    Ok(bitcoin::FeeRate::from_sat_per_kwu(sat_per_kwu as _))
}

pub fn weight_from_vb<'de, D>(deserializer: D) -> Result<bitcoin::Weight, D::Error>
where
    D: Deserializer<'de>,
{
    let vb = u64::deserialize(deserializer)?;
    let weight = bitcoin::Weight::from_vb(vb).ok_or(serde::de::Error::custom(
        serde_json::Error::custom("overflow: vb value cannot fit in to WU"),
    ))?;
    Ok(weight)
}

pub fn amount_from_btc<'de, D>(deserializer: D) -> Result<bitcoin::Amount, D::Error>
where
    D: Deserializer<'de>,
{
    let btc = f64::deserialize(deserializer)?;
    bitcoin::Amount::from_btc(btc).map_err(serde::de::Error::custom)
}

pub fn amount_from_sats<'de, D>(deserializer: D) -> Result<bitcoin::Amount, D::Error>
where
    D: Deserializer<'de>,
{
    let sats = u64::deserialize(deserializer)?;
    Ok(bitcoin::Amount::from_sat(sats))
}

pub fn amount_from_maybe_negative_sats<'de, D>(deserializer: D) -> Result<bitcoin::Amount, D::Error>
where
    D: Deserializer<'de>,
{
    let sats = i64::deserialize(deserializer)?.unsigned_abs();
    Ok(bitcoin::Amount::from_sat(sats))
}

pub fn all_inputs_confirmed_bool_from_height<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    match i64::deserialize(deserializer)? {
        0 => Ok(true),
        -1 => Ok(false),
        unexpected => Err(serde::de::Error::custom(serde_json::Error::invalid_value(
            Unexpected::Signed(unexpected),
            &"0 or -1",
        ))),
    }
}

pub fn result<'de, D>(deserializer: D) -> Result<Result<Value, Value>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Debug, Deserialize)]
    pub enum T {
        #[serde(rename(deserialize = "result"))]
        Result(Value),
        #[serde(rename(deserialize = "error"))]
        Error(Value),
    }
    T::deserialize(deserializer).map(|r| match r {
        T::Result(v) => Ok(v),
        T::Error(e) => Err(e),
    })
}

pub fn version<'de, D>(deserializer: D) -> Result<Version, D::Error>
where
    D: Deserializer<'de>,
{
    let version_str = CowStr::deserialize(deserializer)?;
    if version_str != JSONRPC_VERSION_2_0 {
        return Err(serde::de::Error::custom("JSON-RPC version is not 2.0"));
    }
    Ok(Version)
}