tob 0.2.13

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

#[derive(Clone)]
pub struct EthereumRpcClient {
    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(EthereumRpcClient::new(options))),
        _ => Err(crate::error::ClientError::UnsupportedType(client_type)),
    }
}

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

        EthereumRpcClient {
            client: Arc::new(client),
        }
    }
}

#[async_trait]
impl CryptoClient for EthereumRpcClient {
    async fn sync_stats(&self) -> SyncStats {
        let result: serde_json::Value = match self.client.execute("eth_syncing", &[]).await {
            Ok(r) => r,
            //@todo we should just move this entire error handling outside of here once we have results
            //in this lib.
            Err(_) => {
                // info!("Node appeared to not be ready yet. Sleeping for 60 seconds");
                async_std::task::sleep(std::time::Duration::from_secs(60)).await;

                return SyncStats {
                    current_block: 0,
                    syncing: true,
                    sync_item: SyncItem::Block,
                    estimated_sync_item_remaining: 0.0,
                };
            }
        };

        //@todo Eth is so fucked that I don't know if this will return false even if there is more
        //state tries to sync.
        //
        //We need to keep up on this issue as it looks like Eth might at some point introduce an
        //RPC to facilitate with this process.
        //
        //
        //@todo actually looks like they now include "known states and pulled states". Not sure if
        //that can be used to accurately represent this shit, but let's invetigate.
        if let Some(syncing) = result.as_bool() {
            let result: serde_json::Value =
                self.client.execute("eth_blockNumber", &[]).await.unwrap();

            let block =
                u64::from_str_radix(result.as_str().unwrap().trim_start_matches("0x"), 16).unwrap();

            if syncing == false {
                return SyncStats {
                    current_block: block,
                    syncing: false,
                    sync_item: SyncItem::Block,
                    estimated_sync_item_remaining: 0.0,
                };
            }
        }

        //@todo use this to debug the Known and pulled states shit.
        dbg!(&result);

        let current_block = u64::from_str_radix(
            result
                .as_object()
                .unwrap()
                .get("currentBlock")
                .unwrap()
                .as_str()
                .unwrap()
                .trim_start_matches("0x"),
            16,
        )
        .unwrap();

        let tip = u64::from_str_radix(
            result
                .as_object()
                .unwrap()
                .get("highestBlock")
                .unwrap()
                .as_str()
                .unwrap()
                .trim_start_matches("0x"),
            16,
        )
        .unwrap();

        SyncStats {
            current_block,
            sync_item: SyncItem::Block,
            estimated_sync_item_remaining: (tip - current_block) as f64,
            syncing: true,
        }
    }

    async fn wallet_balance(&self, identifier: &str) -> Result<WalletBalance> {
        let balance_hex: String = self
            .client
            .execute("eth_getBalance", &[json!(identifier), json!("latest")])
            .await?;

        //@todo not sure if we want bigint or biguint here...
        let confirmed_balance =
            BigInt::parse_bytes(balance_hex.trim_start_matches("0x").as_bytes(), 16).unwrap();

        let unconfirmed_balance_hex: String = self
            .client
            .execute("eth_getBalance", &[json!(identifier), json!("pending")])
            .await?;

        //@todo not sure if we want bigint or biguint here...
        let unconfirmed_balance = BigInt::parse_bytes(
            unconfirmed_balance_hex.trim_start_matches("0x").as_bytes(),
            16,
        )
        .unwrap();

        Ok(WalletBalance {
            //We need bigints here actually.
            confirmed_balance: Amount {
                value: confirmed_balance,
                currency: Currency {
                    symbol: Symbol::ETH,
                    decimals: 18,
                },
            },
            unconfirmed_balance: Amount {
                value: unconfirmed_balance,
                currency: Currency {
                    symbol: Symbol::ETH,
                    decimals: 18,
                },
            },
        })
    }

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