feat: Improve Ethereum module design and add network registry
This commit is contained in:
parent
feb0a619f7
commit
22373a4339
125
examples/hero_vault/add_custom_network.rhai
Normal file
125
examples/hero_vault/add_custom_network.rhai
Normal file
@ -0,0 +1,125 @@
|
||||
// Example script demonstrating how to add a custom network and use it
|
||||
// This script shows the new network-independent wallet design
|
||||
|
||||
// Load a key space (or create one if it doesn't exist)
|
||||
if (!load_key_space("demo", "password123")) {
|
||||
print("Creating new key space...");
|
||||
create_key_space("demo", "password123");
|
||||
}
|
||||
|
||||
// Always create a keypair (will be a no-op if it already exists)
|
||||
print("Creating keypair...");
|
||||
create_keypair("demo_key", "password123");
|
||||
|
||||
// Select the keypair
|
||||
print("Selecting keypair...");
|
||||
select_keypair("demo_key");
|
||||
|
||||
// Create an Ethereum wallet (network-independent)
|
||||
print("Creating Ethereum wallet...");
|
||||
create_ethereum_wallet();
|
||||
|
||||
// Get the wallet address (same for all networks)
|
||||
let address = get_ethereum_address();
|
||||
print(`Your Ethereum address: ${address}`);
|
||||
|
||||
// List the built-in networks
|
||||
print("\nBuilt-in networks:");
|
||||
let networks = list_supported_networks();
|
||||
for network in networks {
|
||||
print(`- ${network}`);
|
||||
}
|
||||
|
||||
// Register a custom network (Sepolia testnet)
|
||||
print("\nRegistering Sepolia testnet...");
|
||||
let success = register_network(
|
||||
"Sepolia",
|
||||
11155111,
|
||||
"https://sepolia.blast.io", // Using Blast.io's public RPC endpoint
|
||||
"https://sepolia.etherscan.io",
|
||||
"ETH",
|
||||
18
|
||||
);
|
||||
|
||||
if (success) {
|
||||
print("Sepolia testnet registered successfully!");
|
||||
} else {
|
||||
print("Failed to register Sepolia testnet!");
|
||||
}
|
||||
|
||||
// List networks again to confirm Sepolia was added
|
||||
print("\nUpdated networks list:");
|
||||
networks = list_supported_networks();
|
||||
for network in networks {
|
||||
print(`- ${network}`);
|
||||
}
|
||||
|
||||
// Get network details
|
||||
print("\nSepolia network details:");
|
||||
print(`Token symbol: ${get_network_token_symbol("sepolia")}`);
|
||||
print(`Explorer URL: ${get_network_explorer_url("sepolia")}`);
|
||||
|
||||
// Check balance on different networks
|
||||
print("\nChecking balances on different networks...");
|
||||
print(`NOTE: These will likely show zero unless you've funded the address`);
|
||||
print(`NOTE: Balance checks may fail if the RPC endpoints are not accessible`);
|
||||
|
||||
// Check balance on Sepolia (this might fail if the RPC endpoint is not accessible)
|
||||
print("\nTrying to get balance on Sepolia...");
|
||||
let sepolia_balance = get_balance("sepolia", address);
|
||||
if (sepolia_balance == "") {
|
||||
print("Failed to get balance on Sepolia (this is expected if you don't have access to the Sepolia RPC)");
|
||||
} else {
|
||||
print(`Balance on Sepolia: ${sepolia_balance}`);
|
||||
}
|
||||
|
||||
// Check balance on Gnosis
|
||||
print("\nTrying to get balance on Gnosis...");
|
||||
let gnosis_balance = get_balance("gnosis", address);
|
||||
if (gnosis_balance == "") {
|
||||
print("Failed to get balance on Gnosis");
|
||||
} else {
|
||||
print(`Balance on Gnosis: ${gnosis_balance}`);
|
||||
}
|
||||
|
||||
// Check balance on Peaq
|
||||
print("\nTrying to get balance on Peaq...");
|
||||
let peaq_balance = get_balance("peaq", address);
|
||||
if (peaq_balance == "") {
|
||||
print("Failed to get balance on Peaq");
|
||||
} else {
|
||||
print(`Balance on Peaq: ${peaq_balance}`);
|
||||
}
|
||||
|
||||
// Demonstrate sending a transaction (commented out to prevent accidental execution)
|
||||
// To execute this, uncomment the following lines and make sure your wallet has funds
|
||||
/*
|
||||
print("\nSending 0.001 ETH on Sepolia...");
|
||||
let recipient = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"; // Example address
|
||||
let amount = "1000000000000000"; // 0.001 ETH in wei
|
||||
let tx_hash = send_eth("sepolia", recipient, amount);
|
||||
if (tx_hash != "") {
|
||||
print(`Transaction sent! Hash: ${tx_hash}`);
|
||||
print(`View on Sepolia Explorer: ${get_network_explorer_url("sepolia")}/tx/${tx_hash}`);
|
||||
} else {
|
||||
print("Transaction failed!");
|
||||
}
|
||||
*/
|
||||
|
||||
// Remove the custom network
|
||||
print("\nRemoving Sepolia network...");
|
||||
success = remove_network("Sepolia");
|
||||
if (success) {
|
||||
print("Sepolia network removed successfully!");
|
||||
} else {
|
||||
print("Failed to remove Sepolia network!");
|
||||
}
|
||||
|
||||
// List networks again to confirm Sepolia was removed
|
||||
print("\nFinal networks list:");
|
||||
networks = list_supported_networks();
|
||||
for network in networks {
|
||||
print(`- ${network}`);
|
||||
}
|
||||
|
||||
print("\nDone!");
|
@ -21,25 +21,25 @@ The Ethereum module is organized into several components:
|
||||
The module provides functionality for creating and managing Ethereum wallets:
|
||||
|
||||
```rust
|
||||
// Create a new Ethereum wallet for a specific network
|
||||
let wallet = create_ethereum_wallet_for_network("Ethereum")?;
|
||||
|
||||
// Create a wallet for specific networks
|
||||
let peaq_wallet = create_peaq_wallet()?;
|
||||
let agung_wallet = create_agung_wallet()?;
|
||||
// Create a network-independent Ethereum wallet
|
||||
let wallet = create_ethereum_wallet()?;
|
||||
|
||||
// Create a wallet with a specific name
|
||||
let named_wallet = create_ethereum_wallet_from_name_for_network("my_wallet", "Gnosis")?;
|
||||
let named_wallet = create_ethereum_wallet_from_name("my_wallet")?;
|
||||
|
||||
// Create a wallet from a private key
|
||||
let imported_wallet = create_ethereum_wallet_from_private_key("0x...")?;
|
||||
|
||||
// Get the current wallet for a network
|
||||
let current_wallet = get_current_ethereum_wallet_for_network("Ethereum")?;
|
||||
// Get the current wallet
|
||||
let current_wallet = get_current_ethereum_wallet()?;
|
||||
|
||||
// Clear wallets
|
||||
clear_ethereum_wallets()?;
|
||||
clear_ethereum_wallets_for_network("Gnosis")?;
|
||||
|
||||
// Legacy functions for backward compatibility
|
||||
let wallet = create_ethereum_wallet_for_network(network)?;
|
||||
let peaq_wallet = create_peaq_wallet()?;
|
||||
let agung_wallet = create_agung_wallet()?;
|
||||
```
|
||||
|
||||
### Network Management
|
||||
@ -47,6 +47,12 @@ clear_ethereum_wallets_for_network("Gnosis")?;
|
||||
The module supports multiple Ethereum networks and provides functionality for managing network configurations:
|
||||
|
||||
```rust
|
||||
// Register a new network
|
||||
register_network("Arbitrum", 42161, "https://arb1.arbitrum.io/rpc", "https://arbiscan.io", "ETH", 18);
|
||||
|
||||
// Remove a network
|
||||
remove_network("Arbitrum");
|
||||
|
||||
// Get a network configuration by name
|
||||
let network = get_network_by_name("Ethereum")?;
|
||||
|
||||
@ -68,7 +74,10 @@ The module provides functionality for creating and managing Ethereum providers:
|
||||
// Create a provider for a specific network
|
||||
let provider = create_provider("Ethereum")?;
|
||||
|
||||
// Create providers for specific networks
|
||||
// Create a provider from a network configuration
|
||||
let provider = create_provider_from_config(&network)?;
|
||||
|
||||
// Legacy functions for backward compatibility
|
||||
let gnosis_provider = create_gnosis_provider()?;
|
||||
let peaq_provider = create_peaq_provider()?;
|
||||
let agung_provider = create_agung_provider()?;
|
||||
@ -79,14 +88,20 @@ let agung_provider = create_agung_provider()?;
|
||||
The module provides functionality for managing Ethereum transactions:
|
||||
|
||||
```rust
|
||||
// Get the balance of an address
|
||||
let balance = get_balance("Ethereum", "0x...")?;
|
||||
// Get the balance of an address on a specific network
|
||||
let balance = get_balance("Ethereum", address).await?;
|
||||
|
||||
// Send ETH to an address
|
||||
let tx_hash = send_eth("Ethereum", "0x...", "1000000000000000")?;
|
||||
// Get the balance using a provider
|
||||
let balance = get_balance_with_provider(&provider, address).await?;
|
||||
|
||||
// Send ETH to an address on a specific network
|
||||
let tx_hash = send_eth(&wallet, "Ethereum", to_address, amount).await?;
|
||||
|
||||
// Legacy function for backward compatibility
|
||||
let tx_hash = send_eth_with_provider(&wallet, &provider, to_address, amount).await?;
|
||||
|
||||
// Format a balance for display
|
||||
let formatted = format_balance(balance, 18)?; // Convert wei to ETH
|
||||
let formatted = format_balance(balance, &network);
|
||||
```
|
||||
|
||||
### Smart Contract Interactions
|
||||
@ -98,16 +113,16 @@ The module provides functionality for interacting with smart contracts:
|
||||
let abi = load_abi_from_json(json_string)?;
|
||||
|
||||
// Create a contract instance
|
||||
let contract = Contract::new(provider, "0x...", abi)?;
|
||||
let contract = Contract::new(address, abi, network);
|
||||
|
||||
// Call a read-only function
|
||||
let result = call_read_function(contract, "balanceOf", vec!["0x..."])?;
|
||||
let result = call_read_function(&contract, &provider, "balanceOf", tokens).await?;
|
||||
|
||||
// Call a write function
|
||||
let tx_hash = call_write_function(contract, "transfer", vec!["0x...", "1000"])?;
|
||||
let tx_hash = call_write_function(&contract, &wallet, &provider, "transfer", tokens).await?;
|
||||
|
||||
// Estimate gas for a function call
|
||||
let gas = estimate_gas(contract, "transfer", vec!["0x...", "1000"])?;
|
||||
let gas = estimate_gas(&contract, &provider, "transfer", tokens).await?;
|
||||
```
|
||||
|
||||
### Contract Utilities
|
||||
@ -119,29 +134,49 @@ The module provides utilities for working with contract function arguments and r
|
||||
let token = convert_rhai_to_token(value)?;
|
||||
|
||||
// Prepare function arguments
|
||||
let args = prepare_function_arguments(function, vec![arg1, arg2])?;
|
||||
let args = prepare_function_arguments(&abi, function_name, &args)?;
|
||||
|
||||
// Convert Ethereum tokens to Rhai values
|
||||
let rhai_value = convert_token_to_rhai(token)?;
|
||||
let rhai_value = convert_token_to_rhai(&token)?;
|
||||
|
||||
// Convert a token to a dynamic value
|
||||
let dynamic = token_to_dynamic(token)?;
|
||||
let dynamic = token_to_dynamic(&token)?;
|
||||
```
|
||||
|
||||
## Supported Networks
|
||||
## Network Registry
|
||||
|
||||
The module supports multiple Ethereum networks, including:
|
||||
The module now includes a centralized network registry that allows for dynamic management of EVM-compatible networks:
|
||||
|
||||
- Gnosis Chain
|
||||
- Peaq Network
|
||||
- Agung Network
|
||||
```rust
|
||||
// Built-in networks
|
||||
- Gnosis Chain (chain_id: 100, token: xDAI)
|
||||
- Peaq Network (chain_id: 3338, token: PEAQ)
|
||||
- Agung Network (chain_id: 9990, token: AGNG)
|
||||
|
||||
Each network has its own configuration, including:
|
||||
// Register a custom network at runtime
|
||||
register_network(
|
||||
"Polygon",
|
||||
137,
|
||||
"https://polygon-rpc.com",
|
||||
"https://polygonscan.com",
|
||||
"MATIC",
|
||||
18
|
||||
);
|
||||
```
|
||||
|
||||
- RPC URL
|
||||
- Chain ID
|
||||
- Explorer URL
|
||||
- Native currency symbol and decimals
|
||||
## Wallet Address Consistency
|
||||
|
||||
In the new design, Ethereum wallet addresses are consistent across all EVM-compatible networks. This reflects how Ethereum addresses work in reality - the same private key generates the same address on all EVM chains.
|
||||
|
||||
```rust
|
||||
// Create a wallet once
|
||||
let wallet = create_ethereum_wallet()?;
|
||||
|
||||
// Use the same wallet address on any network
|
||||
let eth_balance = get_balance("Ethereum", wallet.address).await?;
|
||||
let polygon_balance = get_balance("Polygon", wallet.address).await?;
|
||||
let gnosis_balance = get_balance("Gnosis", wallet.address).await?;
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
@ -149,6 +184,8 @@ The module uses the `CryptoError` type for handling errors that can occur during
|
||||
|
||||
- `InvalidAddress` - Invalid Ethereum address format
|
||||
- `ContractError` - Smart contract interaction error
|
||||
- `NoKeypairSelected` - No keypair selected for wallet creation
|
||||
- `InvalidKeyLength` - Invalid private key length
|
||||
|
||||
## Examples
|
||||
|
||||
@ -158,3 +195,31 @@ For examples of how to use the Ethereum module, see the `examples/hero_vault` di
|
||||
- `agung_simple_transfer.rhai` - Shows how to perform a simple ETH transfer on the Agung network
|
||||
- `agung_send_transaction.rhai` - Demonstrates sending transactions on the Agung network
|
||||
- `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung
|
||||
|
||||
## Adding a New Network
|
||||
|
||||
With the new design, adding a new network is as simple as registering it with the network registry:
|
||||
|
||||
```rust
|
||||
// In Rust
|
||||
ethereum::register_network(
|
||||
"Optimism",
|
||||
10,
|
||||
"https://mainnet.optimism.io",
|
||||
"https://optimistic.etherscan.io",
|
||||
"ETH",
|
||||
18
|
||||
);
|
||||
|
||||
// In Rhai
|
||||
register_network(
|
||||
"Optimism",
|
||||
10,
|
||||
"https://mainnet.optimism.io",
|
||||
"https://optimistic.etherscan.io",
|
||||
"ETH",
|
||||
18
|
||||
);
|
||||
```
|
||||
|
||||
No code changes are required to add support for new networks!
|
||||
|
@ -42,7 +42,7 @@ impl Contract {
|
||||
}
|
||||
|
||||
/// Creates an ethers Contract instance for interaction.
|
||||
pub fn create_ethers_contract(&self, provider: Provider<Http>, _wallet: Option<&EthereumWallet>) -> Result<ethers::contract::Contract<ethers::providers::Provider<Http>>, CryptoError> {
|
||||
pub fn create_ethers_contract(&self, provider: Provider<Http>) -> Result<ethers::contract::Contract<ethers::providers::Provider<Http>>, CryptoError> {
|
||||
let contract = ethers::contract::Contract::new(
|
||||
self.address,
|
||||
self.abi.clone(),
|
||||
@ -65,9 +65,9 @@ pub async fn call_read_function(
|
||||
provider: &Provider<Http>,
|
||||
function_name: &str,
|
||||
args: Vec<Token>,
|
||||
) -> Result<Vec<Token>, CryptoError> {
|
||||
) -> Result<Token, CryptoError> {
|
||||
// Create the ethers contract (not used directly but kept for future extensions)
|
||||
let _ethers_contract = contract.create_ethers_contract(provider.clone(), None)?;
|
||||
let _ethers_contract = contract.create_ethers_contract(provider.clone())?;
|
||||
|
||||
// Get the function from the ABI
|
||||
let function = contract.abi.function(function_name)
|
||||
@ -89,7 +89,12 @@ pub async fn call_read_function(
|
||||
let decoded = function.decode_output(&result)
|
||||
.map_err(|e| CryptoError::ContractError(format!("Failed to decode function output: {}", e)))?;
|
||||
|
||||
Ok(decoded)
|
||||
// Return the first token if there's only one, otherwise return a tuple
|
||||
if decoded.len() == 1 {
|
||||
Ok(decoded[0].clone())
|
||||
} else {
|
||||
Ok(Token::Tuple(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a state-changing function on a contract.
|
||||
@ -100,10 +105,11 @@ pub async fn call_write_function(
|
||||
function_name: &str,
|
||||
args: Vec<Token>,
|
||||
) -> Result<H256, CryptoError> {
|
||||
// Create a client with the wallet
|
||||
// Create a client with the wallet configured for this network
|
||||
let network_wallet = wallet.for_network(&contract.network);
|
||||
let client = SignerMiddleware::new(
|
||||
provider.clone(),
|
||||
wallet.wallet.clone(),
|
||||
network_wallet,
|
||||
);
|
||||
|
||||
// Get the function from the ABI
|
||||
@ -126,13 +132,14 @@ pub async fn call_write_function(
|
||||
|
||||
// Send the transaction using the client directly
|
||||
log::info!("Sending transaction to contract at {}", contract.address);
|
||||
log::info!("Function: {}, Args: {:?}", function_name, args);
|
||||
log::info!("Function: {}", function_name);
|
||||
|
||||
// Log detailed information about the transaction
|
||||
log::debug!("Sending transaction to contract at {}", contract.address);
|
||||
log::debug!("Function: {}, Args: {:?}", function_name, args);
|
||||
log::debug!("From address: {}", wallet.address);
|
||||
log::debug!("Gas limit: {:?}", tx.gas);
|
||||
log::debug!("Network: {}", contract.network.name);
|
||||
|
||||
let pending_tx = match client.send_transaction(tx, None).await {
|
||||
Ok(pending_tx) => {
|
||||
@ -179,5 +186,8 @@ pub async fn estimate_gas(
|
||||
.await
|
||||
.map_err(|e| CryptoError::ContractError(format!("Failed to estimate gas: {}", e)))?;
|
||||
|
||||
Ok(gas)
|
||||
// Add a buffer to the gas estimate to account for potential variations
|
||||
let gas_with_buffer = gas * 12 / 10; // Add 20% buffer
|
||||
|
||||
Ok(gas_with_buffer)
|
||||
}
|
||||
|
@ -27,27 +27,33 @@ pub use networks::NetworkConfig;
|
||||
|
||||
// Re-export wallet creation functions
|
||||
pub use storage::{
|
||||
create_ethereum_wallet,
|
||||
create_ethereum_wallet_from_name,
|
||||
create_ethereum_wallet_from_private_key,
|
||||
// Legacy functions for backward compatibility
|
||||
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,
|
||||
clear_ethereum_wallets,
|
||||
// Legacy functions for backward compatibility
|
||||
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_provider_from_config,
|
||||
// Legacy functions for backward compatibility
|
||||
create_gnosis_provider,
|
||||
create_peaq_provider,
|
||||
create_agung_provider,
|
||||
@ -56,12 +62,16 @@ pub use provider::{
|
||||
// Re-export transaction functions
|
||||
pub use transaction::{
|
||||
get_balance,
|
||||
get_balance_with_provider,
|
||||
send_eth,
|
||||
send_eth_with_provider,
|
||||
format_balance,
|
||||
};
|
||||
|
||||
// Re-export network registry functions
|
||||
pub use networks::{
|
||||
register_network,
|
||||
remove_network,
|
||||
get_network_by_name,
|
||||
get_proper_network_name,
|
||||
list_network_names,
|
||||
|
@ -4,7 +4,8 @@
|
||||
//! to work with them.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::RwLock;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
/// Configuration for an EVM-compatible network
|
||||
@ -18,6 +19,16 @@ pub struct NetworkConfig {
|
||||
pub decimals: u8,
|
||||
}
|
||||
|
||||
/// Global registry of all supported networks
|
||||
static NETWORK_REGISTRY: Lazy<RwLock<HashMap<String, NetworkConfig>>> = Lazy::new(|| {
|
||||
let mut registry = HashMap::new();
|
||||
|
||||
// Add built-in networks
|
||||
register_built_in_networks(&mut registry);
|
||||
|
||||
RwLock::new(registry)
|
||||
});
|
||||
|
||||
/// Network name constants
|
||||
pub mod names {
|
||||
pub const GNOSIS: &str = "Gnosis";
|
||||
@ -25,50 +36,80 @@ pub mod names {
|
||||
pub const AGUNG: &str = "Agung";
|
||||
}
|
||||
|
||||
/// Get the Gnosis Chain network configuration
|
||||
pub fn gnosis() -> NetworkConfig {
|
||||
NetworkConfig {
|
||||
/// Register all built-in networks
|
||||
fn register_built_in_networks(registry: &mut HashMap<String, NetworkConfig>) {
|
||||
// Gnosis Chain
|
||||
registry.insert(names::GNOSIS.to_lowercase(), 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 {
|
||||
});
|
||||
|
||||
// Peaq Network
|
||||
registry.insert(names::PEAQ.to_lowercase(), NetworkConfig {
|
||||
name: names::PEAQ.to_string(),
|
||||
chain_id: 3338,
|
||||
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 {
|
||||
});
|
||||
|
||||
// Agung Testnet
|
||||
registry.insert(names::AGUNG.to_lowercase(), 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,
|
||||
});
|
||||
}
|
||||
|
||||
/// Register a new network
|
||||
pub fn register_network(
|
||||
name: &str,
|
||||
chain_id: u64,
|
||||
rpc_url: &str,
|
||||
explorer_url: &str,
|
||||
token_symbol: &str,
|
||||
decimals: u8,
|
||||
) -> bool {
|
||||
let config = NetworkConfig {
|
||||
name: name.to_string(),
|
||||
chain_id,
|
||||
rpc_url: rpc_url.to_string(),
|
||||
explorer_url: explorer_url.to_string(),
|
||||
token_symbol: token_symbol.to_string(),
|
||||
decimals,
|
||||
};
|
||||
|
||||
if let Ok(mut registry) = NETWORK_REGISTRY.write() {
|
||||
registry.insert(name.to_lowercase(), config);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a network from the registry
|
||||
pub fn remove_network(name: &str) -> bool {
|
||||
if let Ok(mut registry) = NETWORK_REGISTRY.write() {
|
||||
registry.remove(&name.to_lowercase()).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
if let Ok(registry) = NETWORK_REGISTRY.read() {
|
||||
registry.get(&name.to_lowercase()).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,19 +125,57 @@ pub fn get_proper_network_name(name: &str) -> Option<&'static str> {
|
||||
}
|
||||
|
||||
/// Get a list of all supported network names
|
||||
pub fn list_network_names() -> Vec<&'static str> {
|
||||
vec![names::GNOSIS, names::PEAQ, names::AGUNG]
|
||||
pub fn list_network_names() -> Vec<String> {
|
||||
if let Ok(registry) = NETWORK_REGISTRY.read() {
|
||||
registry.values().map(|config| config.name.clone()).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
pub fn get_all_networks() -> HashMap<String, NetworkConfig> {
|
||||
if let Ok(registry) = NETWORK_REGISTRY.read() {
|
||||
registry.clone()
|
||||
} else {
|
||||
HashMap::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
|
||||
// Legacy functions for backward compatibility
|
||||
|
||||
/// Get the Gnosis Chain network configuration
|
||||
pub fn gnosis() -> NetworkConfig {
|
||||
get_network_by_name("gnosis").unwrap_or_else(|| 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 {
|
||||
get_network_by_name("peaq").unwrap_or_else(|| NetworkConfig {
|
||||
name: names::PEAQ.to_string(),
|
||||
chain_id: 3338,
|
||||
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 {
|
||||
get_network_by_name("agung").unwrap_or_else(|| 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,
|
||||
})
|
||||
}
|
||||
|
@ -3,25 +3,36 @@
|
||||
use ethers::prelude::*;
|
||||
|
||||
use crate::hero_vault::error::CryptoError;
|
||||
use super::networks::{self, NetworkConfig};
|
||||
use super::networks;
|
||||
|
||||
/// Creates a provider for a specific network.
|
||||
pub fn create_provider(network: &NetworkConfig) -> Result<Provider<Http>, CryptoError> {
|
||||
pub fn create_provider(network_name: &str) -> Result<Provider<Http>, CryptoError> {
|
||||
let network = networks::get_network_by_name(network_name)
|
||||
.ok_or_else(|| CryptoError::SerializationError(format!("Unknown network: {}", network_name)))?;
|
||||
|
||||
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 a specific network configuration.
|
||||
pub fn create_provider_from_config(network: &networks::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)))
|
||||
}
|
||||
|
||||
// Legacy functions for backward compatibility
|
||||
|
||||
/// Creates a provider for the Gnosis Chain.
|
||||
pub fn create_gnosis_provider() -> Result<Provider<Http>, CryptoError> {
|
||||
create_provider(&networks::gnosis())
|
||||
create_provider("gnosis")
|
||||
}
|
||||
|
||||
/// Creates a provider for the Peaq network.
|
||||
pub fn create_peaq_provider() -> Result<Provider<Http>, CryptoError> {
|
||||
create_provider(&networks::peaq())
|
||||
create_provider("peaq")
|
||||
}
|
||||
|
||||
/// Creates a provider for the Agung testnet.
|
||||
pub fn create_agung_provider() -> Result<Provider<Http>, CryptoError> {
|
||||
create_provider(&networks::agung())
|
||||
create_provider("agung")
|
||||
}
|
||||
|
@ -6,60 +6,64 @@ use once_cell::sync::Lazy;
|
||||
|
||||
use crate::hero_vault::error::CryptoError;
|
||||
use super::wallet::EthereumWallet;
|
||||
use super::networks::{self, NetworkConfig};
|
||||
use super::networks;
|
||||
|
||||
/// Global storage for Ethereum wallets.
|
||||
static ETH_WALLETS: Lazy<Mutex<HashMap<String, Vec<EthereumWallet>>>> = Lazy::new(|| {
|
||||
Mutex::new(HashMap::new())
|
||||
static ETH_WALLETS: Lazy<Mutex<Vec<EthereumWallet>>> = Lazy::new(|| {
|
||||
Mutex::new(Vec::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> {
|
||||
/// 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::hero_vault::keypair::get_selected_keypair()?;
|
||||
|
||||
// Create an Ethereum wallet from the keypair
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network)?;
|
||||
let wallet = EthereumWallet::from_keypair(&keypair)?;
|
||||
|
||||
// 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());
|
||||
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 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::hero_vault::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 the currently selected keypair for the Agung testnet.
|
||||
pub fn create_agung_wallet() -> Result<EthereumWallet, CryptoError> {
|
||||
create_ethereum_wallet_for_network(networks::agung())
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Gets the current Ethereum wallet for a specific network.
|
||||
pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result<EthereumWallet, CryptoError> {
|
||||
/// Gets the current Ethereum wallet.
|
||||
pub fn get_current_ethereum_wallet() -> 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() {
|
||||
if 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")
|
||||
Ok(wallets.last().unwrap().clone())
|
||||
}
|
||||
|
||||
/// Clears all Ethereum wallets.
|
||||
@ -68,47 +72,50 @@ pub fn clear_ethereum_wallets() {
|
||||
wallets.clear();
|
||||
}
|
||||
|
||||
// Legacy functions for backward compatibility
|
||||
|
||||
/// Creates an Ethereum wallet from the currently selected keypair for a specific network.
|
||||
pub fn create_ethereum_wallet_for_network(network: networks::NetworkConfig) -> Result<EthereumWallet, CryptoError> {
|
||||
create_ethereum_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()
|
||||
}
|
||||
|
||||
/// Creates an Ethereum wallet from the currently selected keypair for the Agung testnet.
|
||||
pub fn create_agung_wallet() -> Result<EthereumWallet, CryptoError> {
|
||||
create_ethereum_wallet()
|
||||
}
|
||||
|
||||
/// Gets the current Ethereum wallet for a specific network.
|
||||
pub fn get_current_ethereum_wallet_for_network(network_name: &str) -> Result<EthereumWallet, CryptoError> {
|
||||
get_current_ethereum_wallet()
|
||||
}
|
||||
|
||||
/// Gets the current Ethereum wallet for the Peaq network.
|
||||
pub fn get_current_peaq_wallet() -> Result<EthereumWallet, CryptoError> {
|
||||
get_current_ethereum_wallet()
|
||||
}
|
||||
|
||||
/// Gets the current Ethereum wallet for the Agung testnet.
|
||||
pub fn get_current_agung_wallet() -> Result<EthereumWallet, CryptoError> {
|
||||
get_current_ethereum_wallet()
|
||||
}
|
||||
|
||||
/// 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);
|
||||
// In the new design, we don't have network-specific wallets,
|
||||
// so this is a no-op for backward compatibility
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn create_ethereum_wallet_from_name_for_network(name: &str, network: networks::NetworkConfig) -> Result<EthereumWallet, CryptoError> {
|
||||
create_ethereum_wallet_from_name(name)
|
||||
}
|
||||
|
||||
/// 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())
|
||||
pub fn create_ethereum_wallet_from_private_key_for_network(private_key: &str, network: networks::NetworkConfig) -> Result<EthereumWallet, CryptoError> {
|
||||
create_ethereum_wallet_from_private_key(private_key)
|
||||
}
|
||||
|
@ -22,53 +22,123 @@ fn test_network_config() {
|
||||
|
||||
#[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"));
|
||||
// Get initial network names
|
||||
let initial_network_names = list_network_names();
|
||||
assert!(initial_network_names.iter().any(|name| name == "Gnosis"));
|
||||
assert!(initial_network_names.iter().any(|name| name == "Peaq"));
|
||||
assert!(initial_network_names.iter().any(|name| name == "Agung"));
|
||||
|
||||
let gnosis_proper = networks::get_proper_network_name("gnosis");
|
||||
assert_eq!(gnosis_proper, Some("Gnosis"));
|
||||
// Test proper network name lookup
|
||||
let gnosis_proper = get_proper_network_name("gnosis");
|
||||
assert_eq!(gnosis_proper, Some(networks::names::GNOSIS));
|
||||
|
||||
let peaq_proper = networks::get_proper_network_name("peaq");
|
||||
assert_eq!(peaq_proper, Some("Peaq"));
|
||||
let peaq_proper = get_proper_network_name("peaq");
|
||||
assert_eq!(peaq_proper, Some(networks::names::PEAQ));
|
||||
|
||||
let agung_proper = networks::get_proper_network_name("agung");
|
||||
assert_eq!(agung_proper, Some("Agung"));
|
||||
let agung_proper = get_proper_network_name("agung");
|
||||
assert_eq!(agung_proper, Some(networks::names::AGUNG));
|
||||
|
||||
let unknown = networks::get_proper_network_name("unknown");
|
||||
let unknown = get_proper_network_name("unknown");
|
||||
assert_eq!(unknown, None);
|
||||
|
||||
let gnosis_config = networks::get_network_by_name("Gnosis");
|
||||
// Test network lookup by name
|
||||
let gnosis_config = 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");
|
||||
let unknown_config = get_network_by_name("Unknown");
|
||||
assert!(unknown_config.is_none());
|
||||
|
||||
// Test case insensitivity
|
||||
let gnosis_lower = get_network_by_name("gnosis");
|
||||
assert!(gnosis_lower.is_some());
|
||||
assert_eq!(gnosis_lower.unwrap().chain_id, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_registry_dynamic() {
|
||||
// Register a new network
|
||||
let success = register_network(
|
||||
"Sepolia",
|
||||
11155111,
|
||||
"https://rpc.sepolia.org",
|
||||
"https://sepolia.etherscan.io",
|
||||
"ETH",
|
||||
18
|
||||
);
|
||||
assert!(success);
|
||||
|
||||
// Verify the network was added
|
||||
let network_names = list_network_names();
|
||||
assert!(network_names.iter().any(|name| name == "Sepolia"));
|
||||
|
||||
// Get the network config
|
||||
let sepolia = get_network_by_name("Sepolia");
|
||||
assert!(sepolia.is_some());
|
||||
let sepolia = sepolia.unwrap();
|
||||
assert_eq!(sepolia.chain_id, 11155111);
|
||||
assert_eq!(sepolia.token_symbol, "ETH");
|
||||
assert_eq!(sepolia.rpc_url, "https://rpc.sepolia.org");
|
||||
assert_eq!(sepolia.explorer_url, "https://sepolia.etherscan.io");
|
||||
assert_eq!(sepolia.decimals, 18);
|
||||
|
||||
// Test case insensitivity
|
||||
let sepolia_lower = get_network_by_name("sepolia");
|
||||
assert!(sepolia_lower.is_some());
|
||||
|
||||
// Remove the network
|
||||
let removed = remove_network("Sepolia");
|
||||
assert!(removed);
|
||||
|
||||
// Verify the network was removed
|
||||
let network_names = list_network_names();
|
||||
assert!(!network_names.iter().any(|name| name == "Sepolia"));
|
||||
|
||||
// Try to get the removed network
|
||||
let sepolia = get_network_by_name("Sepolia");
|
||||
assert!(sepolia.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);
|
||||
// Create providers using network configs
|
||||
let gnosis_provider = create_provider_from_config(&networks::gnosis());
|
||||
let peaq_provider = create_provider_from_config(&networks::peaq());
|
||||
let agung_provider = create_provider_from_config(&networks::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();
|
||||
// Create providers using network names
|
||||
let gnosis_provider2 = create_provider("gnosis");
|
||||
let peaq_provider2 = create_provider("peaq");
|
||||
let agung_provider2 = create_provider("agung");
|
||||
|
||||
assert!(gnosis_provider2.is_ok());
|
||||
assert!(peaq_provider2.is_ok());
|
||||
assert!(agung_provider2.is_ok());
|
||||
|
||||
// The legacy convenience functions should also work
|
||||
let gnosis_provider3 = create_gnosis_provider();
|
||||
let peaq_provider3 = create_peaq_provider();
|
||||
let agung_provider3 = create_agung_provider();
|
||||
|
||||
assert!(gnosis_provider3.is_ok());
|
||||
assert!(peaq_provider3.is_ok());
|
||||
assert!(agung_provider3.is_ok());
|
||||
|
||||
// Test with an unknown network
|
||||
let unknown_provider = create_provider("unknown");
|
||||
assert!(unknown_provider.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_all_networks() {
|
||||
let networks = get_all_networks();
|
||||
assert!(!networks.is_empty());
|
||||
assert!(networks.contains_key("gnosis"));
|
||||
assert!(networks.contains_key("peaq"));
|
||||
assert!(networks.contains_key("agung"));
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ fn test_get_balance() {
|
||||
|
||||
// Create a provider
|
||||
let network = networks::gnosis();
|
||||
let provider_result = create_provider(&network);
|
||||
let provider_result = create_provider_from_config(&network);
|
||||
|
||||
// The provider creation should succeed
|
||||
assert!(provider_result.is_ok());
|
||||
@ -59,10 +59,10 @@ fn test_send_eth() {
|
||||
// Create a wallet
|
||||
let keypair = KeyPair::new("test_keypair6");
|
||||
let network = networks::gnosis();
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
||||
let wallet = EthereumWallet::from_keypair(&keypair).unwrap();
|
||||
|
||||
// Create a provider
|
||||
let provider_result = create_provider(&network);
|
||||
let provider_result = create_provider_from_config(&network);
|
||||
assert!(provider_result.is_ok());
|
||||
|
||||
// We can't actually test send_eth without a blockchain
|
||||
|
@ -3,58 +3,56 @@
|
||||
use crate::hero_vault::ethereum::*;
|
||||
use crate::hero_vault::keypair::KeyPair;
|
||||
use ethers::utils::hex;
|
||||
use ethers::prelude::Signer;
|
||||
|
||||
#[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);
|
||||
let wallet = EthereumWallet::from_keypair(&keypair).unwrap();
|
||||
|
||||
// The address should be a valid Ethereum address
|
||||
assert!(wallet.address_string().starts_with("0x"));
|
||||
|
||||
// The wallet should not have a name
|
||||
assert!(wallet.name.is_none());
|
||||
}
|
||||
|
||||
#[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);
|
||||
let wallet = EthereumWallet::from_name_and_keypair("test", &keypair).unwrap();
|
||||
|
||||
// The address should be a valid Ethereum address
|
||||
assert!(wallet.address_string().starts_with("0x"));
|
||||
|
||||
// The wallet should have the correct name
|
||||
assert_eq!(wallet.name, Some("test".to_string()));
|
||||
|
||||
// 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();
|
||||
let wallet2 = EthereumWallet::from_name_and_keypair("test", &keypair).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();
|
||||
let wallet3 = EthereumWallet::from_name_and_keypair("test2", &keypair).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);
|
||||
let wallet = EthereumWallet::from_private_key(private_key).unwrap();
|
||||
|
||||
// The address should be a valid Ethereum address
|
||||
assert!(wallet.address_string().starts_with("0x"));
|
||||
|
||||
// The wallet should not have a name
|
||||
assert!(wallet.name.is_none());
|
||||
|
||||
// The address should be deterministic based on the private key
|
||||
let wallet2 = EthereumWallet::from_private_key(private_key, network.clone()).unwrap();
|
||||
let wallet2 = EthereumWallet::from_private_key(private_key).unwrap();
|
||||
assert_eq!(wallet.address, wallet2.address);
|
||||
}
|
||||
|
||||
@ -67,33 +65,53 @@ fn test_wallet_management() {
|
||||
crate::hero_vault::keypair::create_space("test_space").unwrap();
|
||||
crate::hero_vault::keypair::create_keypair("test_keypair3").unwrap();
|
||||
|
||||
// Create wallets for different networks
|
||||
// Create a wallet
|
||||
let wallet = create_ethereum_wallet().unwrap();
|
||||
|
||||
// Get the current wallet
|
||||
let current_wallet = get_current_ethereum_wallet().unwrap();
|
||||
|
||||
// Check that they match
|
||||
assert_eq!(wallet.address, current_wallet.address);
|
||||
|
||||
// Clear all wallets
|
||||
clear_ethereum_wallets();
|
||||
|
||||
// Check that the wallet is gone
|
||||
let result = get_current_ethereum_wallet();
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test legacy functions
|
||||
|
||||
// Create wallets for different networks (should all create the same wallet)
|
||||
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
|
||||
// They should all have the same address
|
||||
assert_eq!(gnosis_wallet.address, peaq_wallet.address);
|
||||
assert_eq!(gnosis_wallet.address, agung_wallet.address);
|
||||
|
||||
// Get the current wallets for different networks (should all return the same wallet)
|
||||
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);
|
||||
// They should all have the same address
|
||||
assert_eq!(current_gnosis.address, current_peaq.address);
|
||||
assert_eq!(current_gnosis.address, current_agung.address);
|
||||
|
||||
// Clear wallets for a specific network
|
||||
// Clear wallets for a specific network (should be a no-op in the new design)
|
||||
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
|
||||
// All wallets should still be accessible
|
||||
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();
|
||||
assert_eq!(peaq_wallet.address, current_peaq.address);
|
||||
assert_eq!(agung_wallet.address, current_agung.address);
|
||||
|
||||
// They should all have the same address
|
||||
assert_eq!(current_gnosis.address, current_peaq.address);
|
||||
assert_eq!(current_gnosis.address, current_agung.address);
|
||||
|
||||
// Clear all wallets
|
||||
clear_ethereum_wallets();
|
||||
@ -110,9 +128,8 @@ fn test_wallet_management() {
|
||||
#[test]
|
||||
fn test_sign_message() {
|
||||
let keypair = KeyPair::new("test_keypair4");
|
||||
let network = networks::gnosis();
|
||||
|
||||
let wallet = EthereumWallet::from_keypair(&keypair, network.clone()).unwrap();
|
||||
let wallet = EthereumWallet::from_keypair(&keypair).unwrap();
|
||||
|
||||
// Create a tokio runtime for the async test
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
@ -128,9 +145,8 @@ fn test_sign_message() {
|
||||
#[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();
|
||||
let wallet = EthereumWallet::from_keypair(&keypair).unwrap();
|
||||
|
||||
// Get the private key as hex
|
||||
let private_key_hex = wallet.private_key_hex();
|
||||
@ -141,3 +157,56 @@ fn test_private_key_hex() {
|
||||
// It should be possible to parse it as hex
|
||||
let _bytes = hex::decode(private_key_hex).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_for_network() {
|
||||
let keypair = KeyPair::new("test_keypair6");
|
||||
|
||||
let wallet = EthereumWallet::from_keypair(&keypair).unwrap();
|
||||
|
||||
// Get wallets for different networks
|
||||
let gnosis_network = networks::gnosis();
|
||||
let peaq_network = networks::peaq();
|
||||
let agung_network = networks::agung();
|
||||
|
||||
let gnosis_wallet = wallet.for_network(&gnosis_network);
|
||||
let peaq_wallet = wallet.for_network(&peaq_network);
|
||||
let agung_wallet = wallet.for_network(&agung_network);
|
||||
|
||||
// The chain IDs should match the networks
|
||||
assert_eq!(gnosis_wallet.chain_id(), gnosis_network.chain_id);
|
||||
assert_eq!(peaq_wallet.chain_id(), peaq_network.chain_id);
|
||||
assert_eq!(agung_wallet.chain_id(), agung_network.chain_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_legacy_wallet_functions() {
|
||||
let keypair = KeyPair::new("test_keypair7");
|
||||
|
||||
// Test legacy wallet creation functions
|
||||
let gnosis_network = networks::gnosis();
|
||||
|
||||
// Create a wallet with the legacy function
|
||||
let wallet1 = EthereumWallet::from_keypair_for_network(&keypair, gnosis_network.clone()).unwrap();
|
||||
|
||||
// Create a wallet with the new function
|
||||
let wallet2 = EthereumWallet::from_keypair(&keypair).unwrap();
|
||||
|
||||
// They should have the same address
|
||||
assert_eq!(wallet1.address, wallet2.address);
|
||||
|
||||
// Test with name
|
||||
let wallet3 = EthereumWallet::from_name_and_keypair_for_network("test", &keypair, gnosis_network.clone()).unwrap();
|
||||
let wallet4 = EthereumWallet::from_name_and_keypair("test", &keypair).unwrap();
|
||||
|
||||
// They should have the same address
|
||||
assert_eq!(wallet3.address, wallet4.address);
|
||||
|
||||
// Test with private key
|
||||
let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
let wallet5 = EthereumWallet::from_private_key_for_network(private_key, gnosis_network.clone()).unwrap();
|
||||
let wallet6 = EthereumWallet::from_private_key(private_key).unwrap();
|
||||
|
||||
// They should have the same address
|
||||
assert_eq!(wallet5.address, wallet6.address);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use ethers::types::transaction::eip2718::TypedTransaction;
|
||||
use crate::hero_vault::error::CryptoError;
|
||||
use super::wallet::EthereumWallet;
|
||||
use super::networks::NetworkConfig;
|
||||
use super::provider;
|
||||
|
||||
/// Formats a token balance for display.
|
||||
pub fn format_balance(balance: U256, network: &NetworkConfig) -> String {
|
||||
@ -20,7 +21,16 @@ pub fn format_balance(balance: U256, network: &NetworkConfig) -> String {
|
||||
}
|
||||
|
||||
/// Gets the balance of an Ethereum address.
|
||||
pub async fn get_balance(provider: &Provider<Http>, address: Address) -> Result<U256, CryptoError> {
|
||||
pub async fn get_balance(network_name: &str, address: Address) -> Result<U256, CryptoError> {
|
||||
let provider = provider::create_provider(network_name)?;
|
||||
|
||||
provider.get_balance(address, None)
|
||||
.await
|
||||
.map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e)))
|
||||
}
|
||||
|
||||
/// Gets the balance of an Ethereum address using a provider.
|
||||
pub async fn get_balance_with_provider(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)))
|
||||
@ -29,15 +39,66 @@ pub async fn get_balance(provider: &Provider<Http>, address: Address) -> Result<
|
||||
/// Sends Ethereum from one address to another.
|
||||
pub async fn send_eth(
|
||||
wallet: &EthereumWallet,
|
||||
provider: &Provider<Http>,
|
||||
network_name: &str,
|
||||
to: Address,
|
||||
amount: U256,
|
||||
) -> Result<H256, CryptoError> {
|
||||
// Create a client with the wallet
|
||||
// Get the network configuration
|
||||
let network = super::networks::get_network_by_name(network_name)
|
||||
.ok_or_else(|| CryptoError::SerializationError(format!("Unknown network: {}", network_name)))?;
|
||||
|
||||
// Create a provider
|
||||
let provider = provider::create_provider(network_name)?;
|
||||
|
||||
// Create a client with the wallet configured for this network
|
||||
let network_wallet = wallet.for_network(&network);
|
||||
let client = SignerMiddleware::new(
|
||||
provider.clone(),
|
||||
wallet.wallet.clone(),
|
||||
network_wallet,
|
||||
);
|
||||
|
||||
// Estimate gas
|
||||
let tx = TransactionRequest::new()
|
||||
.to(to)
|
||||
.value(amount);
|
||||
|
||||
// Convert TransactionRequest to TypedTransaction explicitly
|
||||
let typed_tx: TypedTransaction = tx.into();
|
||||
let gas = client.estimate_gas(&typed_tx, None)
|
||||
.await
|
||||
.map_err(|e| CryptoError::SerializationError(format!("Failed to estimate gas: {}", e)))?;
|
||||
log::info!("Estimated gas: {:?}", gas);
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Legacy function for backward compatibility
|
||||
|
||||
/// Sends Ethereum from one address to another using a provider.
|
||||
pub async fn send_eth_with_provider(
|
||||
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.signer.clone(),
|
||||
);
|
||||
|
||||
// Estimate gas
|
||||
let tx = TransactionRequest::new()
|
||||
.to(to)
|
||||
|
@ -15,13 +15,13 @@ use super::networks::NetworkConfig;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EthereumWallet {
|
||||
pub address: Address,
|
||||
pub wallet: Wallet<SigningKey>,
|
||||
pub network: NetworkConfig,
|
||||
pub signer: Wallet<SigningKey>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
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> {
|
||||
/// 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();
|
||||
|
||||
@ -29,22 +29,21 @@ impl EthereumWallet {
|
||||
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);
|
||||
let signer = LocalWallet::from_str(&private_key_hex)
|
||||
.map_err(|_e| CryptoError::InvalidKeyLength)?;
|
||||
|
||||
// Get the Ethereum address
|
||||
let address = wallet.address();
|
||||
let address = signer.address();
|
||||
|
||||
Ok(EthereumWallet {
|
||||
address,
|
||||
wallet,
|
||||
network,
|
||||
signer,
|
||||
name: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
/// 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();
|
||||
|
||||
@ -58,37 +57,35 @@ impl EthereumWallet {
|
||||
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);
|
||||
let signer = LocalWallet::from_str(&private_key_hex)
|
||||
.map_err(|_e| CryptoError::InvalidKeyLength)?;
|
||||
|
||||
// Get the Ethereum address
|
||||
let address = wallet.address();
|
||||
let address = signer.address();
|
||||
|
||||
Ok(EthereumWallet {
|
||||
address,
|
||||
wallet,
|
||||
network,
|
||||
signer,
|
||||
name: Some(name.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
/// 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(network.chain_id);
|
||||
let signer = LocalWallet::from_str(private_key_clean)
|
||||
.map_err(|_e| CryptoError::InvalidKeyLength)?;
|
||||
|
||||
// Get the Ethereum address
|
||||
let address = wallet.address();
|
||||
let address = signer.address();
|
||||
|
||||
Ok(EthereumWallet {
|
||||
address,
|
||||
wallet,
|
||||
network,
|
||||
signer,
|
||||
name: None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -99,7 +96,7 @@ impl EthereumWallet {
|
||||
|
||||
/// 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)
|
||||
let signature = self.signer.sign_message(message)
|
||||
.await
|
||||
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
|
||||
|
||||
@ -108,7 +105,34 @@ impl EthereumWallet {
|
||||
|
||||
/// Gets the private key as a hex string.
|
||||
pub fn private_key_hex(&self) -> String {
|
||||
let bytes = self.wallet.signer().to_bytes();
|
||||
let bytes = self.signer.signer().to_bytes();
|
||||
hex::encode(bytes)
|
||||
}
|
||||
|
||||
/// Gets a wallet configured for a specific network.
|
||||
pub fn for_network(&self, network: &NetworkConfig) -> Wallet<SigningKey> {
|
||||
self.signer.clone().with_chain_id(network.chain_id)
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy functions for backward compatibility
|
||||
|
||||
impl EthereumWallet {
|
||||
/// Creates a new Ethereum wallet from a keypair for a specific network.
|
||||
/// This is kept for backward compatibility.
|
||||
pub fn from_keypair_for_network(keypair: &KeyPair, network: NetworkConfig) -> Result<Self, CryptoError> {
|
||||
Self::from_keypair(keypair)
|
||||
}
|
||||
|
||||
/// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network.
|
||||
/// This is kept for backward compatibility.
|
||||
pub fn from_name_and_keypair_for_network(name: &str, keypair: &KeyPair, _network: NetworkConfig) -> Result<Self, CryptoError> {
|
||||
Self::from_name_and_keypair(name, keypair)
|
||||
}
|
||||
|
||||
/// Creates a new Ethereum wallet from a private key for a specific network.
|
||||
/// This is kept for backward compatibility.
|
||||
pub fn from_private_key_for_network(private_key: &str, _network: NetworkConfig) -> Result<Self, CryptoError> {
|
||||
Self::from_private_key(private_key)
|
||||
}
|
||||
}
|
||||
|
@ -12,18 +12,13 @@ use ethers::types::{Address, U256};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::hero_vault::{keypair, symmetric, ethereum};
|
||||
use crate::hero_vault::ethereum::{prepare_function_arguments, convert_token_to_rhai};
|
||||
use crate::hero_vault::ethereum::prepare_function_arguments;
|
||||
|
||||
// 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
|
||||
fn load_key_space(name: &str, password: &str) -> bool {
|
||||
// Get the key spaces directory from config
|
||||
@ -382,9 +377,9 @@ fn decrypt(key: &str, ciphertext: &str) -> String {
|
||||
|
||||
// Ethereum operations
|
||||
|
||||
// Gnosis Chain operations
|
||||
// Create a network-independent Ethereum wallet
|
||||
fn create_ethereum_wallet() -> bool {
|
||||
match ethereum::create_ethereum_wallet_for_network(ethereum::networks::gnosis()) {
|
||||
match ethereum::create_ethereum_wallet() {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
log::error!("Error creating Ethereum wallet: {}", e);
|
||||
@ -393,8 +388,9 @@ fn create_ethereum_wallet() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the Ethereum wallet address (same for all networks)
|
||||
fn get_ethereum_address() -> String {
|
||||
match ethereum::get_current_ethereum_wallet_for_network("Gnosis") {
|
||||
match ethereum::get_current_ethereum_wallet() {
|
||||
Ok(wallet) => wallet.address_string(),
|
||||
Err(e) => {
|
||||
log::error!("Error getting Ethereum address: {}", e);
|
||||
@ -403,104 +399,51 @@ fn get_ethereum_address() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Peaq network operations
|
||||
fn create_peaq_wallet() -> bool {
|
||||
match ethereum::create_peaq_wallet() {
|
||||
// Create a wallet from a name
|
||||
fn create_ethereum_wallet_from_name(name: &str) -> bool {
|
||||
match ethereum::create_ethereum_wallet_from_name(name) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
log::error!("Error creating Peaq wallet: {}", e);
|
||||
log::error!("Error creating Ethereum wallet from name: {}", 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() {
|
||||
// Create a wallet from a private key
|
||||
fn create_ethereum_wallet_from_private_key(private_key: &str) -> bool {
|
||||
match ethereum::create_ethereum_wallet_from_private_key(private_key) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
log::error!("Error creating Agung wallet: {}", e);
|
||||
log::error!("Error creating Ethereum wallet from private key: {}", 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()
|
||||
}
|
||||
}
|
||||
// Network registry functions
|
||||
|
||||
// Register a new network
|
||||
fn register_network(name: &str, chain_id: i64, rpc_url: &str, explorer_url: &str, token_symbol: &str, decimals: i64) -> bool {
|
||||
ethereum::register_network(
|
||||
name,
|
||||
chain_id as u64,
|
||||
rpc_url,
|
||||
explorer_url,
|
||||
token_symbol,
|
||||
decimals as u8
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
// Remove a network
|
||||
fn remove_network(name: &str) -> bool {
|
||||
ethereum::remove_network(name)
|
||||
}
|
||||
|
||||
// List supported networks
|
||||
fn list_supported_networks() -> rhai::Array {
|
||||
let mut arr = rhai::Array::new();
|
||||
for name in ethereum::networks::list_network_names() {
|
||||
for name in ethereum::list_network_names() {
|
||||
arr.push(Dynamic::from(name.to_lowercase()));
|
||||
}
|
||||
arr
|
||||
@ -508,7 +451,7 @@ fn list_supported_networks() -> rhai::Array {
|
||||
|
||||
// Get network token symbol
|
||||
fn get_network_token_symbol(network_name: &str) -> String {
|
||||
match ethereum::networks::get_network_by_name(network_name) {
|
||||
match ethereum::get_network_by_name(network_name) {
|
||||
Some(network) => network.token_symbol,
|
||||
None => {
|
||||
log::error!("Unknown network: {}", network_name);
|
||||
@ -519,7 +462,7 @@ fn get_network_token_symbol(network_name: &str) -> String {
|
||||
|
||||
// Get network explorer URL
|
||||
fn get_network_explorer_url(network_name: &str) -> String {
|
||||
match ethereum::networks::get_network_by_name(network_name) {
|
||||
match ethereum::get_network_by_name(network_name) {
|
||||
Some(network) => network.explorer_url,
|
||||
None => {
|
||||
log::error!("Unknown network: {}", network_name);
|
||||
@ -528,48 +471,6 @@ fn get_network_explorer_url(network_name: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -590,38 +491,17 @@ fn get_balance(network_name: &str, address: &str) -> String {
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
ethereum::get_balance(network_name, addr).await
|
||||
}) {
|
||||
Ok(balance) => balance.to_string(),
|
||||
Ok(balance) => {
|
||||
// Format the balance with the network's token symbol
|
||||
match ethereum::get_network_by_name(network_name) {
|
||||
Some(network) => ethereum::format_balance(balance, &network),
|
||||
None => balance.to_string()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to get balance: {}", e);
|
||||
String::new()
|
||||
@ -629,8 +509,8 @@ fn get_balance(network_name: &str, address: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Send ETH from one address to another using the blocking approach
|
||||
fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String {
|
||||
// Send ETH from one address to another
|
||||
fn send_eth(network_name: &str, to_address: &str, amount_str: &str) -> String {
|
||||
// Get the runtime
|
||||
let rt = match RUNTIME.lock() {
|
||||
Ok(rt) => rt,
|
||||
@ -658,17 +538,8 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
|
||||
}
|
||||
};
|
||||
|
||||
// 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) {
|
||||
let wallet = match ethereum::get_current_ethereum_wallet() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get wallet: {}", e);
|
||||
@ -676,18 +547,9 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
ethereum::send_eth(&wallet, network_name, to_addr, amount).await
|
||||
}) {
|
||||
Ok(tx_hash) => format!("{:?}", tx_hash),
|
||||
Err(e) => {
|
||||
@ -702,7 +564,7 @@ fn send_eth(wallet_network: &str, to_address: &str, amount_str: &str) -> String
|
||||
// Load a contract ABI from a JSON string and create a contract instance
|
||||
fn load_contract_abi(network_name: &str, address: &str, abi_json: &str) -> String {
|
||||
// Get the network
|
||||
let network = match ethereum::networks::get_network_by_name(network_name) {
|
||||
let network = match ethereum::get_network_by_name(network_name) {
|
||||
Some(network) => network,
|
||||
None => {
|
||||
log::error!("Unknown network: {}", network_name);
|
||||
@ -750,8 +612,6 @@ fn load_contract_abi_from_file(network_name: &str, address: &str, file_path: &st
|
||||
}
|
||||
}
|
||||
|
||||
// Use the utility functions from the ethereum module
|
||||
|
||||
// Call a read-only function on a contract (no arguments version)
|
||||
fn call_contract_read_no_args(contract_json: &str, function_name: &str) -> Dynamic {
|
||||
call_contract_read(contract_json, function_name, rhai::Array::new())
|
||||
@ -787,7 +647,7 @@ fn call_contract_read(contract_json: &str, function_name: &str, args: rhai::Arra
|
||||
};
|
||||
|
||||
// Create a provider
|
||||
let provider = match ethereum::create_provider(&contract.network) {
|
||||
let provider = match ethereum::create_provider(&contract.network.name) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create provider: {}", e);
|
||||
@ -799,7 +659,7 @@ fn call_contract_read(contract_json: &str, function_name: &str, args: rhai::Arra
|
||||
match rt.block_on(async {
|
||||
ethereum::call_read_function(&contract, &provider, function_name, tokens).await
|
||||
}) {
|
||||
Ok(result) => convert_token_to_rhai(&result),
|
||||
Ok(result) => ethereum::token_to_dynamic(&result),
|
||||
Err(e) => {
|
||||
log::error!("Failed to call contract function: {}", e);
|
||||
Dynamic::UNIT
|
||||
@ -842,8 +702,7 @@ fn call_contract_write(contract_json: &str, function_name: &str, args: rhai::Arr
|
||||
};
|
||||
|
||||
// Get the wallet
|
||||
let network_name_proper = contract.network.name.as_str();
|
||||
let wallet = match ethereum::get_current_ethereum_wallet_for_network(network_name_proper) {
|
||||
let wallet = match ethereum::get_current_ethereum_wallet() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get wallet: {}", e);
|
||||
@ -852,7 +711,7 @@ fn call_contract_write(contract_json: &str, function_name: &str, args: rhai::Arr
|
||||
};
|
||||
|
||||
// Create a provider
|
||||
let provider = match ethereum::create_provider(&contract.network) {
|
||||
let provider = match ethereum::create_provider(&contract.network.name) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create provider: {}", e);
|
||||
@ -879,6 +738,41 @@ fn call_contract_write(contract_json: &str, function_name: &str, args: rhai::Arr
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy functions for backward compatibility
|
||||
|
||||
fn create_peaq_wallet() -> bool {
|
||||
create_ethereum_wallet()
|
||||
}
|
||||
|
||||
fn get_peaq_address() -> String {
|
||||
get_ethereum_address()
|
||||
}
|
||||
|
||||
fn create_agung_wallet() -> bool {
|
||||
create_ethereum_wallet()
|
||||
}
|
||||
|
||||
fn get_agung_address() -> String {
|
||||
get_ethereum_address()
|
||||
}
|
||||
|
||||
fn create_wallet_for_network(network_name: &str) -> bool {
|
||||
create_ethereum_wallet()
|
||||
}
|
||||
|
||||
fn get_wallet_address_for_network(network_name: &str) -> String {
|
||||
get_ethereum_address()
|
||||
}
|
||||
|
||||
fn clear_wallets_for_network(network_name: &str) -> bool {
|
||||
ethereum::clear_ethereum_wallets();
|
||||
true
|
||||
}
|
||||
|
||||
fn create_wallet_from_private_key_for_network(private_key: &str, network_name: &str) -> bool {
|
||||
create_ethereum_wallet_from_private_key(private_key)
|
||||
}
|
||||
|
||||
/// Register crypto functions with the Rhai engine
|
||||
pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register key space functions
|
||||
@ -901,43 +795,40 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
|
||||
engine.register_fn("encrypt", encrypt);
|
||||
engine.register_fn("decrypt", decrypt);
|
||||
|
||||
// Register Ethereum functions (Gnosis Chain)
|
||||
// Register Ethereum wallet functions
|
||||
engine.register_fn("create_ethereum_wallet", create_ethereum_wallet);
|
||||
engine.register_fn("get_ethereum_address", get_ethereum_address);
|
||||
engine.register_fn("create_ethereum_wallet_from_name", create_ethereum_wallet_from_name);
|
||||
engine.register_fn("create_ethereum_wallet_from_private_key", create_ethereum_wallet_from_private_key);
|
||||
|
||||
// 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);
|
||||
// Register network registry functions
|
||||
engine.register_fn("register_network", register_network);
|
||||
engine.register_fn("remove_network", remove_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);
|
||||
// Register transaction functions
|
||||
engine.register_fn("send_eth", send_eth);
|
||||
engine.register_fn("get_balance", get_balance);
|
||||
|
||||
// Register smart contract functions
|
||||
engine.register_fn("load_contract_abi", load_contract_abi);
|
||||
engine.register_fn("load_contract_abi_from_file", load_contract_abi_from_file);
|
||||
|
||||
// Register the read function with different arities
|
||||
engine.register_fn("call_contract_read", call_contract_read_no_args);
|
||||
engine.register_fn("call_contract_read", call_contract_read);
|
||||
|
||||
// Register the write function with different arities
|
||||
engine.register_fn("call_contract_write", call_contract_write_no_args);
|
||||
engine.register_fn("call_contract_write", call_contract_write);
|
||||
|
||||
// Register legacy functions for backward compatibility
|
||||
engine.register_fn("create_peaq_wallet", create_peaq_wallet);
|
||||
engine.register_fn("get_peaq_address", get_peaq_address);
|
||||
engine.register_fn("create_agung_wallet", create_agung_wallet);
|
||||
engine.register_fn("get_agung_address", get_agung_address);
|
||||
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("create_wallet_from_private_key_for_network", create_wallet_from_private_key_for_network);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user