diff --git a/Cargo.toml b/Cargo.toml index 0d48d90..8df3dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,9 @@ sha2 = "0.10.7" # SHA-2 hash functions chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library dirs = "5.0.1" # Directory paths +tokio = { version = "1.28", features = ["full"] } +uuid = { version = "1.16.0", features = ["v4"] } +tokio-test = "0.4.4" # Optional features for specific OS functionality [target.'cfg(unix)'.dependencies] @@ -46,6 +49,8 @@ windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Thre [dev-dependencies] tempfile = "3.5" # For tests that need temporary files/directories +tokio = { version = "1.28", features = ["full", "test-util"] } # For async testing +mockall = "0.11.4" # For mocking in tests [[bin]] name = "herodo" diff --git a/examples/hero_vault/agung_send_transaction.rhai b/examples/hero_vault/agung_send_transaction.rhai new file mode 100644 index 0000000..c22fa74 --- /dev/null +++ b/examples/hero_vault/agung_send_transaction.rhai @@ -0,0 +1,104 @@ +// Script to create an Agung wallet from a private key and send tokens +// This script demonstrates how to create a wallet from a private key and send tokens + +// Define the private key and recipient address +let private_key = "0x9ecfd58eca522b0e7c109bf945966ee208cd6d593b1dc3378aedfdc60b64f512"; +let recipient_address = "0xf400f9c3F7317e19523a5DB698Ce67e7a7E083e2"; + +print("=== Agung Wallet Transaction Demo ==="); +print(`From private key: ${private_key}`); +print(`To address: ${recipient_address}`); + +// First, create a key space and keypair (required for the wallet infrastructure) +let space_name = "agung_transaction_demo"; +let password = "demo_password"; + +// Create a new key space +if !create_key_space(space_name, password) { + print("Failed to create key space"); + return; +} + +// Create a keypair +if !create_keypair("demo_keypair", password) { + print("Failed to create keypair"); + return; +} + +// Select the keypair +if !select_keypair("demo_keypair") { + print("Failed to select keypair"); + return; +} + +print("\nCreated and selected keypair successfully"); + +// Clear any existing Agung wallets to avoid conflicts +if clear_wallets_for_network("agung") { + print("Cleared existing Agung wallets"); +} else { + print("Failed to clear existing Agung wallets"); + return; +} + +// Create a wallet from the private key directly +print("\n=== Creating Wallet from Private Key ==="); + +// Create a wallet from the private key for the Agung network +if create_wallet_from_private_key_for_network(private_key, "agung") { + print("Successfully created wallet from private key for Agung network"); + + // Get the wallet address + let wallet_address = get_wallet_address_for_network("agung"); + print(`Wallet address: ${wallet_address}`); + + // Create a provider for the Agung network + let provider_id = create_agung_provider(); + if provider_id != "" { + print("Successfully created Agung provider"); + + // Check the wallet balance first + let wallet_address = get_wallet_address_for_network("agung"); + let balance_wei = get_balance("agung", wallet_address); + + if balance_wei == "" { + print("Failed to get wallet balance"); + print("This could be due to network issues or other errors."); + return; + } + + print(`Current wallet balance: ${balance_wei} wei`); + + // Convert 1 AGNG to wei (1 AGNG = 10^18 wei) + // Use string representation for large numbers + let amount_wei_str = "1000000000000000000"; // 1 AGNG in wei as a string + + // Check if we have enough balance + if parse_int(balance_wei) < parse_int(amount_wei_str) { + print(`Insufficient balance to send ${amount_wei_str} wei (1 AGNG)`); + print(`Current balance: ${balance_wei} wei`); + print("Please fund the wallet before attempting to send a transaction"); + return; + } + + print(`Attempting to send ${amount_wei_str} wei (1 AGNG) to ${recipient_address}`); + + // Send the transaction using the blocking implementation + let tx_hash = send_eth("agung", recipient_address, amount_wei_str); + + if tx_hash != "" { + print(`Transaction sent with hash: ${tx_hash}`); + print(`You can view the transaction at: ${get_network_explorer_url("agung")}/tx/${tx_hash}`); + } else { + print("Transaction failed"); + print("This could be due to insufficient funds, network issues, or other errors."); + print("Check the logs for more details."); + } + } else { + print("Failed to create Agung provider"); + } +} else { + print("Failed to create wallet from private key"); +} + +print("\nAgung transaction demo completed"); diff --git a/src/crypto/ethereum/implementation.rs b/src/crypto/ethereum/implementation.rs deleted file mode 100644 index 59ec7ff..0000000 --- a/src/crypto/ethereum/implementation.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Implementation of Ethereum functionality. - -use ethers::prelude::*; -use ethers::signers::{LocalWallet, Signer, Wallet}; -use ethers::utils::hex; -use k256::ecdsa::SigningKey; -use std::str::FromStr; -use std::sync::Mutex; -use once_cell::sync::Lazy; -use sha2::{Sha256, Digest}; - -use crate::crypto::error::CryptoError; -use crate::crypto::keypair::KeyPair; - -// Gnosis Chain configuration -pub const GNOSIS_CHAIN_ID: u64 = 100; -pub const GNOSIS_RPC_URL: &str = "https://rpc.gnosis.gateway.fm"; -pub const GNOSIS_EXPLORER: &str = "https://gnosisscan.io"; - -/// An Ethereum wallet derived from a keypair. -#[derive(Debug, Clone)] -pub struct EthereumWallet { - pub address: Address, - pub wallet: Wallet, -} - -impl EthereumWallet { - /// Creates a new Ethereum wallet from a keypair. - pub fn from_keypair(keypair: &KeyPair) -> Result { - // Get the private key bytes from the keypair - let private_key_bytes = keypair.signing_key.to_bytes(); - - // Convert to a hex string (without 0x prefix) - let private_key_hex = hex::encode(private_key_bytes); - - // Create an Ethereum wallet from the private key - let wallet = LocalWallet::from_str(&private_key_hex) - .map_err(|e| CryptoError::InvalidKeyLength)? - .with_chain_id(GNOSIS_CHAIN_ID); - - // Get the Ethereum address - let address = wallet.address(); - - Ok(EthereumWallet { - address, - wallet, - }) - } - - /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation). - pub fn from_name_and_keypair(name: &str, keypair: &KeyPair) -> Result { - // Get the private key bytes from the keypair - let private_key_bytes = keypair.signing_key.to_bytes(); - - // Create a deterministic seed by combining name and private key - let mut hasher = Sha256::default(); - hasher.update(name.as_bytes()); - hasher.update(&private_key_bytes); - let seed = hasher.finalize(); - - // Use the seed as a private key - let private_key_hex = hex::encode(seed); - - // Create an Ethereum wallet from the derived private key - let wallet = LocalWallet::from_str(&private_key_hex) - .map_err(|e| CryptoError::InvalidKeyLength)? - .with_chain_id(GNOSIS_CHAIN_ID); - - // Get the Ethereum address - let address = wallet.address(); - - Ok(EthereumWallet { - address, - wallet, - }) - } - - /// Creates a new Ethereum wallet from a private key. - pub fn from_private_key(private_key: &str) -> Result { - // Remove 0x prefix if present - let private_key_clean = private_key.trim_start_matches("0x"); - - // Create an Ethereum wallet from the private key - let wallet = LocalWallet::from_str(private_key_clean) - .map_err(|e| CryptoError::InvalidKeyLength)? - .with_chain_id(GNOSIS_CHAIN_ID); - - // Get the Ethereum address - let address = wallet.address(); - - Ok(EthereumWallet { - address, - wallet, - }) - } - - /// Gets the Ethereum address as a string. - pub fn address_string(&self) -> String { - format!("{:?}", self.address) - } - - /// Signs a message with the Ethereum wallet. - pub async fn sign_message(&self, message: &[u8]) -> Result { - let signature = self.wallet.sign_message(message) - .await - .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; - - Ok(signature.to_string()) - } - - /// Gets the private key as a hex string. - pub fn private_key_hex(&self) -> String { - let bytes = self.wallet.signer().to_bytes(); - hex::encode(bytes) - } -} - -/// Global storage for Ethereum wallets. -static ETH_WALLETS: Lazy>> = Lazy::new(|| { - Mutex::new(Vec::new()) -}); - -/// Creates an Ethereum wallet from the currently selected keypair. -pub fn create_ethereum_wallet() -> Result { - // Get the currently selected keypair - let keypair = crate::crypto::keypair::get_selected_keypair()?; - - // Create an Ethereum wallet from the keypair - let wallet = EthereumWallet::from_keypair(&keypair)?; - - // Store the wallet - let mut wallets = ETH_WALLETS.lock().unwrap(); - wallets.push(wallet.clone()); - - Ok(wallet) -} - -/// Gets the current Ethereum wallet. -pub fn get_current_ethereum_wallet() -> Result { - let wallets = ETH_WALLETS.lock().unwrap(); - - if wallets.is_empty() { - return Err(CryptoError::NoKeypairSelected); - } - - Ok(wallets.last().unwrap().clone()) -} - -/// Clears all Ethereum wallets. -pub fn clear_ethereum_wallets() { - let mut wallets = ETH_WALLETS.lock().unwrap(); - wallets.clear(); -} - -/// Formats an Ethereum balance for display. -pub fn format_eth_balance(balance: U256) -> String { - let wei = balance.as_u128(); - let eth = wei as f64 / 1_000_000_000_000_000_000.0; - format!("{:.6} ETH", eth) -} - -/// Gets the balance of an Ethereum address. -pub async fn get_balance(provider: &Provider, address: Address) -> Result { - provider.get_balance(address, None) - .await - .map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e))) -} - -/// Sends Ethereum from one address to another. -pub async fn send_eth( - wallet: &EthereumWallet, - provider: &Provider, - to: Address, - amount: U256, -) -> Result { - // Create a client with the wallet - let client = SignerMiddleware::new( - provider.clone(), - wallet.wallet.clone(), - ); - - // Create the transaction - let tx = TransactionRequest::new() - .to(to) - .value(amount) - .gas(21000); - - // Send the transaction - let pending_tx = client.send_transaction(tx, None) - .await - .map_err(|e| CryptoError::SerializationError(format!("Failed to send transaction: {}", e)))?; - - // Return the transaction hash instead of waiting for the receipt - Ok(pending_tx.tx_hash()) -} - -/// Creates a provider for the Gnosis Chain. -pub fn create_gnosis_provider() -> Result, CryptoError> { - Provider::::try_from(GNOSIS_RPC_URL) - .map_err(|e| CryptoError::SerializationError(format!("Failed to create Gnosis provider: {}", e))) -} - -/// Creates an Ethereum wallet from a name and the currently selected keypair. -pub fn create_ethereum_wallet_from_name(name: &str) -> Result { - // Get the currently selected keypair - let keypair = crate::crypto::keypair::get_selected_keypair()?; - - // Create an Ethereum wallet from the name and keypair - let wallet = EthereumWallet::from_name_and_keypair(name, &keypair)?; - - // Store the wallet - let mut wallets = ETH_WALLETS.lock().unwrap(); - wallets.push(wallet.clone()); - - Ok(wallet) -} - -/// Creates an Ethereum wallet from a private key. -pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result { - // Create an Ethereum wallet from the private key - let wallet = EthereumWallet::from_private_key(private_key)?; - - // Store the wallet - let mut wallets = ETH_WALLETS.lock().unwrap(); - wallets.push(wallet.clone()); - - Ok(wallet) -} \ No newline at end of file diff --git a/src/crypto/ethereum/mod.rs b/src/crypto/ethereum/mod.rs deleted file mode 100644 index 4764b38..0000000 --- a/src/crypto/ethereum/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Ethereum wallet functionality -//! -//! This module provides functionality for creating and managing Ethereum wallets. - -mod implementation; - -// Re-export public types and functions -pub use implementation::{ - EthereumWallet, - create_ethereum_wallet, - get_current_ethereum_wallet -}; \ No newline at end of file diff --git a/src/crypto/error.rs b/src/hero_vault/error.rs similarity index 99% rename from src/crypto/error.rs rename to src/hero_vault/error.rs index edab358..16c6d64 100644 --- a/src/crypto/error.rs +++ b/src/hero_vault/error.rs @@ -47,4 +47,4 @@ impl From for crate::Error { fn from(err: CryptoError) -> Self { crate::Error::Sal(err.to_string()) } -} \ No newline at end of file +} diff --git a/src/hero_vault/ethereum/mod.rs b/src/hero_vault/ethereum/mod.rs new file mode 100644 index 0000000..f5e8d2f --- /dev/null +++ b/src/hero_vault/ethereum/mod.rs @@ -0,0 +1,66 @@ +//! Ethereum wallet functionality +//! +//! This module provides functionality for creating and managing Ethereum wallets. +//! +//! The module is organized into several components: +//! - `wallet.rs`: Core Ethereum wallet implementation +//! - `networks.rs`: Network registry and configuration +//! - `provider.rs`: Provider creation and management +//! - `transaction.rs`: Transaction-related functionality +//! - `storage.rs`: Wallet storage functionality + +mod wallet; +mod provider; +mod transaction; +mod storage; +pub mod networks; +#[cfg(test)] +pub mod tests; + +// Re-export public types and functions +pub use wallet::EthereumWallet; +pub use networks::NetworkConfig; + +// Re-export wallet creation functions +pub use storage::{ + create_ethereum_wallet_for_network, + create_peaq_wallet, + create_agung_wallet, + create_ethereum_wallet_from_name_for_network, + create_ethereum_wallet_from_name, + create_ethereum_wallet_from_private_key_for_network, + create_ethereum_wallet_from_private_key, +}; + +// Re-export wallet management functions +pub use storage::{ + get_current_ethereum_wallet_for_network, + get_current_peaq_wallet, + get_current_agung_wallet, + clear_ethereum_wallets, + clear_ethereum_wallets_for_network, +}; + +// Re-export provider functions +pub use provider::{ + create_provider, + create_gnosis_provider, + create_peaq_provider, + create_agung_provider, +}; + +// Re-export transaction functions +pub use transaction::{ + get_balance, + send_eth, + format_balance, +}; + +// Re-export network registry functions +pub use networks::{ + get_network_by_name, + get_proper_network_name, + list_network_names, + get_all_networks, + names, +}; diff --git a/src/hero_vault/ethereum/networks.rs b/src/hero_vault/ethereum/networks.rs new file mode 100644 index 0000000..d2d5858 --- /dev/null +++ b/src/hero_vault/ethereum/networks.rs @@ -0,0 +1,101 @@ +//! Ethereum network registry +//! +//! This module provides a centralized registry of Ethereum networks and utilities +//! to work with them. + +use std::collections::HashMap; +use std::sync::OnceLock; + +/// Configuration for an EVM-compatible network +#[derive(Debug, Clone)] +pub struct NetworkConfig { + pub name: String, + pub chain_id: u64, + pub rpc_url: String, + pub explorer_url: String, + pub token_symbol: String, + pub decimals: u8, +} + +/// Network name constants +pub mod names { + pub const GNOSIS: &str = "Gnosis"; + pub const PEAQ: &str = "Peaq"; + pub const AGUNG: &str = "Agung"; +} + +/// Get the Gnosis Chain network configuration +pub fn gnosis() -> NetworkConfig { + NetworkConfig { + name: names::GNOSIS.to_string(), + chain_id: 100, + rpc_url: "https://rpc.gnosischain.com".to_string(), + explorer_url: "https://gnosisscan.io".to_string(), + token_symbol: "xDAI".to_string(), + decimals: 18, + } +} + +/// Get the Peaq Network configuration +pub fn peaq() -> NetworkConfig { + NetworkConfig { + name: names::PEAQ.to_string(), + chain_id: 1701, + rpc_url: "https://peaq.api.onfinality.io/public".to_string(), + explorer_url: "https://peaq.subscan.io/".to_string(), + token_symbol: "PEAQ".to_string(), + decimals: 18, + } +} + +/// Get the Agung Testnet configuration +pub fn agung() -> NetworkConfig { + NetworkConfig { + name: names::AGUNG.to_string(), + chain_id: 9990, + rpc_url: "https://wss-async.agung.peaq.network".to_string(), + explorer_url: "https://agung-testnet.subscan.io/".to_string(), + token_symbol: "AGNG".to_string(), + decimals: 18, + } +} + +/// Get a network by its name (case-insensitive) +pub fn get_network_by_name(name: &str) -> Option { + let name_lower = name.to_lowercase(); + match name_lower.as_str() { + "gnosis" => Some(gnosis()), + "peaq" => Some(peaq()), + "agung" => Some(agung()), + _ => None, + } +} + +/// Get the proper capitalization of a network name +pub fn get_proper_network_name(name: &str) -> Option<&'static str> { + let name_lower = name.to_lowercase(); + match name_lower.as_str() { + "gnosis" => Some(names::GNOSIS), + "peaq" => Some(names::PEAQ), + "agung" => Some(names::AGUNG), + _ => None, + } +} + +/// Get a list of all supported network names +pub fn list_network_names() -> Vec<&'static str> { + vec![names::GNOSIS, names::PEAQ, names::AGUNG] +} + +/// Get a map of all networks +pub fn get_all_networks() -> &'static HashMap<&'static str, NetworkConfig> { + static NETWORKS: OnceLock> = OnceLock::new(); + + NETWORKS.get_or_init(|| { + let mut map = HashMap::new(); + map.insert(names::GNOSIS, gnosis()); + map.insert(names::PEAQ, peaq()); + map.insert(names::AGUNG, agung()); + map + }) +} diff --git a/src/hero_vault/ethereum/provider.rs b/src/hero_vault/ethereum/provider.rs new file mode 100644 index 0000000..fc7fcbd --- /dev/null +++ b/src/hero_vault/ethereum/provider.rs @@ -0,0 +1,27 @@ +//! Ethereum provider functionality. + +use ethers::prelude::*; + +use crate::hero_vault::error::CryptoError; +use super::networks::{self, NetworkConfig}; + +/// Creates a provider for a specific network. +pub fn create_provider(network: &NetworkConfig) -> Result, CryptoError> { + Provider::::try_from(network.rpc_url.as_str()) + .map_err(|e| CryptoError::SerializationError(format!("Failed to create provider for {}: {}", network.name, e))) +} + +/// Creates a provider for the Gnosis Chain. +pub fn create_gnosis_provider() -> Result, CryptoError> { + create_provider(&networks::gnosis()) +} + +/// Creates a provider for the Peaq network. +pub fn create_peaq_provider() -> Result, CryptoError> { + create_provider(&networks::peaq()) +} + +/// Creates a provider for the Agung testnet. +pub fn create_agung_provider() -> Result, CryptoError> { + create_provider(&networks::agung()) +} diff --git a/src/hero_vault/ethereum/storage.rs b/src/hero_vault/ethereum/storage.rs new file mode 100644 index 0000000..52b6a8c --- /dev/null +++ b/src/hero_vault/ethereum/storage.rs @@ -0,0 +1,114 @@ +//! Ethereum wallet storage functionality. + +use std::sync::Mutex; +use std::collections::HashMap; +use once_cell::sync::Lazy; + +use crate::hero_vault::error::CryptoError; +use super::wallet::EthereumWallet; +use super::networks::{self, NetworkConfig}; + +/// Global storage for Ethereum wallets. +static ETH_WALLETS: Lazy>>> = Lazy::new(|| { + Mutex::new(HashMap::new()) +}); + +/// Creates an Ethereum wallet from the currently selected keypair for a specific network. +pub fn create_ethereum_wallet_for_network(network: NetworkConfig) -> Result { + // Get the currently selected keypair + let keypair = crate::hero_vault::keypair::get_selected_keypair()?; + + // Create an Ethereum wallet from the keypair + let wallet = EthereumWallet::from_keypair(&keypair, network)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); + network_wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from the currently selected keypair for the Peaq network. +pub fn create_peaq_wallet() -> Result { + create_ethereum_wallet_for_network(networks::peaq()) +} + +/// Creates an Ethereum wallet from the currently selected keypair for the Agung testnet. +pub fn create_agung_wallet() -> Result { + create_ethereum_wallet_for_network(networks::agung()) +} + +/// Gets the current Ethereum wallet for a specific network. +pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result { + let wallets = ETH_WALLETS.lock().unwrap(); + + let network_wallets = wallets.get(network_name).ok_or(CryptoError::NoKeypairSelected)?; + + if network_wallets.is_empty() { + return Err(CryptoError::NoKeypairSelected); + } + + Ok(network_wallets.last().unwrap().clone()) +} + +/// Gets the current Ethereum wallet for the Peaq network. +pub fn get_current_peaq_wallet() -> Result { + get_current_ethereum_wallet_for_network("Peaq") +} + +/// Gets the current Ethereum wallet for the Agung testnet. +pub fn get_current_agung_wallet() -> Result { + get_current_ethereum_wallet_for_network("Agung") +} + +/// Clears all Ethereum wallets. +pub fn clear_ethereum_wallets() { + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.clear(); +} + +/// Clears Ethereum wallets for a specific network. +pub fn clear_ethereum_wallets_for_network(network_name: &str) { + let mut wallets = ETH_WALLETS.lock().unwrap(); + wallets.remove(network_name); +} + +/// Creates an Ethereum wallet from a name and the currently selected keypair for a specific network. +pub fn create_ethereum_wallet_from_name_for_network(name: &str, network: NetworkConfig) -> Result { + // Get the currently selected keypair + let keypair = crate::hero_vault::keypair::get_selected_keypair()?; + + // Create an Ethereum wallet from the name and keypair + let wallet = EthereumWallet::from_name_and_keypair(name, &keypair, network)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); + network_wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from a name and the currently selected keypair for the Gnosis network. +pub fn create_ethereum_wallet_from_name(name: &str) -> Result { + create_ethereum_wallet_from_name_for_network(name, networks::gnosis()) +} + +/// Creates an Ethereum wallet from a private key for a specific network. +pub fn create_ethereum_wallet_from_private_key_for_network(private_key: &str, network: NetworkConfig) -> Result { + // Create an Ethereum wallet from the private key + let wallet = EthereumWallet::from_private_key(private_key, network)?; + + // Store the wallet + let mut wallets = ETH_WALLETS.lock().unwrap(); + let network_wallets = wallets.entry(wallet.network.name.clone()).or_insert_with(Vec::new); + network_wallets.push(wallet.clone()); + + Ok(wallet) +} + +/// Creates an Ethereum wallet from a private key for the Gnosis network. +pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result { + create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis()) +} diff --git a/src/hero_vault/ethereum/tests/mod.rs b/src/hero_vault/ethereum/tests/mod.rs new file mode 100644 index 0000000..08f6788 --- /dev/null +++ b/src/hero_vault/ethereum/tests/mod.rs @@ -0,0 +1,5 @@ +//! Tests for Ethereum functionality. + +mod wallet_tests; +mod network_tests; +mod transaction_tests; diff --git a/src/hero_vault/ethereum/tests/network_tests.rs b/src/hero_vault/ethereum/tests/network_tests.rs new file mode 100644 index 0000000..1244d3b --- /dev/null +++ b/src/hero_vault/ethereum/tests/network_tests.rs @@ -0,0 +1,74 @@ +//! Tests for Ethereum network functionality. + +use crate::hero_vault::ethereum::*; + +#[test] +fn test_network_config() { + let gnosis = networks::gnosis(); + assert_eq!(gnosis.name, "Gnosis"); + assert_eq!(gnosis.chain_id, 100); + assert_eq!(gnosis.token_symbol, "xDAI"); + + let peaq = networks::peaq(); + assert_eq!(peaq.name, "Peaq"); + assert_eq!(peaq.chain_id, 1701); + assert_eq!(peaq.token_symbol, "PEAQ"); + + let agung = networks::agung(); + assert_eq!(agung.name, "Agung"); + assert_eq!(agung.chain_id, 9990); + assert_eq!(agung.token_symbol, "AGNG"); +} + +#[test] +fn test_network_registry() { + let network_names = networks::list_network_names(); + assert!(network_names.iter().any(|&name| name == "Gnosis")); + assert!(network_names.iter().any(|&name| name == "Peaq")); + assert!(network_names.iter().any(|&name| name == "Agung")); + + let gnosis_proper = networks::get_proper_network_name("gnosis"); + assert_eq!(gnosis_proper, Some("Gnosis")); + + let peaq_proper = networks::get_proper_network_name("peaq"); + assert_eq!(peaq_proper, Some("Peaq")); + + let agung_proper = networks::get_proper_network_name("agung"); + assert_eq!(agung_proper, Some("Agung")); + + let unknown = networks::get_proper_network_name("unknown"); + assert_eq!(unknown, None); + + let gnosis_config = networks::get_network_by_name("Gnosis"); + assert!(gnosis_config.is_some()); + assert_eq!(gnosis_config.unwrap().chain_id, 100); + + let unknown_config = networks::get_network_by_name("Unknown"); + assert!(unknown_config.is_none()); +} + +#[test] +fn test_create_provider() { + let gnosis = networks::gnosis(); + let peaq = networks::peaq(); + let agung = networks::agung(); + + // Create providers + let gnosis_provider = create_provider(&gnosis); + let peaq_provider = create_provider(&peaq); + let agung_provider = create_provider(&agung); + + // They should all succeed + assert!(gnosis_provider.is_ok()); + assert!(peaq_provider.is_ok()); + assert!(agung_provider.is_ok()); + + // The convenience functions should also work + let gnosis_provider2 = create_gnosis_provider(); + let peaq_provider2 = create_peaq_provider(); + let agung_provider2 = create_agung_provider(); + + assert!(gnosis_provider2.is_ok()); + assert!(peaq_provider2.is_ok()); + assert!(agung_provider2.is_ok()); +} diff --git a/src/hero_vault/ethereum/tests/transaction_tests.rs b/src/hero_vault/ethereum/tests/transaction_tests.rs new file mode 100644 index 0000000..dd4dc1d --- /dev/null +++ b/src/hero_vault/ethereum/tests/transaction_tests.rs @@ -0,0 +1,70 @@ +//! Tests for Ethereum transaction functionality. + +use crate::hero_vault::ethereum::*; +use crate::hero_vault::keypair::KeyPair; +use ethers::types::U256; +use std::str::FromStr; + +#[test] +fn test_format_balance() { + let network = networks::gnosis(); + + // Test with 0 + let balance = U256::from(0); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "0.000000 xDAI"); + + // Test with 1 wei + let balance = U256::from(1); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "0.000000 xDAI"); + + // Test with 1 gwei (10^9 wei) + let balance = U256::from(1_000_000_000u64); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "0.000000 xDAI"); + + // Test with 1 ETH (10^18 wei) + let balance = U256::from_dec_str("1000000000000000000").unwrap(); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "1.000000 xDAI"); + + // Test with a larger amount + let balance = U256::from_dec_str("123456789000000000000").unwrap(); + let formatted = format_balance(balance, &network); + assert_eq!(formatted, "123.456789 xDAI"); +} + +#[test] +fn test_get_balance() { + // This is a mock test since we can't actually query the blockchain in a unit test + // In a real test, we would use a local blockchain or mock the provider + + // Create a provider + let network = networks::gnosis(); + let provider_result = create_provider(&network); + + // The provider creation should succeed + assert!(provider_result.is_ok()); + + // We can't actually test get_balance without a blockchain + // In a real test, we would mock the provider and test the function +} + +#[test] +fn test_send_eth() { + // This is a mock test since we can't actually send transactions in a unit test + // In a real test, we would use a local blockchain or mock the provider + + // Create a wallet + let keypair = KeyPair::new("test_keypair6"); + let network = networks::gnosis(); + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + // Create a provider + let provider_result = create_provider(&network); + assert!(provider_result.is_ok()); + + // We can't actually test send_eth without a blockchain + // In a real test, we would mock the provider and test the function +} diff --git a/src/hero_vault/ethereum/tests/wallet_tests.rs b/src/hero_vault/ethereum/tests/wallet_tests.rs new file mode 100644 index 0000000..82e1f4b --- /dev/null +++ b/src/hero_vault/ethereum/tests/wallet_tests.rs @@ -0,0 +1,143 @@ +//! Tests for Ethereum wallet functionality. + +use crate::hero_vault::ethereum::*; +use crate::hero_vault::keypair::KeyPair; +use ethers::utils::hex; + +#[test] +fn test_ethereum_wallet_from_keypair() { + let keypair = KeyPair::new("test_keypair"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + assert_eq!(wallet.network.name, "Gnosis"); + assert_eq!(wallet.network.chain_id, 100); + + // The address should be a valid Ethereum address + assert!(wallet.address_string().starts_with("0x")); +} + +#[test] +fn test_ethereum_wallet_from_name_and_keypair() { + let keypair = KeyPair::new("test_keypair2"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap(); + + assert_eq!(wallet.network.name, "Gnosis"); + assert_eq!(wallet.network.chain_id, 100); + + // The address should be a valid Ethereum address + assert!(wallet.address_string().starts_with("0x")); + + // Creating another wallet with the same name and keypair should yield the same address + let wallet2 = EthereumWallet::from_name_and_keypair("test", &keypair, network.clone()).unwrap(); + assert_eq!(wallet.address, wallet2.address); + + // Creating a wallet with a different name should yield a different address + let wallet3 = EthereumWallet::from_name_and_keypair("test2", &keypair, network.clone()).unwrap(); + assert_ne!(wallet.address, wallet3.address); +} + +#[test] +fn test_ethereum_wallet_from_private_key() { + let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_private_key(private_key, network.clone()).unwrap(); + + assert_eq!(wallet.network.name, "Gnosis"); + assert_eq!(wallet.network.chain_id, 100); + + // The address should be a valid Ethereum address + assert!(wallet.address_string().starts_with("0x")); + + // The address should be deterministic based on the private key + let wallet2 = EthereumWallet::from_private_key(private_key, network.clone()).unwrap(); + assert_eq!(wallet.address, wallet2.address); +} + +#[test] +fn test_wallet_management() { + // Clear any existing wallets + clear_ethereum_wallets(); + + // Create a key space and keypair + crate::hero_vault::keypair::create_space("test_space").unwrap(); + crate::hero_vault::keypair::create_keypair("test_keypair3").unwrap(); + + // Create wallets for different networks + let gnosis_wallet = create_ethereum_wallet_for_network(networks::gnosis()).unwrap(); + let peaq_wallet = create_ethereum_wallet_for_network(networks::peaq()).unwrap(); + let agung_wallet = create_ethereum_wallet_for_network(networks::agung()).unwrap(); + + // Get the current wallets + let current_gnosis = get_current_ethereum_wallet_for_network("Gnosis").unwrap(); + let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap(); + let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap(); + + // Check that they match + assert_eq!(gnosis_wallet.address, current_gnosis.address); + assert_eq!(peaq_wallet.address, current_peaq.address); + assert_eq!(agung_wallet.address, current_agung.address); + + // Clear wallets for a specific network + clear_ethereum_wallets_for_network("Gnosis"); + + // Check that the wallet is gone + let result = get_current_ethereum_wallet_for_network("Gnosis"); + assert!(result.is_err()); + + // But the others should still be there + let current_peaq = get_current_ethereum_wallet_for_network("Peaq").unwrap(); + let current_agung = get_current_ethereum_wallet_for_network("Agung").unwrap(); + assert_eq!(peaq_wallet.address, current_peaq.address); + assert_eq!(agung_wallet.address, current_agung.address); + + // Clear all wallets + clear_ethereum_wallets(); + + // Check that all wallets are gone + let result1 = get_current_ethereum_wallet_for_network("Gnosis"); + let result2 = get_current_ethereum_wallet_for_network("Peaq"); + let result3 = get_current_ethereum_wallet_for_network("Agung"); + assert!(result1.is_err()); + assert!(result2.is_err()); + assert!(result3.is_err()); +} + +#[test] +fn test_sign_message() { + let keypair = KeyPair::new("test_keypair4"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + // Create a tokio runtime for the async test + let rt = tokio::runtime::Runtime::new().unwrap(); + + // Sign a message + let message = b"Hello, world!"; + let signature = rt.block_on(wallet.sign_message(message)).unwrap(); + + // The signature should be a non-empty string + assert!(!signature.is_empty()); +} + +#[test] +fn test_private_key_hex() { + let keypair = KeyPair::new("test_keypair5"); + let network = networks::gnosis(); + + let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap(); + + // Get the private key as hex + let private_key_hex = wallet.private_key_hex(); + + // The private key should be a 64-character hex string (32 bytes) + assert_eq!(private_key_hex.len(), 64); + + // It should be possible to parse it as hex + let _bytes = hex::decode(private_key_hex).unwrap(); +} diff --git a/src/hero_vault/ethereum/transaction.rs b/src/hero_vault/ethereum/transaction.rs new file mode 100644 index 0000000..7c1deb5 --- /dev/null +++ b/src/hero_vault/ethereum/transaction.rs @@ -0,0 +1,54 @@ +//! Ethereum transaction functionality. + +use ethers::prelude::*; + +use crate::hero_vault::error::CryptoError; +use super::wallet::EthereumWallet; +use super::networks::NetworkConfig; + +/// Formats a token balance for display. +pub fn format_balance(balance: U256, network: &NetworkConfig) -> String { + let wei = balance.as_u128(); + let divisor = 10u128.pow(network.decimals as u32) as f64; + let token = wei as f64 / divisor; + + // Display with the appropriate number of decimal places + let display_decimals = std::cmp::min(6, network.decimals); + + format!("{:.*} {}", display_decimals as usize, token, network.token_symbol) +} + +/// Gets the balance of an Ethereum address. +pub async fn get_balance(provider: &Provider, address: Address) -> Result { + provider.get_balance(address, None) + .await + .map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e))) +} + +/// Sends Ethereum from one address to another. +pub async fn send_eth( + wallet: &EthereumWallet, + provider: &Provider, + to: Address, + amount: U256, +) -> Result { + // Create a client with the wallet + let client = SignerMiddleware::new( + provider.clone(), + wallet.wallet.clone(), + ); + + // Create the transaction + let tx = TransactionRequest::new() + .to(to) + .value(amount) + .gas(21000); + + // Send the transaction + let pending_tx = client.send_transaction(tx, None) + .await + .map_err(|e| CryptoError::SerializationError(format!("Failed to send transaction: {}", e)))?; + + // Return the transaction hash instead of waiting for the receipt + Ok(pending_tx.tx_hash()) +} diff --git a/src/hero_vault/ethereum/wallet.rs b/src/hero_vault/ethereum/wallet.rs new file mode 100644 index 0000000..ecb73eb --- /dev/null +++ b/src/hero_vault/ethereum/wallet.rs @@ -0,0 +1,114 @@ +//! Ethereum wallet implementation. + +use ethers::prelude::*; +use ethers::signers::{LocalWallet, Signer, Wallet}; +use ethers::utils::hex; +use k256::ecdsa::SigningKey; +use std::str::FromStr; +use sha2::{Sha256, Digest}; + +use crate::hero_vault::error::CryptoError; +use crate::hero_vault::keypair::KeyPair; +use super::networks::NetworkConfig; + +/// An Ethereum wallet derived from a keypair. +#[derive(Debug, Clone)] +pub struct EthereumWallet { + pub address: Address, + pub wallet: Wallet, + pub network: NetworkConfig, +} + +impl EthereumWallet { + /// Creates a new Ethereum wallet from a keypair for a specific network. + pub fn from_keypair(keypair: &KeyPair, network: NetworkConfig) -> Result { + // Get the private key bytes from the keypair + let private_key_bytes = keypair.signing_key.to_bytes(); + + // Convert to a hex string (without 0x prefix) + let private_key_hex = hex::encode(private_key_bytes); + + // Create an Ethereum wallet from the private key + let wallet = LocalWallet::from_str(&private_key_hex) + .map_err(|_e| CryptoError::InvalidKeyLength)? + .with_chain_id(network.chain_id); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + network, + }) + } + + /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network. + pub fn from_name_and_keypair(name: &str, keypair: &KeyPair, network: NetworkConfig) -> Result { + // Get the private key bytes from the keypair + let private_key_bytes = keypair.signing_key.to_bytes(); + + // Create a deterministic seed by combining name and private key + let mut hasher = Sha256::default(); + hasher.update(name.as_bytes()); + hasher.update(&private_key_bytes); + let seed = hasher.finalize(); + + // Use the seed as a private key + let private_key_hex = hex::encode(seed); + + // Create an Ethereum wallet from the derived private key + let wallet = LocalWallet::from_str(&private_key_hex) + .map_err(|_e| CryptoError::InvalidKeyLength)? + .with_chain_id(network.chain_id); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + network, + }) + } + + /// Creates a new Ethereum wallet from a private key for a specific network. + pub fn from_private_key(private_key: &str, network: NetworkConfig) -> Result { + // Remove 0x prefix if present + let private_key_clean = private_key.trim_start_matches("0x"); + + // Create an Ethereum wallet from the private key + let wallet = LocalWallet::from_str(private_key_clean) + .map_err(|_e| CryptoError::InvalidKeyLength)? + .with_chain_id(network.chain_id); + + // Get the Ethereum address + let address = wallet.address(); + + Ok(EthereumWallet { + address, + wallet, + network, + }) + } + + /// Gets the Ethereum address as a string. + pub fn address_string(&self) -> String { + format!("{:?}", self.address) + } + + /// Signs a message with the Ethereum wallet. + pub async fn sign_message(&self, message: &[u8]) -> Result { + let signature = self.wallet.sign_message(message) + .await + .map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?; + + Ok(signature.to_string()) + } + + /// Gets the private key as a hex string. + pub fn private_key_hex(&self) -> String { + let bytes = self.wallet.signer().to_bytes(); + hex::encode(bytes) + } +} diff --git a/src/crypto/keypair/implementation.rs b/src/hero_vault/keypair/implementation.rs similarity index 98% rename from src/crypto/keypair/implementation.rs rename to src/hero_vault/keypair/implementation.rs index 591c87b..edd35c6 100644 --- a/src/crypto/keypair/implementation.rs +++ b/src/hero_vault/keypair/implementation.rs @@ -8,7 +8,7 @@ use once_cell::sync::Lazy; use std::sync::Mutex; use sha2::{Sha256, Digest}; -use crate::crypto::error::CryptoError; +use crate::hero_vault::error::CryptoError; /// A keypair for signing and verifying messages. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -226,7 +226,7 @@ impl KeyPair { }; // Encrypt the message using the derived key - let ciphertext = crate::crypto::symmetric::encrypt_with_key(&shared_secret, message) + let ciphertext = crate::hero_vault::symmetric::encrypt_with_key(&shared_secret, message) .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?; // Format: ephemeral_public_key || ciphertext @@ -263,7 +263,7 @@ impl KeyPair { }; // Decrypt the message using the derived key - crate::crypto::symmetric::decrypt_with_key(&shared_secret, actual_ciphertext) + crate::hero_vault::symmetric::decrypt_with_key(&shared_secret, actual_ciphertext) .map_err(|e| CryptoError::DecryptionFailed(e.to_string())) } } @@ -464,4 +464,4 @@ pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result, CryptoError> { let keypair = get_selected_keypair()?; keypair.decrypt_asymmetric(ciphertext) -} \ No newline at end of file +} diff --git a/src/crypto/keypair/mod.rs b/src/hero_vault/keypair/mod.rs similarity index 99% rename from src/crypto/keypair/mod.rs rename to src/hero_vault/keypair/mod.rs index fde0f35..016b7f2 100644 --- a/src/crypto/keypair/mod.rs +++ b/src/hero_vault/keypair/mod.rs @@ -11,4 +11,4 @@ pub use implementation::{ create_keypair, select_keypair, get_selected_keypair, list_keypairs, keypair_pub_key, derive_public_key, keypair_sign, keypair_verify, verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric -}; \ No newline at end of file +}; diff --git a/src/crypto/kvs/error.rs b/src/hero_vault/kvs/error.rs similarity index 65% rename from src/crypto/kvs/error.rs rename to src/hero_vault/kvs/error.rs index 9b0527d..039644e 100644 --- a/src/crypto/kvs/error.rs +++ b/src/hero_vault/kvs/error.rs @@ -45,22 +45,22 @@ impl From for KvsError { } } -impl From for crate::crypto::error::CryptoError { +impl From for crate::hero_vault::error::CryptoError { fn from(err: KvsError) -> Self { - crate::crypto::error::CryptoError::SerializationError(err.to_string()) + crate::hero_vault::error::CryptoError::SerializationError(err.to_string()) } } -impl From for KvsError { - fn from(err: crate::crypto::error::CryptoError) -> Self { +impl From for KvsError { + fn from(err: crate::hero_vault::error::CryptoError) -> Self { match err { - crate::crypto::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg), - crate::crypto::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg), - crate::crypto::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg), + crate::hero_vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg), + crate::hero_vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg), + crate::hero_vault::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg), _ => KvsError::Other(err.to_string()), } } } /// Result type for key-value store operations. -pub type Result = std::result::Result; \ No newline at end of file +pub type Result = std::result::Result; diff --git a/src/crypto/kvs/mod.rs b/src/hero_vault/kvs/mod.rs similarity index 99% rename from src/crypto/kvs/mod.rs rename to src/hero_vault/kvs/mod.rs index 9171032..890333d 100644 --- a/src/crypto/kvs/mod.rs +++ b/src/hero_vault/kvs/mod.rs @@ -11,4 +11,4 @@ pub use store::{ KvStore, KvPair, create_store, open_store, delete_store, list_stores, get_store_path -}; \ No newline at end of file +}; diff --git a/src/crypto/kvs/store.rs b/src/hero_vault/kvs/store.rs similarity index 99% rename from src/crypto/kvs/store.rs rename to src/hero_vault/kvs/store.rs index be70caa..6ea279f 100644 --- a/src/crypto/kvs/store.rs +++ b/src/hero_vault/kvs/store.rs @@ -1,7 +1,7 @@ //! Implementation of a simple key-value store using the filesystem. -use crate::crypto::kvs::error::{KvsError, Result}; -use crate::crypto::symmetric; +use crate::hero_vault::kvs::error::{KvsError, Result}; +use crate::hero_vault::symmetric; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; use std::fs; diff --git a/src/crypto/mod.rs b/src/hero_vault/mod.rs similarity index 63% rename from src/crypto/mod.rs rename to src/hero_vault/mod.rs index 43cb5bf..0a301ca 100644 --- a/src/crypto/mod.rs +++ b/src/hero_vault/mod.rs @@ -1,7 +1,9 @@ -//! Cryptographic functionality for SAL +//! Hero Vault: Cryptographic functionality for SAL //! //! This module provides cryptographic operations including: +//! - Key space management (creation, loading, encryption, decryption) //! - Key pair management (ECDSA) +//! - Digital signatures (signing and verification) //! - Symmetric encryption (ChaCha20Poly1305) //! - Ethereum wallet functionality //! - Key-value store with encryption @@ -14,4 +16,4 @@ pub mod kvs; // Re-export common types for convenience pub use error::CryptoError; -pub use keypair::{KeyPair, KeySpace}; \ No newline at end of file +pub use keypair::{KeyPair, KeySpace}; diff --git a/src/crypto/symmetric/implementation.rs b/src/hero_vault/symmetric/implementation.rs similarity index 98% rename from src/crypto/symmetric/implementation.rs rename to src/hero_vault/symmetric/implementation.rs index 65eff19..476470e 100644 --- a/src/crypto/symmetric/implementation.rs +++ b/src/hero_vault/symmetric/implementation.rs @@ -6,8 +6,8 @@ use rand::{rngs::OsRng, RngCore}; use serde::{Serialize, Deserialize}; use sha2::{Sha256, Digest}; -use crate::crypto::error::CryptoError; -use crate::crypto::keypair::KeySpace; +use crate::hero_vault::error::CryptoError; +use crate::hero_vault::keypair::KeySpace; /// The size of the nonce in bytes. const NONCE_SIZE: usize = 12; @@ -263,4 +263,4 @@ pub fn deserialize_encrypted_space(serialized: &str) -> Result> = Lazy::new(|| { + Mutex::new(Runtime::new().expect("Failed to create Tokio runtime")) +}); + +// Global provider registry +static PROVIDERS: Lazy>>> = Lazy::new(|| { + Mutex::new(HashMap::new()) +}); // Key space management functions fn load_key_space(name: &str, password: &str) -> bool { @@ -366,8 +382,10 @@ fn decrypt(key: &str, ciphertext: &str) -> String { } // Ethereum operations + +// Gnosis Chain operations fn create_ethereum_wallet() -> bool { - match ethereum::create_ethereum_wallet() { + match ethereum::create_ethereum_wallet_for_network(ethereum::networks::gnosis()) { Ok(_) => true, Err(e) => { log::error!("Error creating Ethereum wallet: {}", e); @@ -377,7 +395,7 @@ fn create_ethereum_wallet() -> bool { } fn get_ethereum_address() -> String { - match ethereum::get_current_ethereum_wallet() { + match ethereum::get_current_ethereum_wallet_for_network("Gnosis") { Ok(wallet) => wallet.address_string(), Err(e) => { log::error!("Error getting Ethereum address: {}", e); @@ -386,6 +404,300 @@ fn get_ethereum_address() -> String { } } +// Peaq network operations +fn create_peaq_wallet() -> bool { + match ethereum::create_peaq_wallet() { + Ok(_) => true, + Err(e) => { + log::error!("Error creating Peaq wallet: {}", e); + false + } + } +} + +fn get_peaq_address() -> String { + match ethereum::get_current_peaq_wallet() { + Ok(wallet) => wallet.address_string(), + Err(e) => { + log::error!("Error getting Peaq address: {}", e); + String::new() + } + } +} + +// Agung testnet operations +fn create_agung_wallet() -> bool { + match ethereum::create_agung_wallet() { + Ok(_) => true, + Err(e) => { + log::error!("Error creating Agung wallet: {}", e); + false + } + } +} + +fn get_agung_address() -> String { + match ethereum::get_current_agung_wallet() { + Ok(wallet) => wallet.address_string(), + Err(e) => { + log::error!("Error getting Agung address: {}", e); + String::new() + } + } +} + +// Generic network operations +fn create_wallet_for_network(network_name: &str) -> bool { + let network = match ethereum::networks::get_network_by_name(network_name) { + Some(network) => network, + None => { + log::error!("Unknown network: {}", network_name); + return false; + } + }; + + match ethereum::create_ethereum_wallet_for_network(network) { + Ok(_) => true, + Err(e) => { + log::error!("Error creating wallet for network {}: {}", network_name, e); + false + } + } +} + +// Get wallet address for a specific network +fn get_wallet_address_for_network(network_name: &str) -> String { + let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { + Some(name) => name, + None => { + log::error!("Unknown network: {}", network_name); + return String::new(); + } + }; + + match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { + Ok(wallet) => wallet.address_string(), + Err(e) => { + log::error!("Error getting wallet address for network {}: {}", network_name, e); + String::new() + } + } +} + +// Clear wallets for a specific network +fn clear_wallets_for_network(network_name: &str) -> bool { + let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { + Some(name) => name, + None => { + log::error!("Unknown network: {}", network_name); + return false; + } + }; + + ethereum::clear_ethereum_wallets_for_network(network_name_proper); + true +} + +// List supported networks +fn list_supported_networks() -> rhai::Array { + let mut arr = rhai::Array::new(); + for name in ethereum::networks::list_network_names() { + arr.push(Dynamic::from(name.to_lowercase())); + } + arr +} + +// Get network token symbol +fn get_network_token_symbol(network_name: &str) -> String { + match ethereum::networks::get_network_by_name(network_name) { + Some(network) => network.token_symbol, + None => { + log::error!("Unknown network: {}", network_name); + String::new() + } + } +} + +// Get network explorer URL +fn get_network_explorer_url(network_name: &str) -> String { + match ethereum::networks::get_network_by_name(network_name) { + Some(network) => network.explorer_url, + None => { + log::error!("Unknown network: {}", network_name); + String::new() + } + } +} + +// Create a wallet from a private key for a specific network +fn create_wallet_from_private_key_for_network(private_key: &str, network_name: &str) -> bool { + let network = match ethereum::networks::get_network_by_name(network_name) { + Some(network) => network, + None => { + log::error!("Unknown network: {}", network_name); + return false; + } + }; + + match ethereum::create_ethereum_wallet_from_private_key_for_network(private_key, network) { + Ok(_) => true, + Err(e) => { + log::error!("Error creating wallet from private key for network {}: {}", network_name, e); + false + } + } +} + +// Create a provider for the Agung network +fn create_agung_provider() -> String { + match ethereum::create_agung_provider() { + Ok(provider) => { + // Generate a unique ID for the provider + let id = format!("provider_{}", uuid::Uuid::new_v4()); + + // Store the provider in the registry + if let Ok(mut providers) = PROVIDERS.lock() { + providers.insert(id.clone(), provider); + return id; + } + + log::error!("Failed to acquire provider registry lock"); + String::new() + }, + Err(e) => { + log::error!("Error creating Agung provider: {}", e); + String::new() + } + } +} + +// Get the balance of an address on a specific network +fn get_balance(network_name: &str, address: &str) -> String { + // Get the runtime + let rt = match RUNTIME.lock() { + Ok(rt) => rt, + Err(e) => { + log::error!("Failed to acquire runtime lock: {}", e); + return String::new(); + } + }; + + // Parse the address + let addr = match Address::from_str(address) { + Ok(addr) => addr, + Err(e) => { + log::error!("Invalid address format: {}", e); + return String::new(); + } + }; + + // Get the proper network name + let network_name_proper = match ethereum::networks::get_proper_network_name(network_name) { + Some(name) => name, + None => { + log::error!("Unknown network: {}", network_name); + return String::new(); + } + }; + + // Get the network config + let network = match ethereum::networks::get_network_by_name(network_name_proper) { + Some(n) => n, + None => { + log::error!("Failed to get network config for: {}", network_name_proper); + return String::new(); + } + }; + + // Create a provider + let provider = match ethereum::create_provider(&network) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to create provider: {}", e); + return String::new(); + } + }; + + // Execute the balance query in a blocking manner + match rt.block_on(async { + ethereum::get_balance(&provider, addr).await + }) { + Ok(balance) => balance.to_string(), + Err(e) => { + log::error!("Failed to get balance: {}", e); + String::new() + } + } +} + +// Send ETH from one address to another using the blocking approach +fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String { + // Get the runtime + let rt = match RUNTIME.lock() { + Ok(rt) => rt, + Err(e) => { + log::error!("Failed to acquire runtime lock: {}", e); + return String::new(); + } + }; + + // Parse the address + let to_addr = match Address::from_str(to_address) { + Ok(addr) => addr, + Err(e) => { + log::error!("Invalid address format: {}", e); + return String::new(); + } + }; + + // Parse the amount (using string to handle large numbers) + let amount = match U256::from_dec_str(amount_str) { + Ok(amt) => amt, + Err(e) => { + log::error!("Invalid amount format: {}", e); + return String::new(); + } + }; + + // Get the proper network name + let network_name_proper = match ethereum::networks::get_proper_network_name(wallet_network) { + Some(name) => name, + None => { + log::error!("Unknown network: {}", wallet_network); + return String::new(); + } + }; + + // Get the wallet + let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) { + Ok(w) => w, + Err(e) => { + log::error!("Failed to get wallet: {}", e); + return String::new(); + } + }; + + // Create a provider + let provider = match ethereum::create_provider(&wallet.network) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to create provider: {}", e); + return String::new(); + } + }; + + // Execute the transaction in a blocking manner + match rt.block_on(async { + ethereum::send_eth(&wallet, &provider, to_addr, amount).await + }) { + Ok(tx_hash) => format!("{:?}", tx_hash), + Err(e) => { + log::error!("Transaction failed: {}", e); + String::new() + } + } +} + /// Register crypto functions with the Rhai engine pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box> { // Register key space functions @@ -408,9 +720,31 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box Result<(), Box> { rfs::register(engine)?; // Register Crypto module functions - crypto::register_crypto_module(engine)?; + hero_vault::register_crypto_module(engine)?; // Future modules can be registered here Ok(()) -} \ No newline at end of file +}