tob 0.2.13

A helper library that makes calling various nodes easier
Documentation
use crate::client::{Client, ClientType, CryptoClient};
use crate::types::{Amount, ClientOptions, Currency, Symbol, SyncItem, SyncStats, WalletBalance};
use crate::Result;
use async_std::sync::Arc;
use async_trait::async_trait;
use num_bigint::BigInt;
use rpc_json_client::{ClientBuilder, RpcClient};

#[derive(Clone)]
pub struct TurtlRpcClient {
    client: Arc<RpcClient>,
}

pub fn get_client(client_type: ClientType, options: ClientOptions) -> crate::Result<Box<Client>> {
    match client_type {
        //@todo need url to come as well.
        ClientType::RPC => Ok(Box::new(TurtlRpcClient::new(options))),
        _ => Err(crate::error::ClientError::UnsupportedType(client_type)),
    }
}

impl TurtlRpcClient {
    pub fn new(options: ClientOptions) -> Self {
        let client = ClientBuilder::new(&options.url).with_retry();

        let inner_client = if let Some(username) = options.username {
            if let Some(password) = options.password {
                client.with_user(&username, &password).build()
            } else {
                client.build()
            }
        } else {
            client.build()
        };

        TurtlRpcClient {
            client: Arc::new(inner_client),
        }
    }
}

#[async_trait]
impl CryptoClient for TurtlRpcClient {
    //@todo Result here please.
    async fn sync_stats(&self) -> SyncStats {
        let result: serde_json::Value = match self.client.execute("getblockchaininfo", &[]).await {
            Ok(r) => r,
            Err(_e) => {
                //@todo completely revamp this section should probably return a error like I say
                //below, but we want to make sure that the monitor understands to re-try.
                // info!("Node appeared to not be ready yet. Sleeping for 60 seconds");
                async_std::task::sleep(std::time::Duration::from_secs(60)).await;
                //We should return an error here... but oh well
                return SyncStats {
                    current_block: 0,
                    syncing: true,
                    sync_item: SyncItem::VerificationProgress,
                    estimated_sync_item_remaining: 0.0,
                };
            }
        };

        let blocks = result.as_object().unwrap().get("blocks").unwrap();
        //Should be match instead.
        let current_block = blocks.as_u64().unwrap();

        //@todo save the as_object up above and then just reuse that everytime instead of
        //re-unwrapping it everytime.
        //Should be match instead.
        let progress = result
            .as_object()
            .unwrap()
            .get("verificationprogress")
            .unwrap()
            .as_f64()
            .unwrap();

        //@todo we need to double check on this as I'm not 100% sure that if ibd is false then we
        //are forsure not syncing, but I believe this will work for now.
        let ibd = result
            .as_object()
            .unwrap()
            .get("initialblockdownload")
            .unwrap()
            .as_bool()
            .unwrap();

        let (syncing, estimated_sync_item_remaining) = if ibd == false {
            (false, 0.0)
        } else {
            let estimated_sync_item_remaining = 1.0 - progress;

            //Triple 9s
            if estimated_sync_item_remaining < 0.001 {
                (false, 0.0)
            } else {
                (true, estimated_sync_item_remaining)
            }
        };

        SyncStats {
            current_block,
            syncing,
            sync_item: SyncItem::VerificationProgress,
            estimated_sync_item_remaining,
        }
    }

    async fn wallet_balance(&self, _identifier: &str) -> Result<WalletBalance> {
        let result: serde_json::Value = self.client.execute("getwalletinfo", &[]).await?;

        let confirmed_balance = result["balance"].as_f64().unwrap_or(0.0);
        let unconfirmed_balance = result["unconfirmed_balance"].as_f64().unwrap_or(0.0);

        let confirmed_balance = BigInt::from((confirmed_balance * 100_000_000.0).floor() as u64);
        let unconfirmed_balance =
            BigInt::from((unconfirmed_balance * 100_000_000.0).floor() as u64);

        Ok(WalletBalance {
            confirmed_balance: Amount {
                value: confirmed_balance,
                currency: Currency {
                    symbol: Symbol::BTC,
                    decimals: 8,
                },
            },
            unconfirmed_balance: Amount {
                value: unconfirmed_balance,
                currency: Currency {
                    symbol: Symbol::BTC,
                    decimals: 8,
                },
            },
        })
    }

    fn client_type(&self) -> ClientType {
        ClientType::RPC
    }
}