...
This commit is contained in:
@@ -34,6 +34,12 @@ pub enum CryptoError {
|
||||
InvalidPassword,
|
||||
/// Error during serialization or deserialization.
|
||||
SerializationError,
|
||||
/// No Ethereum wallet is available.
|
||||
NoEthereumWallet,
|
||||
/// Ethereum transaction failed.
|
||||
EthereumTransactionFailed,
|
||||
/// Invalid Ethereum address.
|
||||
InvalidEthereumAddress,
|
||||
/// Other error with description.
|
||||
#[allow(dead_code)]
|
||||
Other(String),
|
||||
@@ -57,6 +63,9 @@ impl std::fmt::Display for CryptoError {
|
||||
CryptoError::SpaceAlreadyExists => write!(f, "Space already exists"),
|
||||
CryptoError::InvalidPassword => write!(f, "Invalid password"),
|
||||
CryptoError::SerializationError => write!(f, "Serialization error"),
|
||||
CryptoError::NoEthereumWallet => write!(f, "No Ethereum wallet available"),
|
||||
CryptoError::EthereumTransactionFailed => write!(f, "Ethereum transaction failed"),
|
||||
CryptoError::InvalidEthereumAddress => write!(f, "Invalid Ethereum address"),
|
||||
CryptoError::Other(s) => write!(f, "Crypto error: {}", s),
|
||||
}
|
||||
}
|
||||
@@ -82,6 +91,9 @@ pub fn error_to_status_code(err: CryptoError) -> i32 {
|
||||
CryptoError::SpaceAlreadyExists => -13,
|
||||
CryptoError::InvalidPassword => -14,
|
||||
CryptoError::SerializationError => -15,
|
||||
CryptoError::NoEthereumWallet => -16,
|
||||
CryptoError::EthereumTransactionFailed => -17,
|
||||
CryptoError::InvalidEthereumAddress => -18,
|
||||
CryptoError::Other(_) => -99,
|
||||
}
|
||||
}
|
228
src/core/ethereum.rs
Normal file
228
src/core/ethereum.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
//! Core 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 super::error::CryptoError;
|
||||
use super::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(|_| 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(|_| 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(|_| 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(|_| CryptoError::SignatureFormatError)?;
|
||||
|
||||
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 = super::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(|_| CryptoError::Other("Failed to get balance".to_string()))
|
||||
}
|
||||
|
||||
/// 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(|_| CryptoError::Other("Failed to send transaction".to_string()))?;
|
||||
|
||||
// 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(|_| CryptoError::Other("Failed to create Gnosis provider".to_string()))
|
||||
}
|
||||
|
||||
/// 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 = super::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)
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
pub mod error;
|
||||
pub mod keypair;
|
||||
pub mod symmetric;
|
||||
pub mod ethereum;
|
||||
|
||||
// Re-export commonly used items for internal use
|
||||
// (Keeping this even though it's currently unused, as it's good practice for internal modules)
|
||||
|
Reference in New Issue
Block a user