secretrs 0.1.4

An extension of `cosmrs` for Secret.
Documentation
use base64::prelude::{Engine, BASE64_STANDARD};
use color_eyre::{eyre::Error, owo_colors::OwoColorize, Result};
use regex::Regex;
use serde::{Deserialize, Serialize};

use secretrs::{
    grpc_clients::{ComputeQueryClient, RegistrationQueryClient},
    proto::secret::compute::v1beta1::{QueryByContractAddressRequest, QuerySecretContractRequest},
    utils::encryption::EnigmaUtils,
};

const GRPC_URL: &str = "http://grpc.testnet.secretsaturn.net:9090";
const CONTRACT_ADDRESS: &str = "secret19gtpkk25r0c36gtlyrc6repd3q52ngmkpfszw3";

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
    color_eyre::install()?;

    println!("\n{}", "Registration Module".underline().blue());
    println!("Creating `registration` query client...");
    let mut secret_registration = RegistrationQueryClient::connect(GRPC_URL).await?;

    let enclave_key_bytes = secret_registration.tx_key(()).await?.into_inner().key;
    let enclave_key = hex::encode(&enclave_key_bytes);
    println!("Enclave Public Key => {:>4}", enclave_key.bright_blue());

    println!("\n{}", "Compute Module".underline().blue());
    println!("Creating `compute` query client...");
    let mut secret_compute = ComputeQueryClient::connect(GRPC_URL).await?;

    let request = QueryByContractAddressRequest {
        contract_address: CONTRACT_ADDRESS.to_string(),
    };

    let code_hash = secret_compute
        .code_hash_by_contract_address(request)
        .await?
        .into_inner()
        .code_hash;

    let query = QueryMsg::TokenInfo {};
    println!("Query => {}", serde_json::to_string(&query)?.green());

    let mut io_key = [0u8; 32];
    io_key.copy_from_slice(&enclave_key_bytes[0..32]);

    let enigma_utils = EnigmaUtils::from_io_key(None, io_key);
    let encrypted = enigma_utils.encrypt(&code_hash, &query)?;
    let nonce = encrypted.nonce();
    let query = encrypted.into_inner();

    let display_request = format!(
        "QuerySecretContractRequest {{ contract_address: \"{}\", query: \"{}\" }}",
        CONTRACT_ADDRESS,
        BASE64_STANDARD.encode(&query)
    );
    println!("Request => {}", display_request.green());

    let request = QuerySecretContractRequest {
        contract_address: CONTRACT_ADDRESS.to_string(),
        query,
    };

    let response = match secret_compute.query_secret_contract(request).await {
        Ok(response) => {
            let response = response.into_inner();
            let decrypted_bytes = enigma_utils.decrypt(&nonce, &response.data)?;
            let decrypted_b64_string = String::from_utf8(decrypted_bytes)?;
            let decoded_bytes = BASE64_STANDARD.decode(decrypted_b64_string)?;
            let data = String::from_utf8(decoded_bytes)?;
            println!("Response => {}", data.green());
            assert_eq!(
                data.trim(),
                r#"{"token_info":{"name":"Amber","symbol":"AMBER","decimals":6,"total_supply":"8888000000"}}"#
            );

            Ok(data)
        }
        Err(status) => {
            let error_message = status.message();

            let re = Regex::new(r"encrypted: (.*?):").unwrap();

            if let Some(caps) = re.captures(error_message) {
                let encrypted_bytes = BASE64_STANDARD.decode(&caps[1])?;
                let decrypted_bytes = enigma_utils.decrypt(&nonce, &encrypted_bytes)?;
                let decrypted_string = String::from_utf8(decrypted_bytes)?;
                Err(Error::msg(decrypted_string))
            } else {
                Err(Error::msg(error_message.to_string()))
            }
        }
    }?;

    let deserialized: QueryAnswer = serde_json::from_str(response.trim())?;
    println!("Deserialized Response => {:?}", deserialized.green());

    Ok(())
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    TokenInfo {},
    MemberCode { address: String, key: String },
    ValidCodes { codes: Vec<String> },
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum QueryAnswer {
    TokenInfo {
        name: String,
        symbol: String,
        decimals: u8,
        total_supply: String,
    },
    MemberCode {
        code: String,
    },
    ValidCodes {
        codes: Vec<String>,
    },
    ViewingKeyError {
        msg: String,
    },
}