use crate::config::EvmNetworkConfig;
use crate::error::{Error, Result};
use evmlib::Network as EvmNetwork;
use evmlib::RewardsAddress;
#[derive(Debug, Clone)]
pub struct WalletConfig {
pub rewards_address: Option<RewardsAddress>,
pub network: EvmNetwork,
}
impl WalletConfig {
pub fn new(rewards_address: Option<&str>, evm_network: EvmNetworkConfig) -> Result<Self> {
let rewards_address = rewards_address.map(parse_rewards_address).transpose()?;
let network = evm_network.into_evm_network();
Ok(Self {
rewards_address,
network,
})
}
#[must_use]
pub fn has_rewards_address(&self) -> bool {
self.rewards_address.is_some()
}
#[must_use]
pub fn get_rewards_address(&self) -> Option<&RewardsAddress> {
self.rewards_address.as_ref()
}
#[must_use]
pub fn is_mainnet(&self) -> bool {
matches!(self.network, EvmNetwork::ArbitrumOne)
}
}
pub fn parse_rewards_address(address: &str) -> Result<RewardsAddress> {
if !address.starts_with("0x") && !address.starts_with("0X") {
return Err(Error::Payment(format!(
"Invalid rewards address format: must start with '0x', got: {address}"
)));
}
let len = address.len();
if len != 42 {
return Err(Error::Payment(format!(
"Invalid rewards address length: expected 42 characters, got {len}",
)));
}
let hex_part = &address[2..];
if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(Error::Payment(format!(
"Invalid rewards address: contains non-hex characters: {address}"
)));
}
let bytes = hex::decode(hex_part)
.map_err(|e| Error::Payment(format!("Failed to decode rewards address: {e}")))?;
let mut address_bytes = [0u8; 20];
address_bytes.copy_from_slice(&bytes);
Ok(RewardsAddress::new(address_bytes))
}
#[must_use]
pub fn is_valid_address(address: &str) -> bool {
parse_rewards_address(address).is_ok()
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_address() {
let address = "0x742d35Cc6634C0532925a3b844Bc9e7595916Da2";
let result = parse_rewards_address(address);
assert!(result.is_ok());
}
#[test]
fn test_parse_lowercase_address() {
let address = "0x742d35cc6634c0532925a3b844bc9e7595916da2";
let result = parse_rewards_address(address);
assert!(result.is_ok());
}
#[test]
fn test_invalid_prefix() {
let address = "742d35Cc6634C0532925a3b844Bc9e7595916Da2";
let result = parse_rewards_address(address);
assert!(result.is_err());
}
#[test]
fn test_invalid_length() {
let address = "0x742d35Cc6634C0532925a3b844Bc9e7595916Da";
let result = parse_rewards_address(address);
assert!(result.is_err());
}
#[test]
fn test_invalid_hex_chars() {
let address = "0x742d35Cc6634C0532925a3b844Bc9e7595916DgZ";
let result = parse_rewards_address(address);
assert!(result.is_err());
}
#[test]
fn test_is_valid_address() {
assert!(is_valid_address(
"0x742d35Cc6634C0532925a3b844Bc9e7595916Da2"
));
assert!(!is_valid_address("invalid"));
}
#[test]
fn test_wallet_config_new() {
let config = WalletConfig::new(
Some("0x742d35Cc6634C0532925a3b844Bc9e7595916Da2"),
EvmNetworkConfig::ArbitrumSepolia,
);
assert!(config.is_ok());
let config = config.expect("valid config");
assert!(config.has_rewards_address());
assert!(!config.is_mainnet());
}
#[test]
fn test_wallet_config_no_address() {
let config = WalletConfig::new(None, EvmNetworkConfig::ArbitrumOne);
assert!(config.is_ok());
let config = config.expect("valid config");
assert!(!config.has_rewards_address());
assert!(config.is_mainnet());
}
#[test]
fn test_uppercase_0x_prefix() {
let address = "0X742d35Cc6634C0532925a3b844Bc9e7595916Da2";
let result = parse_rewards_address(address);
assert!(result.is_ok());
}
#[test]
fn test_empty_string() {
let result = parse_rewards_address("");
assert!(result.is_err());
}
#[test]
fn test_just_0x_prefix() {
let result = parse_rewards_address("0x");
assert!(result.is_err());
let err_msg = format!("{}", result.expect_err("should fail"));
assert!(err_msg.contains("length"));
}
#[test]
fn test_address_with_spaces() {
let result = parse_rewards_address("0x 742d35Cc6634C0532925a3b844Bc9e7595916Da");
assert!(result.is_err());
}
#[test]
fn test_get_rewards_address_none() {
let config = WalletConfig::new(None, EvmNetworkConfig::ArbitrumOne).expect("valid config");
assert!(config.get_rewards_address().is_none());
}
#[test]
fn test_get_rewards_address_some() {
let config = WalletConfig::new(
Some("0x742d35Cc6634C0532925a3b844Bc9e7595916Da2"),
EvmNetworkConfig::ArbitrumOne,
)
.expect("valid config");
assert!(config.get_rewards_address().is_some());
}
#[test]
fn test_all_zeros_address() {
let address = "0x0000000000000000000000000000000000000000";
let result = parse_rewards_address(address);
assert!(result.is_ok());
}
#[test]
fn test_all_ff_address() {
let address = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
let result = parse_rewards_address(address);
assert!(result.is_ok());
}
#[test]
fn test_too_long_address() {
let address = "0x742d35Cc6634C0532925a3b844Bc9e7595916Da2a";
let result = parse_rewards_address(address);
assert!(result.is_err());
let err_msg = format!("{}", result.expect_err("should fail"));
assert!(err_msg.contains("length"));
}
#[test]
fn test_wallet_config_invalid_address() {
let result = WalletConfig::new(Some("invalid"), EvmNetworkConfig::ArbitrumOne);
assert!(result.is_err());
}
}