novax-executor 0.2.12

Part of the NovaX framework, this crate facilitates the execution of transactions and queries against smart contracts on the blockchain.
use std::fmt::{Debug, Formatter};
use multiversx_sdk::crypto::private_key::{PRIVATE_KEY_LENGTH, PrivateKey};
use multiversx_sdk::crypto::public_key::PublicKey;
use serde::Serialize;
use serde_json::json;
use sha3::{Digest, Keccak256};
use novax_data::Address;
use crate::error::wallet::WalletError;
use crate::ExecutorError;
use crate::network::transaction::models::send_request::TransactionSendRequest;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignableTransaction {
    pub nonce: u64,
    pub value: String,
    pub receiver: String,
    pub sender: String,
    pub gas_price: u64,
    pub gas_limit: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<String>,
    #[serde(rename = "chainID")]
    pub chain_id: String,
    pub version: u32,
    #[serde(skip_serializing_if = "is_zero")]
    pub options: u32,
}

#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_zero(num: &u32) -> bool {
    *num == 0
}

impl SignableTransaction {
    pub fn into_sendable_transaction(self, wallet: &Wallet) -> TransactionSendRequest {
        let signature = wallet.sign_transaction(&self);

        TransactionSendRequest {
            nonce: self.nonce,
            value: self.value,
            receiver: self.receiver,
            sender: self.sender,
            gas_price: self.gas_price,
            gas_limit: self.gas_limit,
            data: self.data,
            signature,
            chain_id: self.chain_id,
            version: self.version,
            options: self.options,
        }
    }
}

#[derive(Clone, Copy)]
pub struct Wallet(PrivateKey);

impl Wallet {
    pub fn from_private_key(private_key: &str) -> Result<Wallet, ExecutorError> {
        let private_key = PrivateKey::from_hex_str(private_key)
            .map_err(|_| WalletError::InvalidPrivateKey)?;

        Ok(Wallet(private_key))
    }

    pub fn from_pem_file(file_path: &str) -> Result<Self, ExecutorError> {
        let contents = std::fs::read_to_string(file_path)
            .map_err(|_| WalletError::InvalidPemFile)?;

        Self::from_pem_file_contents(contents)
    }

    pub fn from_pem_file_contents(contents: String) -> Result<Self, ExecutorError> {
        let x = pem::parse(contents)
            .map_err(|_| WalletError::InvalidPemFile)?;
        let x = x.contents()[..PRIVATE_KEY_LENGTH].to_vec();
        let priv_key_str = std::str::from_utf8(x.as_slice())
            .map_err(|_| WalletError::InvalidPemFile)?;
        let pri_key = PrivateKey::from_hex_str(priv_key_str)
            .map_err(|_| WalletError::InvalidPemFile)?;
        Ok(Self(pri_key))
    }

    pub fn get_address(&self) -> Address {
        let public_key = PublicKey::from(&self.0);
        Address::from_bytes(*public_key.to_address().as_array())
    }

    pub fn sign_transaction(&self, transaction: &SignableTransaction) -> String {
        let mut tx_bytes = json!(transaction).to_string().as_bytes().to_vec();

        let should_sign_on_tx_hash = transaction.version >= 2 && transaction.options & 1 > 0;
        if should_sign_on_tx_hash {
            let mut h = Keccak256::new();
            h.update(tx_bytes);
            tx_bytes = h.finalize().as_slice().to_vec();
        }

        hex::encode(self.0.sign(tx_bytes))
    }
}

impl Debug for Wallet {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.get_address().to_bech32_string().unwrap())
    }
}