smplx-sdk 0.0.5

Simplex sdk to simplify the development with simplicity
Documentation
use std::str::FromStr;

use electrsd::bitcoind::bitcoincore_rpc::{Auth, Client, RpcApi};

use serde_json::Value;

use simplicityhl::elements::{Address, AssetId, Txid};
use simplicityhl::simplicity::bitcoin;

use super::error::RpcError;

use crate::utils::sat2btc;

/// A lightweight wrapper around the standard `bitcoincore_rpc` `Client` providing Elements-specific functionality.
#[derive(Debug)]
pub struct ElementsRpc {
    /// The underlying JSON-RPC client connected to the Elements node.
    pub inner: Client,
    /// The authentication credentials used.
    pub auth: Auth,
    /// The URL endpoint of the node.
    pub url: String,
}

impl ElementsRpc {
    /// Creates a new `ElementsRpc` client.
    ///
    /// # Errors
    /// Returns an `RpcError` if it fails to initialize the connection or if a liveness `ping` fails.
    pub fn new(url: String, auth: Auth) -> Result<Self, RpcError> {
        let inner = Client::new(url.as_str(), auth.clone())?;
        inner.ping()?;

        Ok(Self { inner, auth, url })
    }

    /// Requests a new wallet address from the node, mapped to the provided label.
    ///
    /// # Errors
    /// Returns an `RpcError` if the node fails to generate an address or yields an invalid string payload.
    ///
    /// # Panics
    /// Panics if the returned JSON value is not a string, or if the string cannot be parsed into a valid `Address`.
    pub fn get_new_address(&self, label: &str) -> Result<Address, RpcError> {
        const METHOD: &str = "getnewaddress";

        let addr: Value = self.inner.call(METHOD, &[label.into(), "bech32".to_string().into()])?;

        Ok(Address::from_str(addr.as_str().unwrap()).unwrap())
    }

    /// Instructs the node to transfer funds directly to the target address.
    ///
    /// # Errors
    /// Returns an `RpcError` if the node returns an error or returns an invalid `Txid` payload.
    ///
    /// # Panics
    /// Panics if the satoshi amount cannot be converted to a valid BTC amount, if the returned
    ///  JSON value is not a string, or if the string cannot be parsed into a valid `Txid`.
    pub fn send_to_address(&self, address: &Address, satoshi: u64, asset: Option<AssetId>) -> Result<Txid, RpcError> {
        const METHOD: &str = "sendtoaddress";

        let btc = sat2btc(satoshi);
        let btc = bitcoin::amount::Amount::from_btc(btc)
            .unwrap()
            .to_string_in(bitcoin::amount::Denomination::Bitcoin);

        let r = match asset {
            Some(asset) => self.inner.call::<Value>(
                METHOD,
                &[
                    address.to_string().into(),
                    btc.into(),
                    "".into(),
                    "".into(),
                    false.into(),
                    false.into(),
                    1.into(),
                    "UNSET".into(),
                    false.into(),
                    asset.to_string().into(),
                ],
            )?,
            None => self
                .inner
                .call::<Value>(METHOD, &[address.to_string().into(), btc.into()])?,
        };

        Ok(Txid::from_str(r.as_str().unwrap()).unwrap())
    }

    /// Instructs the node to rescan the block chain for missed wallet transactions.
    ///
    /// # Errors
    /// Returns an `RpcError` if the node fails the RPC call.
    pub fn rescan_blockchain(&self, start: Option<u64>, stop: Option<u64>) -> Result<(), RpcError> {
        const METHOD: &str = "rescanblockchain";

        let mut args = Vec::with_capacity(2);

        if start.is_some() {
            args.push(start.into());
        }

        if stop.is_some() {
            args.push(stop.into());
        }

        self.inner.call::<Value>(METHOD, &args)?;

        Ok(())
    }

    /// Mines a specified number of new blocks mapping generated rewards to a newly generated wallet address.
    ///
    /// # Errors
    /// Returns an `RpcError` if generating the address or calling the `generatetoaddress` RPC command fails.
    pub fn generate_blocks(&self, block_num: u64) -> Result<(), RpcError> {
        const METHOD: &str = "generatetoaddress";

        let address = self.get_new_address("")?.to_string();
        self.inner.call::<Value>(METHOD, &[block_num.into(), address.into()])?;

        Ok(())
    }

    /// Instructs the node to sweep the `initialfreecoins` balance generated by standard regtest genesis blocks.
    ///
    /// # Errors
    /// Returns an `RpcError` if the `getnewaddress` or `sendtoaddress` RPC commands fail.
    pub fn sweep_initialfreecoins(&self) -> Result<(), RpcError> {
        const METHOD: &str = "sendtoaddress";

        let address = self.get_new_address("")?;
        self.inner.call::<Value>(
            METHOD,
            &[
                address.to_string().into(),
                "21".into(),
                "".into(),
                "".into(),
                true.into(),
            ],
        )?;

        Ok(())
    }

    /// Retrieves the current block chain tip height.
    ///
    /// # Errors
    /// Returns an `RpcError` if the node call fails or yields a JSON structure that does not map successfully to a `u64`.
    pub fn height(&self) -> Result<u64, RpcError> {
        const METHOD: &str = "getblockcount";

        self.inner
            .call::<serde_json::Value>(METHOD, &[])?
            .as_u64()
            .ok_or_else(|| RpcError::ElementsRpcUnexpectedReturn(METHOD.into()))
    }
}