refactor and add peaq support
This commit is contained in:
parent
98ab2e1536
commit
07390c3cae
@ -36,6 +36,9 @@ sha2 = "0.10.7" # SHA-2 hash functions
|
|||||||
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
|
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
|
||||||
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
|
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
|
||||||
dirs = "5.0.1" # Directory paths
|
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
|
# Optional features for specific OS functionality
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
@ -46,6 +49,8 @@ windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Thre
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.5" # For tests that need temporary files/directories
|
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]]
|
[[bin]]
|
||||||
name = "herodo"
|
name = "herodo"
|
||||||
|
104
examples/hero_vault/agung_send_transaction.rhai
Normal file
104
examples/hero_vault/agung_send_transaction.rhai
Normal file
@ -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");
|
@ -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<SigningKey>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EthereumWallet {
|
|
||||||
/// Creates a new Ethereum wallet from a keypair.
|
|
||||||
pub fn from_keypair(keypair: &KeyPair) -> Result<Self, CryptoError> {
|
|
||||||
// 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<Self, CryptoError> {
|
|
||||||
// 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<Self, CryptoError> {
|
|
||||||
// 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<String, CryptoError> {
|
|
||||||
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<Mutex<Vec<EthereumWallet>>> = Lazy::new(|| {
|
|
||||||
Mutex::new(Vec::new())
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Creates an Ethereum wallet from the currently selected keypair.
|
|
||||||
pub fn create_ethereum_wallet() -> Result<EthereumWallet, CryptoError> {
|
|
||||||
// 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<EthereumWallet, CryptoError> {
|
|
||||||
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<Http>, address: Address) -> Result<U256, CryptoError> {
|
|
||||||
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<Http>,
|
|
||||||
to: Address,
|
|
||||||
amount: U256,
|
|
||||||
) -> Result<H256, CryptoError> {
|
|
||||||
// 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<Provider<Http>, CryptoError> {
|
|
||||||
Provider::<Http>::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<EthereumWallet, CryptoError> {
|
|
||||||
// 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<EthereumWallet, CryptoError> {
|
|
||||||
// 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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
};
|
|
@ -47,4 +47,4 @@ impl From<CryptoError> for crate::Error {
|
|||||||
fn from(err: CryptoError) -> Self {
|
fn from(err: CryptoError) -> Self {
|
||||||
crate::Error::Sal(err.to_string())
|
crate::Error::Sal(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
66
src/hero_vault/ethereum/mod.rs
Normal file
66
src/hero_vault/ethereum/mod.rs
Normal file
@ -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,
|
||||||
|
};
|
101
src/hero_vault/ethereum/networks.rs
Normal file
101
src/hero_vault/ethereum/networks.rs
Normal file
@ -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<NetworkConfig> {
|
||||||
|
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<HashMap<&'static str, NetworkConfig>> = 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
|
||||||
|
})
|
||||||
|
}
|
27
src/hero_vault/ethereum/provider.rs
Normal file
27
src/hero_vault/ethereum/provider.rs
Normal file
@ -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<Provider<Http>, CryptoError> {
|
||||||
|
Provider::<Http>::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<Provider<Http>, CryptoError> {
|
||||||
|
create_provider(&networks::gnosis())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a provider for the Peaq network.
|
||||||
|
pub fn create_peaq_provider() -> Result<Provider<Http>, CryptoError> {
|
||||||
|
create_provider(&networks::peaq())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a provider for the Agung testnet.
|
||||||
|
pub fn create_agung_provider() -> Result<Provider<Http>, CryptoError> {
|
||||||
|
create_provider(&networks::agung())
|
||||||
|
}
|
114
src/hero_vault/ethereum/storage.rs
Normal file
114
src/hero_vault/ethereum/storage.rs
Normal file
@ -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<Mutex<HashMap<String, Vec<EthereumWallet>>>> = 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<EthereumWallet, CryptoError> {
|
||||||
|
// 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<EthereumWallet, CryptoError> {
|
||||||
|
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<EthereumWallet, CryptoError> {
|
||||||
|
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<EthereumWallet, CryptoError> {
|
||||||
|
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<EthereumWallet, CryptoError> {
|
||||||
|
get_current_ethereum_wallet_for_network("Peaq")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current Ethereum wallet for the Agung testnet.
|
||||||
|
pub fn get_current_agung_wallet() -> Result<EthereumWallet, CryptoError> {
|
||||||
|
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<EthereumWallet, CryptoError> {
|
||||||
|
// 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<EthereumWallet, CryptoError> {
|
||||||
|
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<EthereumWallet, CryptoError> {
|
||||||
|
// 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<EthereumWallet, CryptoError> {
|
||||||
|
create_ethereum_wallet_from_private_key_for_network(private_key, networks::gnosis())
|
||||||
|
}
|
5
src/hero_vault/ethereum/tests/mod.rs
Normal file
5
src/hero_vault/ethereum/tests/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//! Tests for Ethereum functionality.
|
||||||
|
|
||||||
|
mod wallet_tests;
|
||||||
|
mod network_tests;
|
||||||
|
mod transaction_tests;
|
74
src/hero_vault/ethereum/tests/network_tests.rs
Normal file
74
src/hero_vault/ethereum/tests/network_tests.rs
Normal file
@ -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());
|
||||||
|
}
|
70
src/hero_vault/ethereum/tests/transaction_tests.rs
Normal file
70
src/hero_vault/ethereum/tests/transaction_tests.rs
Normal file
@ -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
|
||||||
|
}
|
143
src/hero_vault/ethereum/tests/wallet_tests.rs
Normal file
143
src/hero_vault/ethereum/tests/wallet_tests.rs
Normal file
@ -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();
|
||||||
|
}
|
54
src/hero_vault/ethereum/transaction.rs
Normal file
54
src/hero_vault/ethereum/transaction.rs
Normal file
@ -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<Http>, address: Address) -> Result<U256, CryptoError> {
|
||||||
|
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<Http>,
|
||||||
|
to: Address,
|
||||||
|
amount: U256,
|
||||||
|
) -> Result<H256, CryptoError> {
|
||||||
|
// 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())
|
||||||
|
}
|
114
src/hero_vault/ethereum/wallet.rs
Normal file
114
src/hero_vault/ethereum/wallet.rs
Normal file
@ -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<SigningKey>,
|
||||||
|
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<Self, CryptoError> {
|
||||||
|
// 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<Self, CryptoError> {
|
||||||
|
// 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<Self, CryptoError> {
|
||||||
|
// 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<String, CryptoError> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ use once_cell::sync::Lazy;
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
use crate::crypto::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
|
|
||||||
/// A keypair for signing and verifying messages.
|
/// A keypair for signing and verifying messages.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -226,7 +226,7 @@ impl KeyPair {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Encrypt the message using the derived key
|
// 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()))?;
|
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
|
||||||
|
|
||||||
// Format: ephemeral_public_key || ciphertext
|
// Format: ephemeral_public_key || ciphertext
|
||||||
@ -263,7 +263,7 @@ impl KeyPair {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt the message using the derived key
|
// 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()))
|
.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<Vec<u8>, CryptoError> {
|
pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||||
let keypair = get_selected_keypair()?;
|
let keypair = get_selected_keypair()?;
|
||||||
keypair.decrypt_asymmetric(ciphertext)
|
keypair.decrypt_asymmetric(ciphertext)
|
||||||
}
|
}
|
@ -11,4 +11,4 @@ pub use implementation::{
|
|||||||
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
|
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
|
||||||
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
|
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
|
||||||
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
|
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
|
||||||
};
|
};
|
@ -45,22 +45,22 @@ impl From<serde_json::Error> for KvsError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<KvsError> for crate::crypto::error::CryptoError {
|
impl From<KvsError> for crate::hero_vault::error::CryptoError {
|
||||||
fn from(err: KvsError) -> Self {
|
fn from(err: KvsError) -> Self {
|
||||||
crate::crypto::error::CryptoError::SerializationError(err.to_string())
|
crate::hero_vault::error::CryptoError::SerializationError(err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::crypto::error::CryptoError> for KvsError {
|
impl From<crate::hero_vault::error::CryptoError> for KvsError {
|
||||||
fn from(err: crate::crypto::error::CryptoError) -> Self {
|
fn from(err: crate::hero_vault::error::CryptoError) -> Self {
|
||||||
match err {
|
match err {
|
||||||
crate::crypto::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
|
crate::hero_vault::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
|
||||||
crate::crypto::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
|
crate::hero_vault::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
|
||||||
crate::crypto::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg),
|
crate::hero_vault::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg),
|
||||||
_ => KvsError::Other(err.to_string()),
|
_ => KvsError::Other(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type for key-value store operations.
|
/// Result type for key-value store operations.
|
||||||
pub type Result<T> = std::result::Result<T, KvsError>;
|
pub type Result<T> = std::result::Result<T, KvsError>;
|
@ -11,4 +11,4 @@ pub use store::{
|
|||||||
KvStore, KvPair,
|
KvStore, KvPair,
|
||||||
create_store, open_store, delete_store,
|
create_store, open_store, delete_store,
|
||||||
list_stores, get_store_path
|
list_stores, get_store_path
|
||||||
};
|
};
|
@ -1,7 +1,7 @@
|
|||||||
//! Implementation of a simple key-value store using the filesystem.
|
//! Implementation of a simple key-value store using the filesystem.
|
||||||
|
|
||||||
use crate::crypto::kvs::error::{KvsError, Result};
|
use crate::hero_vault::kvs::error::{KvsError, Result};
|
||||||
use crate::crypto::symmetric;
|
use crate::hero_vault::symmetric;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
@ -1,7 +1,9 @@
|
|||||||
//! Cryptographic functionality for SAL
|
//! Hero Vault: Cryptographic functionality for SAL
|
||||||
//!
|
//!
|
||||||
//! This module provides cryptographic operations including:
|
//! This module provides cryptographic operations including:
|
||||||
|
//! - Key space management (creation, loading, encryption, decryption)
|
||||||
//! - Key pair management (ECDSA)
|
//! - Key pair management (ECDSA)
|
||||||
|
//! - Digital signatures (signing and verification)
|
||||||
//! - Symmetric encryption (ChaCha20Poly1305)
|
//! - Symmetric encryption (ChaCha20Poly1305)
|
||||||
//! - Ethereum wallet functionality
|
//! - Ethereum wallet functionality
|
||||||
//! - Key-value store with encryption
|
//! - Key-value store with encryption
|
||||||
@ -14,4 +16,4 @@ pub mod kvs;
|
|||||||
|
|
||||||
// Re-export common types for convenience
|
// Re-export common types for convenience
|
||||||
pub use error::CryptoError;
|
pub use error::CryptoError;
|
||||||
pub use keypair::{KeyPair, KeySpace};
|
pub use keypair::{KeyPair, KeySpace};
|
@ -6,8 +6,8 @@ use rand::{rngs::OsRng, RngCore};
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
use crate::crypto::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use crate::crypto::keypair::KeySpace;
|
use crate::hero_vault::keypair::KeySpace;
|
||||||
|
|
||||||
/// The size of the nonce in bytes.
|
/// The size of the nonce in bytes.
|
||||||
const NONCE_SIZE: usize = 12;
|
const NONCE_SIZE: usize = 12;
|
||||||
@ -263,4 +263,4 @@ pub fn deserialize_encrypted_space(serialized: &str) -> Result<EncryptedKeySpace
|
|||||||
Err(CryptoError::SerializationError(e.to_string()))
|
Err(CryptoError::SerializationError(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,4 +12,4 @@ pub use implementation::{
|
|||||||
encrypt_key_space, decrypt_key_space,
|
encrypt_key_space, decrypt_key_space,
|
||||||
serialize_encrypted_space, deserialize_encrypted_space,
|
serialize_encrypted_space, deserialize_encrypted_space,
|
||||||
EncryptedKeySpace, EncryptedKeySpaceMetadata
|
EncryptedKeySpace, EncryptedKeySpaceMetadata
|
||||||
};
|
};
|
@ -45,7 +45,7 @@ pub mod text;
|
|||||||
pub mod virt;
|
pub mod virt;
|
||||||
pub mod rhai;
|
pub mod rhai;
|
||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
pub mod crypto;
|
pub mod hero_vault;
|
||||||
|
|
||||||
// Version information
|
// Version information
|
||||||
/// Returns the version of the SAL library
|
/// Returns the version of the SAL library
|
||||||
|
@ -4,10 +4,26 @@ use rhai::{Engine, Dynamic, FnPtr, EvalAltResult};
|
|||||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use ethers::types::{Address, U256};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::crypto::{keypair, symmetric, ethereum};
|
use crate::hero_vault::{keypair, symmetric, ethereum};
|
||||||
use crate::crypto::error::CryptoError;
|
use crate::hero_vault::error::CryptoError;
|
||||||
use crate::crypto::kvs;
|
use crate::hero_vault::kvs;
|
||||||
|
|
||||||
|
// Global Tokio runtime for blocking async operations
|
||||||
|
static RUNTIME: Lazy<Mutex<Runtime>> = Lazy::new(|| {
|
||||||
|
Mutex::new(Runtime::new().expect("Failed to create Tokio runtime"))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global provider registry
|
||||||
|
static PROVIDERS: Lazy<Mutex<HashMap<String, ethers::providers::Provider<ethers::providers::Http>>>> = Lazy::new(|| {
|
||||||
|
Mutex::new(HashMap::new())
|
||||||
|
});
|
||||||
|
|
||||||
// Key space management functions
|
// Key space management functions
|
||||||
fn load_key_space(name: &str, password: &str) -> bool {
|
fn load_key_space(name: &str, password: &str) -> bool {
|
||||||
@ -366,8 +382,10 @@ fn decrypt(key: &str, ciphertext: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ethereum operations
|
// Ethereum operations
|
||||||
|
|
||||||
|
// Gnosis Chain operations
|
||||||
fn create_ethereum_wallet() -> bool {
|
fn create_ethereum_wallet() -> bool {
|
||||||
match ethereum::create_ethereum_wallet() {
|
match ethereum::create_ethereum_wallet_for_network(ethereum::networks::gnosis()) {
|
||||||
Ok(_) => true,
|
Ok(_) => true,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Error creating Ethereum wallet: {}", e);
|
log::error!("Error creating Ethereum wallet: {}", e);
|
||||||
@ -377,7 +395,7 @@ fn create_ethereum_wallet() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_ethereum_address() -> String {
|
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(),
|
Ok(wallet) => wallet.address_string(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Error getting Ethereum address: {}", 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
|
/// Register crypto functions with the Rhai engine
|
||||||
pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
// Register key space functions
|
// Register key space functions
|
||||||
@ -408,9 +720,31 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
|
|||||||
engine.register_fn("encrypt", encrypt);
|
engine.register_fn("encrypt", encrypt);
|
||||||
engine.register_fn("decrypt", decrypt);
|
engine.register_fn("decrypt", decrypt);
|
||||||
|
|
||||||
// Register Ethereum functions
|
// Register Ethereum functions (Gnosis Chain)
|
||||||
engine.register_fn("create_ethereum_wallet", create_ethereum_wallet);
|
engine.register_fn("create_ethereum_wallet", create_ethereum_wallet);
|
||||||
engine.register_fn("get_ethereum_address", get_ethereum_address);
|
engine.register_fn("get_ethereum_address", get_ethereum_address);
|
||||||
|
|
||||||
|
// Register Peaq network functions
|
||||||
|
engine.register_fn("create_peaq_wallet", create_peaq_wallet);
|
||||||
|
engine.register_fn("get_peaq_address", get_peaq_address);
|
||||||
|
|
||||||
|
// Register Agung testnet functions
|
||||||
|
engine.register_fn("create_agung_wallet", create_agung_wallet);
|
||||||
|
engine.register_fn("get_agung_address", get_agung_address);
|
||||||
|
|
||||||
|
// Register generic network functions
|
||||||
|
engine.register_fn("create_wallet_for_network", create_wallet_for_network);
|
||||||
|
engine.register_fn("get_wallet_address_for_network", get_wallet_address_for_network);
|
||||||
|
engine.register_fn("clear_wallets_for_network", clear_wallets_for_network);
|
||||||
|
engine.register_fn("list_supported_networks", list_supported_networks);
|
||||||
|
engine.register_fn("get_network_token_symbol", get_network_token_symbol);
|
||||||
|
engine.register_fn("get_network_explorer_url", get_network_explorer_url);
|
||||||
|
|
||||||
|
// Register new Ethereum functions for wallet creation from private key and transactions
|
||||||
|
engine.register_fn("create_wallet_from_private_key_for_network", create_wallet_from_private_key_for_network);
|
||||||
|
engine.register_fn("create_agung_provider", create_agung_provider);
|
||||||
|
engine.register_fn("send_eth", send_eth);
|
||||||
|
engine.register_fn("get_balance", get_balance);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ mod nerdctl;
|
|||||||
mod git;
|
mod git;
|
||||||
mod text;
|
mod text;
|
||||||
mod rfs;
|
mod rfs;
|
||||||
mod crypto;
|
mod hero_vault; // This module now uses hero_vault internally
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@ -75,7 +75,7 @@ pub use crate::text::{
|
|||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
||||||
// Re-export crypto module
|
// Re-export crypto module
|
||||||
pub use crypto::register_crypto_module;
|
pub use hero_vault::register_crypto_module;
|
||||||
|
|
||||||
// Rename copy functions to avoid conflicts
|
// Rename copy functions to avoid conflicts
|
||||||
pub use os::copy as os_copy;
|
pub use os::copy as os_copy;
|
||||||
@ -121,10 +121,10 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
|
|||||||
rfs::register(engine)?;
|
rfs::register(engine)?;
|
||||||
|
|
||||||
// Register Crypto module functions
|
// Register Crypto module functions
|
||||||
crypto::register_crypto_module(engine)?;
|
hero_vault::register_crypto_module(engine)?;
|
||||||
|
|
||||||
// Future modules can be registered here
|
// Future modules can be registered here
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user