libmonero 0.1.9

Batteries-included Monero Library
Documentation
/*
 * This file is part of Monume's library libmonero
 *
 * Copyright (c) 2023-2024, Monume (monume.xyz)
 * All Rights Reserved
 * The code is distributed under MIT license, see LICENSE file for details.
 * Generated by Monume
 *
 */

use std::io::Cursor;

use super::{block::{Block, BlockDetailsJSON, BlockHeader, EcdhInfo, Gen, KeyRawTx, MinerTxInfo, RawTx, RctSignatures, RctsigPrunable, TaggedKey, Target, Vin, VinRawTx, Vout, BPP, CLSAG}, nodes::DaemonNode};

fn get_json_rpc_url(node: DaemonNode) -> String {
    match node.tls {
        true => format!("https://{}:{}/json_rpc", node.url, node.port),
        false => format!("http://{}:{}/json_rpc", node.url, node.port),
    }
}

fn get_rpc_url(node: DaemonNode) -> String {
    match node.tls {
        true => format!("https://{}:{}", node.url, node.port),
        false => format!("http://{}:{}", node.url, node.port),
    }
}

/// Gets the block from the given daemon by its height
/// Returns the block as a Block struct if succesfull
/// Returns an error message if not succesfull
/// 
/// Example:
/// ```
/// use libmonero::blocks::get_block_from_height;
/// use libmonero::blocks::DaemonNode;
/// 
/// let block = get_block_from_height(3000000, DaemonNode::cake_wallet_default()).unwrap();
/// println!("Block hash: {}", block.block_header.hash);
/// ```
pub fn get_block_from_height(block_height: u64, node: DaemonNode) -> Result<Block, String> {
    let rpc_url = get_json_rpc_url(node);
    let response = ureq::post(&rpc_url)
        .set("Content-Type", "application/json")
        .send_json(ureq::json!({
            "jsonrpc": "2.0",
            "id": "0",
            "method": "get_block",
            "params": {
                "height": block_height
            }
        }));
    if let Err(e) = response.as_ref() {
        return Err(format!("Error while getting the block from daemon: {}", e));
    }
    let response:  serde_json::Value = response.unwrap().into_json().unwrap();
    let block_header = BlockHeader {
        block_size: response["result"]["block_header"]["block_size"].as_u64().unwrap(),
        block_weight: response["result"]["block_header"]["block_weight"].as_u64().unwrap(),
        cumulative_difficulty: response["result"]["block_header"]["cumulative_difficulty"].as_u64().unwrap(),
        cumulative_difficulty_top64: response["result"]["block_header"]["cumulative_difficulty_top64"].as_u64().unwrap(),
        depth: response["result"]["block_header"]["depth"].as_u64().unwrap(),
        difficulty: response["result"]["block_header"]["difficulty"].as_u64().unwrap(),
        difficulty_top64: response["result"]["block_header"]["difficulty_top64"].as_u64().unwrap(),
        hash: response["result"]["block_header"]["hash"].as_str().unwrap().to_string(),
        height: response["result"]["block_header"]["height"].as_u64().unwrap(),
        long_term_weight: response["result"]["block_header"]["long_term_weight"].as_u64().unwrap(),
        major_version: response["result"]["block_header"]["major_version"].as_u64().unwrap(),
        miner_tx_hash: response["result"]["block_header"]["miner_tx_hash"].as_str().unwrap().to_string(),
        minor_version: response["result"]["block_header"]["minor_version"].as_u64().unwrap(),
        nonce: response["result"]["block_header"]["nonce"].as_u64().unwrap(),
        num_txes: response["result"]["block_header"]["num_txes"].as_u64().unwrap(),
        orphan_status: response["result"]["block_header"]["orphan_status"].as_bool().unwrap(),
        pow_hash: response["result"]["block_header"]["pow_hash"].as_str().unwrap().to_string(),
        prev_hash: response["result"]["block_header"]["prev_hash"].as_str().unwrap().to_string(),
        reward: response["result"]["block_header"]["reward"].as_u64().unwrap(),
        timestamp: response["result"]["block_header"]["timestamp"].as_u64().unwrap(),
        wide_cumulative_difficulty: response["result"]["block_header"]["wide_cumulative_difficulty"].as_str().unwrap().to_string(),
        wide_difficulty: response["result"]["block_header"]["wide_difficulty"].as_str().unwrap().to_string(),
    };
    let json = response["result"]["json"].as_str().unwrap().to_string();
    let parsed_json: serde_json::Value = serde_json::from_str(&json).unwrap_or(serde_json::Value::Null);
    if parsed_json.is_null() {
        return Err("Error while parsing the block JSON".to_string());
    }
    let vin_array = parsed_json["miner_tx"]["vin"].as_array().unwrap();
    let mut vin_vec: Vec<Vin> = Vec::new();
    for vin in vin_array {
        vin_vec.push(Vin {
            gen: Gen {
                height: vin["gen"]["height"].as_u64().unwrap_or(0),
            }
        });
    };
    let vout_array = parsed_json["miner_tx"]["vout"].as_array().unwrap();
    let mut vout_vec: Vec<Vout> = Vec::new();
    for vout in vout_array {
        vout_vec.push(Vout {
            amount: vout["amount"].as_u64().unwrap_or(0),
            target: Target {
                tagged_key: TaggedKey {
                    key: vout["target"]["key"].as_str().unwrap_or("").to_string(),
                    view_tag: vout["target"]["view_tag"].as_str().unwrap_or("").to_string(),
                }
            }
        });
    };
    Ok(Block {
        blob: response["result"]["blob"].as_str().unwrap_or("").to_string(),
        block_header,
        credits: response["result"]["credits"].as_u64().unwrap_or(0),
        json: BlockDetailsJSON {
            major_version: parsed_json["major_version"].as_u64().unwrap_or(0),
            minor_version: parsed_json["minor_version"].as_u64().unwrap_or(0),
            timestamp: parsed_json["timestamp"].as_u64().unwrap_or(0),
            prev_id: parsed_json["prev_id"].as_str().unwrap_or("").to_string(),
            nonce: parsed_json["nonce"].as_u64().unwrap_or(0),
            miner_tx: MinerTxInfo {
                version: parsed_json["miner_tx"]["version"].as_u64().unwrap_or(0),
                unlock_time: parsed_json["miner_tx"]["unlock_time"].as_u64().unwrap_or(0),
                vin: vin_vec,
                vout: vout_vec,
                extra: parsed_json["miner_tx"]["extra"].as_str().unwrap_or("").as_bytes().to_vec(),
                rct_signatures: RctSignatures {
                    type_int: parsed_json["miner_tx"]["rct_signatures"]["type"].as_u64().unwrap_or(0),
                    txn_fee: 0,
                    ecdh_info: Vec::new(),
                    out_pk: Vec::new(),
                }
            },
            tx_hashes: parsed_json["tx_hashes"].as_array().unwrap().to_vec().iter().map(|x| x.as_str().unwrap().to_string()).collect(),
        },
        miner_tx_hash: response["result"]["miner_tx_hash"].as_str().unwrap_or("").to_string(),
        status: response["result"]["status"].as_str().unwrap_or("ERROR").to_string(),
        top_hash: response["result"]["top_hash"].as_str().unwrap_or("").to_string(),
        untrusted: response["result"]["untrusted"].as_bool().unwrap_or(false),
    })
}

/// Gets the current height of the blockchain from the given daemon
/// Returns the height as a u64 if succesfull
/// Returns an error message if not succesfull
/// 
/// Example:
/// ```
/// use libmonero::blocks::get_height;
/// use libmonero::blocks::DaemonNode;
/// 
/// let height = get_height(DaemonNode::cake_wallet_default()).unwrap();
/// println!("Current height: {}", height);
/// ```
pub fn get_height(node: DaemonNode) -> Result<u64, String> {
    let rpc_url = get_rpc_url(node);
    let reader = Cursor::new(Vec::new());
    let response = ureq::get(format!("{}/get_height", &rpc_url).as_str())
        .set("Content-Type", "application/json").send(reader);
    if let Err(e) = response.as_ref() {
        return Err(format!("Error while getting the block count (height) from daemon: {}", e));
    }
    let response:  serde_json::Value = response.unwrap().into_json().unwrap_or(serde_json::Value::Null);
    if response.is_null() {
        return Err("Error while parsing the block count (height) JSON".to_string());
    }
    Ok(response["height"].as_u64().unwrap_or(0))
}

/// Gets the transaction from the given daemon by its hash
/// Returns the transaction as a RawTx struct if succesfull
/// Returns an error message if not succesfull
/// 
/// Example:
/// ```
/// use libmonero::blocks::get_transaction_from_hash;
/// use libmonero::blocks::DaemonNode;
/// 
/// let tx_hash = "e4516854a5984eaf5f8750ac7af41d1e0b2c602a2297a673001e8c0af88eba11";
/// let tx = get_transaction_from_hash(tx_hash.to_string(), DaemonNode::cake_wallet_default()).unwrap();
/// println!("Unlock time: {}", tx.unlock_time);
/// ```
pub fn get_transaction_from_hash(hash: String, node: DaemonNode) -> Result<RawTx, String> {
    let rpc_url = format!("{}/get_transactions", get_rpc_url(node));
    let response = ureq::post(&rpc_url)
        .set("Content-Type", "application/json")
        .send_json(ureq::json!({
            "txs_hashes": [hash],
            "decode_as_json": true,
        }));
    if let Err(e) = response.as_ref() {
        return Err(format!("Error while getting the transaction from daemon: {}", e));
    }
    let response: serde_json::Value = response.unwrap().into_json().unwrap_or(serde_json::Value::Null);
    if response.is_null() {
        return Err("Error while parsing the transaction JSON".to_string());
    }
    let json_part = response["txs"][0]["as_json"].as_str().unwrap_or("").to_string();
    if json_part.is_empty() {
        return Err("Error while getting the as_json part".to_string());
    }
    let json_final = serde_json::from_str(&json_part).unwrap_or(serde_json::Value::Null);
    if json_final.is_null() {
        return Err("Error while parsing the as_json part".to_string());
    }
    let mut vin_raw_tx = Vec::new();
    for vin in json_final["vin"].as_array().unwrap().iter() {
        vin_raw_tx.push(VinRawTx {
            key: KeyRawTx {
                amount: vin["key"]["amount"].as_u64().unwrap_or(0),
                key_offsets: vin["key"]["key_offsets"].as_array().unwrap_or(&Vec::new()).to_vec().iter().map(|x| x.as_u64().unwrap_or(0)).collect(),
                k_image: vin["key"]["k_image"].as_str().unwrap_or("").to_string(),
            }
        });
    }
    let mut vout_raw_tx = Vec::new();
    for vout in json_final["vout"].as_array().unwrap().iter() {
        vout_raw_tx.push(Vout {
            amount: vout["amount"].as_u64().unwrap_or(0),
            target: Target {
                tagged_key: TaggedKey {
                    key: vout["target"]["tagged_key"]["key"].as_str().unwrap_or("").to_string(),
                    view_tag: vout["target"]["tagged_key"]["view_tag"].as_str().unwrap_or("").to_string(),
                }
            }
        });
    }
    let mut ecdh_raw_tx = Vec::new();
    for ecdh in json_final["rct_signatures"]["ecdhInfo"].as_array().unwrap().iter() {
        ecdh_raw_tx.push(EcdhInfo {
            trunc_amount: ecdh["trunc_amount"].as_str().unwrap_or("").to_string(),
        });
    }
    let mut bpp_raw_tx = Vec::new();
    for bpp in json_final["rctsig_prunable"]["bpp"].as_array().unwrap().iter() {
        bpp_raw_tx.push(BPP {
            A: bpp["A"].as_str().unwrap_or("").to_string(),
            A1: bpp["A1"].as_str().unwrap_or("").to_string(),
            B: bpp["B"].as_str().unwrap_or("").to_string(),
            r1: bpp["r1"].as_str().unwrap_or("").to_string(),
            s1: bpp["s1"].as_str().unwrap_or("").to_string(),
            d1: bpp["d1"].as_str().unwrap_or("").to_string(),
            L: {
                let mut l = Vec::new();
                for l_part in bpp["L"].as_array().unwrap_or(&Vec::new()).to_vec().iter() {
                    l.push(l_part.as_str().unwrap_or("").to_string());
                }
                l
            },
            R: {
                let mut r = Vec::new();
                for r_part in bpp["R"].as_array().unwrap_or(&Vec::new()).to_vec().iter() {
                    r.push(r_part.as_str().unwrap_or("").to_string());
                }
                r
            }
        });
    }
    let mut clsags_raw_tx = Vec::new();
    for clsag in json_final["rctsig_prunable"]["CLSAGs"].as_array().unwrap().iter() {
        clsags_raw_tx.push(CLSAG {
            s: {
                let mut s = Vec::new();
                for s_part in clsag["s"].as_array().unwrap_or(&Vec::new()).to_vec().iter() {
                    s.push(s_part.as_str().unwrap_or("").to_string());
                }
                s
            },
            c1: clsag["c1"].as_str().unwrap_or("").to_string(),
            D: clsag["D"].as_str().unwrap_or("").to_string(),
        });
    }
    Ok(RawTx {
        version: json_final["version"].as_u64().unwrap_or(0),
        unlock_time: json_final["unlock_time"].as_u64().unwrap_or(0),
        vin: vin_raw_tx,
        vout: vout_raw_tx,
        extra: json_final["extra"].as_str().unwrap_or("").as_bytes().to_vec(),
        rct_signatures: RctSignatures {
            type_int: json_final["rct_signatures"]["type"].as_u64().unwrap_or(0),
            txn_fee: json_final["rct_signatures"]["txnFee"].as_u64().unwrap_or(0),
            ecdh_info: ecdh_raw_tx,
            out_pk: json_final["rct_signatures"]["outPk"].as_array().unwrap().to_vec().iter().map(|x| x.as_str().unwrap_or("").to_string()).collect(),
        },
        rctsig_prunable: RctsigPrunable {
            nbp: json_final["rctsig_prunable"]["nbp"].as_u64().unwrap_or(0),
            bpp: bpp_raw_tx,
            CLSAGs: clsags_raw_tx,
            pseudo_outs: json_final["rctsig_prunable"]["pseudoOuts"].as_array().unwrap().to_vec().iter().map(|x| x.as_str().unwrap_or("").to_string()).collect(),
        }
    })
}