#![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; #[macro_use] extern crate rocket_contrib; #[macro_use] extern crate serde_derive; extern crate reqwest; extern crate qrcode_generator; mod data_types; use rocket::http::RawStr; use rocket::response::Redirect; use rocket_contrib::json::JsonValue; use rocket_contrib::templates::Template; use rocket_contrib::serve::StaticFiles; use reqwest::blocking::{RequestBuilder, Client}; use reqwest::Error; use qrcode_generator::QrCodeEcc; use std::env; use data_types::*; fn build_rpc(method: &str, raw_data: Option, raw: bool) -> RequestBuilder { let http_client = Client::new(); let daemon_uri = env::var("DAEMON_URI").unwrap(); match raw { true => { let uri = format!("{}/{}", &daemon_uri, &method); if let None = raw_data { http_client.post(&uri) } else { http_client.post(&uri).json(&raw_data) } }, false => { let uri = format!("{}/json_rpc", &daemon_uri); if let None = raw_data { http_client.post(&uri) } else { http_client.post(&uri).json(&raw_data) } } } } #[get("/block/hash/")] fn get_block_by_hash(block_hash: String) -> Template { let payload: JsonValue = json!({ "method": "get_block", "params": { "hash": block_hash } }); let res: GetBlock = build_rpc( &"get_block", Some(payload), false ).send().unwrap().json().unwrap(); Template::render("block", &res.result) } #[get("/block/height/")] fn get_block_by_height(block_height: String) -> Template { let payload: JsonValue = json!({ "method": "get_block", "params": { "height": block_height } }); let res: GetBlock = build_rpc( &"get_block", Some(payload), false ).send().unwrap().json().unwrap(); Template::render("block", &res.result) } #[get("/receipt?
&&")] fn get_tx_receipt(address: String, tx_hash: String, tx_key: String) -> Template { let payload: JsonValue = json!({ "method": "check_tx_key", "params": { "address": address, "txid": tx_hash, "tx_key": tx_key } }); let http_client = Client::new(); let wallet_uri = env::var("WALLET_URI").unwrap(); let uri = format!("{}/json_rpc", &wallet_uri); let res: CheckTxKeyResponse = http_client.post(&uri).json(&payload) .send().unwrap().json().unwrap(); let context = json!({ "res": &res.result, "tx_hash": tx_hash, "address": address }); Template::render("receipt", context) } #[get("/transaction/")] fn get_transaction_by_hash(tx_hash: String) -> Template { let params: JsonValue = json!({ "txs_hashes": [&tx_hash], "decode_as_json": true }); let mut res: GetTransactions = build_rpc( &"get_transactions", Some(params), true ).send().unwrap().json().unwrap(); if res.txs.len() > 0 { for f in &mut res.txs { f.process(); }; }; let context = json!({ "tx_info": res.txs, "tx_hash": tx_hash, "debug": res.clone() }); Template::render("transaction", context) } #[get("/address/?&&&")] fn show_wallet_address( wallet_address: String, tx_amount: Option, tx_description: Option, recipient_name: Option, tx_payment_id: Option ) -> Template { let qr_data: QRData = QRData { tx_amount: tx_amount.unwrap_or("".to_string()), tx_description: tx_description.unwrap_or("".to_string()), recipient_name: recipient_name.unwrap_or("".to_string()), tx_payment_id: tx_payment_id.unwrap_or("".to_string()) }; let address_uri = format!( "wownero:{}&tx_amount={}&tx_description={}&recipient_name={}&tx_payment_id={}", wallet_address, qr_data.tx_amount, qr_data.tx_description, qr_data.recipient_name, qr_data.tx_payment_id ); let qr_code: String = qrcode_generator::to_svg_to_string(address_uri, QrCodeEcc::Low, 256, None) .unwrap(); let qr_code: String = base64::encode(qr_code); let context: JsonValue = json!({ "qr_code": qr_code, "qr_data": qr_data, "wallet_address": wallet_address }); Template::render("address", context) } #[get("/search?")] fn search(value: &RawStr) -> Redirect { // This search implementation is not ideal but it works. // We basically check the length of the search value and // attempt to redirect to the appropriate route. let sl: usize = value.len(); if sl == 0 { return Redirect::found(uri!(index)); } else if sl < 8 { // Less than 8 characters is probably a block height. If it can // be parsed as valid u32 then redirect to `get_block_by_height`, // otherwise redirect to the error response. match value.parse::() { Ok(_) => return Redirect::found(uri!(get_block_by_height: value.as_str())), Err(_) => return Redirect::found(uri!(error)) } } else if sl == 64 { // Equal to 64 characters is probably a hash; block or tx. // For this we attempt to query for a block with // given hash. If we don't receive a valid/expected // response then we attempt to query for a transaction hash. // If neither works then redirect to error response. let block_hash_params: JsonValue = json!({ "method": "get_block", "params": { "hash": value.to_string() } }); let check_valid_block_hash: Result = build_rpc( &"get_block", Some(block_hash_params), false ).send().unwrap().json(); match check_valid_block_hash { Ok(_) => return Redirect::found(uri!(get_block_by_hash: value.as_str())), Err(_) => { let tx_hash_params: JsonValue = json!({"txs_hashes": [&value.as_str()]}); let check_valid_tx_hash: Result = build_rpc( &"get_transactions", Some(tx_hash_params), true ).send().unwrap().json(); match check_valid_tx_hash { Ok(_) => return Redirect::found(uri!(get_transaction_by_hash: value.as_str())), Err(_) => return Redirect::found(uri!(error)) } } } } else if sl == 97 { // Equal to 97 characters is probably a wallet address. // For this let's just redirect to the `show_wallet_address` route. return Redirect::found(uri!(show_wallet_address: value.as_str(), "", "", "", "")) } else if sl == 107 { // Equal to 107 characters is probably an integrated address. // For this let's just redirect to the `show_wallet_address` route. return Redirect::found(uri!(show_wallet_address: value.as_str(), "", "", "", "")) } else { // Anything else hasn't been implemented yet // so redirect to error response. return Redirect::found(uri!(error)); }; } #[get("/")] fn index() -> Template { let daemon_uri = env::var("DAEMON_URI").unwrap(); let daemon_info: GetInfoResult = build_rpc( &"get_info", None, true ).send().unwrap().json().unwrap(); let tx_pool: GetTransactionPool = build_rpc( &"get_transaction_pool", None, true ).send().unwrap().json().unwrap(); let mut pool_txs: Vec = tx_pool.transactions.unwrap_or(vec![]); for f in &mut pool_txs { f.process(); }; let context: JsonValue = json!({ "daemon_info": daemon_info, "tx_pool_txs": pool_txs, "daemon_uri": daemon_uri }); Template::render("index", context) } #[get("/error", )] fn error() -> JsonValue { json!({ "status": "error", "reason": "There was an error while searching the provided values." }) } #[catch(404)] fn not_found() -> JsonValue { json!({ "status": "error", "reason": "Resource was not found." }) } fn main() { let env_url = env::var("DAEMON_URI"); match env_url { Ok(_) => { rocket::ignite() .mount("/", routes![ index, search, // show_tx_pool, get_block_by_height, get_block_by_hash, get_transaction_by_hash, get_tx_receipt, show_wallet_address, error ]) .mount("/static", StaticFiles::from("./static")) .register(catchers![not_found]) .attach(Template::fairing()) .launch(); }, Err(_) => panic!("Environment variable `DAEMON_URI` not provided.") } }