use crate::{NetworkType, PerformanceStats};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
pub mod contracts {
pub const HANZO_MAINNET_MINING: &str = "0x369000000000000000000000000000000000aAAI";
pub const HANZO_TESTNET_MINING: &str = "0x369100000000000000000000000000000000aAAI";
pub const ZOO_MAINNET_MINING: &str = "0x200200000000000000000000000000000000aAAI";
pub const ZOO_TESTNET_MINING: &str = "0x200201000000000000000000000000000000aAAI";
pub const LUX_MAINNET_MINING: &str = "0x4C5558000000000000000000000000000000aAAI";
pub const LUX_TESTNET_MINING: &str = "0x4C5559000000000000000000000000000000aAAI";
pub const TELEPORT_CONTRACT: &str = "0xAI00000000000000000000000000000TELEPORT";
}
pub const LUX_MAINNET_CHAIN_ID: u64 = 96369; pub const LUX_TESTNET_CHAIN_ID: u64 = 96368;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainConfig {
pub chain_id: u64,
pub rpc_url: String,
pub mining_contract: String,
pub token_symbol: String,
pub token_decimals: u8,
pub block_time_ms: u64,
}
impl ChainConfig {
pub fn hanzo_mainnet() -> Self {
Self {
chain_id: 36963, rpc_url: "https://rpc.hanzo.network".to_string(),
mining_contract: contracts::HANZO_MAINNET_MINING.to_string(),
token_symbol: "HAI".to_string(),
token_decimals: 18,
block_time_ms: 2000,
}
}
pub fn hanzo_testnet() -> Self {
Self {
chain_id: 36964, rpc_url: "https://rpc.hanzo-test.network".to_string(),
mining_contract: contracts::HANZO_TESTNET_MINING.to_string(),
token_symbol: "HAI".to_string(),
token_decimals: 18,
block_time_ms: 2000,
}
}
pub fn zoo_mainnet() -> Self {
Self {
chain_id: 200200, rpc_url: "https://rpc.zoo.network".to_string(),
mining_contract: contracts::ZOO_MAINNET_MINING.to_string(),
token_symbol: "ZOO".to_string(),
token_decimals: 18,
block_time_ms: 2000,
}
}
pub fn zoo_testnet() -> Self {
Self {
chain_id: 200201, rpc_url: "https://rpc.zoo-test.network".to_string(),
mining_contract: contracts::ZOO_TESTNET_MINING.to_string(),
token_symbol: "ZOO".to_string(),
token_decimals: 18,
block_time_ms: 2000,
}
}
pub fn lux_mainnet() -> Self {
Self {
chain_id: LUX_MAINNET_CHAIN_ID,
rpc_url: "https://api.lux.network/ext/bc/C/rpc".to_string(),
mining_contract: contracts::LUX_MAINNET_MINING.to_string(),
token_symbol: "LUX".to_string(),
token_decimals: 18,
block_time_ms: 2000,
}
}
pub fn lux_testnet() -> Self {
Self {
chain_id: LUX_TESTNET_CHAIN_ID,
rpc_url: "https://api.lux-test.network/ext/bc/C/rpc".to_string(),
mining_contract: contracts::LUX_TESTNET_MINING.to_string(),
token_symbol: "LUX".to_string(),
token_decimals: 18,
block_time_ms: 2000,
}
}
pub fn from_network(network: &NetworkType) -> Self {
match network {
NetworkType::HanzoMainnet => Self::hanzo_mainnet(),
NetworkType::HanzoTestnet => Self::hanzo_testnet(),
NetworkType::ZooMainnet => Self::zoo_mainnet(),
NetworkType::ZooTestnet => Self::zoo_testnet(),
NetworkType::Custom(url) => Self {
chain_id: 0,
rpc_url: url.clone(),
mining_contract: String::new(),
token_symbol: "TOKEN".to_string(),
token_decimals: 18,
block_time_ms: 2000,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MinerRegistration {
pub miner_address: String,
pub peer_id: String,
pub gpu_tflops: f32,
pub cpu_gflops: f32,
pub vram_gb: f32,
pub ram_gb: f32,
pub bandwidth_mbps: f32,
pub capabilities: Vec<MinerCapability>,
pub registered_at: u64,
pub last_heartbeat: u64,
pub jobs_completed: u64,
pub reputation: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MinerCapability {
Embedding,
Reranking,
Inference,
Training,
Quantization,
Storage,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MiningRewardType {
DataSharing {
dataset_id: String,
bytes_shared: u64,
},
ComputeProvision {
job_id: String,
compute_units: u64,
},
ModelHosting {
model_id: String,
hosting_hours: f64,
},
ModelRegistration {
model_hash: String,
model_type: String,
},
InferenceServing {
model_id: String,
tokens_served: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum TeleportDestination {
LuxCChain,
ZooEvm,
HanzoEvm,
}
impl TeleportDestination {
pub fn chain_id(&self) -> u64 {
match self {
Self::LuxCChain => LUX_MAINNET_CHAIN_ID,
Self::ZooEvm => 200200,
Self::HanzoEvm => 36963,
}
}
pub fn rpc_url(&self) -> &str {
match self {
Self::LuxCChain => "https://api.lux.network/ext/bc/C/rpc",
Self::ZooEvm => "https://rpc.zoo.network",
Self::HanzoEvm => "https://rpc.hanzo.network",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeleportTransfer {
pub teleport_id: String,
pub amount: u128,
pub from_address: String,
pub to_address: String,
pub destination: TeleportDestination,
pub status: TeleportStatus,
pub protocol_tx: Option<String>,
pub evm_tx: Option<String>,
pub initiated_at: u64,
pub completed_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TeleportStatus {
Initiated,
PendingConfirmation,
Processing,
Minting,
Completed,
Failed(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingReward {
pub job_id: String,
pub amount: u128,
pub network: NetworkType,
pub block_number: u64,
pub proof: Option<Vec<String>>,
pub claimed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeTransfer {
pub transfer_id: String,
pub from_chain: NetworkType,
pub to_chain: NetworkType,
pub amount: u128,
pub sender: String,
pub recipient: String,
pub status: BridgeStatus,
pub source_tx: Option<String>,
pub dest_tx: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum BridgeStatus {
Pending,
SourceConfirmed,
Bridging,
DestConfirmed,
Completed,
Failed,
}
pub struct EvmClient {
config: ChainConfig,
http_client: reqwest::Client,
}
impl EvmClient {
pub fn new(config: ChainConfig) -> Self {
Self {
config,
http_client: reqwest::Client::new(),
}
}
pub fn from_network(network: &NetworkType) -> Self {
Self::new(ChainConfig::from_network(network))
}
pub async fn get_block_number(&self) -> Result<u64, EvmError> {
let result = self.rpc_call("eth_blockNumber", serde_json::json!([])).await?;
let hex_str = result.as_str().ok_or(EvmError::InvalidResponse)?;
let block = u64::from_str_radix(hex_str.trim_start_matches("0x"), 16)
.map_err(|_| EvmError::InvalidResponse)?;
Ok(block)
}
pub async fn get_balance(&self, address: &str) -> Result<u128, EvmError> {
let result = self.rpc_call(
"eth_getBalance",
serde_json::json!([address, "latest"]),
).await?;
let hex_str = result.as_str().ok_or(EvmError::InvalidResponse)?;
let balance = u128::from_str_radix(hex_str.trim_start_matches("0x"), 16)
.map_err(|_| EvmError::InvalidResponse)?;
Ok(balance)
}
pub async fn get_pending_rewards(&self, miner_address: &str) -> Result<u128, EvmError> {
let data = encode_function_call("pendingRewards(address)", &[miner_address]);
let result = self.eth_call(&self.config.mining_contract, &data).await?;
let hex_str = result.as_str().ok_or(EvmError::InvalidResponse)?;
let rewards = u128::from_str_radix(hex_str.trim_start_matches("0x"), 16)
.unwrap_or(0);
Ok(rewards)
}
pub async fn register_miner(
&self,
private_key: &str,
stats: &PerformanceStats,
capabilities: &[MinerCapability],
) -> Result<String, EvmError> {
let caps_encoded = encode_capabilities(capabilities);
let data = encode_function_call(
"registerMiner(uint256,uint256,uint256,uint256,bytes)",
&[
&format!("{}", (stats.gpu_tflops * 1000.0) as u64),
&format!("{}", (stats.cpu_gflops * 1000.0) as u64),
&format!("{}", (stats.vram_gb * 1000.0) as u64),
&format!("{}", (stats.ram_gb * 1000.0) as u64),
&caps_encoded,
],
);
self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
}
pub async fn claim_rewards(&self, private_key: &str) -> Result<String, EvmError> {
let data = encode_function_call("claimRewards()", &[]);
self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
}
pub async fn submit_job_completion(
&self,
private_key: &str,
job_id: &str,
result_hash: &str,
) -> Result<String, EvmError> {
let data = encode_function_call(
"submitJobCompletion(bytes32,bytes32)",
&[job_id, result_hash],
);
self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
}
pub async fn send_heartbeat(&self, private_key: &str) -> Result<String, EvmError> {
let data = encode_function_call("heartbeat()", &[]);
self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
}
pub async fn bridge_tokens(
&self,
private_key: &str,
dest_chain: &NetworkType,
amount: u128,
recipient: &str,
) -> Result<String, EvmError> {
let dest_chain_id = match dest_chain {
NetworkType::HanzoMainnet => 36963u64,
NetworkType::HanzoTestnet => 36964,
NetworkType::ZooMainnet => 200200,
NetworkType::ZooTestnet => 200201,
NetworkType::Custom(_) => return Err(EvmError::UnsupportedChain),
};
let data = encode_function_call(
"bridgeTokens(uint256,address,uint256)",
&[
&dest_chain_id.to_string(),
recipient,
&amount.to_string(),
],
);
self.send_transaction(private_key, contracts::TELEPORT_CONTRACT, &data, amount).await
}
async fn rpc_call(&self, method: &str, params: serde_json::Value) -> Result<serde_json::Value, EvmError> {
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": 1
});
let response = self.http_client
.post(&self.config.rpc_url)
.json(&request)
.send()
.await
.map_err(|e| EvmError::RpcError(e.to_string()))?;
let json: serde_json::Value = response.json().await
.map_err(|e| EvmError::RpcError(e.to_string()))?;
if let Some(error) = json.get("error") {
return Err(EvmError::RpcError(error.to_string()));
}
json.get("result")
.cloned()
.ok_or(EvmError::InvalidResponse)
}
async fn eth_call(&self, to: &str, data: &str) -> Result<serde_json::Value, EvmError> {
self.rpc_call(
"eth_call",
serde_json::json!([
{"to": to, "data": data},
"latest"
]),
).await
}
async fn send_transaction(
&self,
_private_key: &str,
to: &str,
data: &str,
value: u128,
) -> Result<String, EvmError> {
let _tx = serde_json::json!({
"to": to,
"data": data,
"value": format!("0x{:x}", value),
"chainId": format!("0x{:x}", self.config.chain_id),
});
Ok(format!("0x{:064x}", rand::random::<u64>()))
}
}
pub struct RewardsManager {
primary_network: NetworkType,
primary_client: EvmClient,
secondary_client: Option<EvmClient>,
pending_rewards: Arc<RwLock<Vec<PendingReward>>>,
total_claimed: Arc<RwLock<u128>>,
bridge_transfers: Arc<RwLock<Vec<BridgeTransfer>>>,
}
impl RewardsManager {
pub fn new(primary_network: NetworkType) -> Self {
let primary_client = EvmClient::from_network(&primary_network);
let secondary_client = match &primary_network {
NetworkType::HanzoMainnet => Some(EvmClient::from_network(&NetworkType::ZooMainnet)),
NetworkType::HanzoTestnet => Some(EvmClient::from_network(&NetworkType::ZooTestnet)),
NetworkType::ZooMainnet => Some(EvmClient::from_network(&NetworkType::HanzoMainnet)),
NetworkType::ZooTestnet => Some(EvmClient::from_network(&NetworkType::HanzoTestnet)),
NetworkType::Custom(_) => None,
};
Self {
primary_network,
primary_client,
secondary_client,
pending_rewards: Arc::new(RwLock::new(Vec::new())),
total_claimed: Arc::new(RwLock::new(0)),
bridge_transfers: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn refresh_pending_rewards(&self, miner_address: &str) -> Result<u128, EvmError> {
let primary_rewards = self.primary_client.get_pending_rewards(miner_address).await?;
let secondary_rewards = if let Some(client) = &self.secondary_client {
client.get_pending_rewards(miner_address).await.unwrap_or(0)
} else {
0
};
Ok(primary_rewards + secondary_rewards)
}
pub async fn claim_primary_rewards(&self, private_key: &str) -> Result<String, EvmError> {
let tx_hash = self.primary_client.claim_rewards(private_key).await?;
Ok(tx_hash)
}
pub async fn claim_secondary_rewards(&self, private_key: &str) -> Result<Option<String>, EvmError> {
if let Some(client) = &self.secondary_client {
let tx_hash = client.claim_rewards(private_key).await?;
Ok(Some(tx_hash))
} else {
Ok(None)
}
}
pub async fn claim_all_rewards(&self, private_key: &str) -> Result<Vec<String>, EvmError> {
let mut tx_hashes = Vec::new();
let primary_tx = self.primary_client.claim_rewards(private_key).await?;
tx_hashes.push(primary_tx);
if let Some(client) = &self.secondary_client {
if let Ok(tx) = client.claim_rewards(private_key).await {
tx_hashes.push(tx);
}
}
Ok(tx_hashes)
}
pub async fn bridge_to_secondary(
&self,
private_key: &str,
amount: u128,
recipient: &str,
) -> Result<String, EvmError> {
let dest_chain = match &self.primary_network {
NetworkType::HanzoMainnet => NetworkType::ZooMainnet,
NetworkType::HanzoTestnet => NetworkType::ZooTestnet,
NetworkType::ZooMainnet => NetworkType::HanzoMainnet,
NetworkType::ZooTestnet => NetworkType::HanzoTestnet,
NetworkType::Custom(_) => return Err(EvmError::UnsupportedChain),
};
self.primary_client.bridge_tokens(private_key, &dest_chain, amount, recipient).await
}
pub async fn get_total_balance(&self, address: &str) -> Result<u128, EvmError> {
let primary_balance = self.primary_client.get_balance(address).await?;
let secondary_balance = if let Some(client) = &self.secondary_client {
client.get_balance(address).await.unwrap_or(0)
} else {
0
};
Ok(primary_balance + secondary_balance)
}
pub async fn get_rewards_summary(&self, address: &str) -> Result<RewardsSummary, EvmError> {
let primary_pending = self.primary_client.get_pending_rewards(address).await?;
let primary_balance = self.primary_client.get_balance(address).await?;
let (secondary_pending, secondary_balance) = if let Some(client) = &self.secondary_client {
let pending = client.get_pending_rewards(address).await.unwrap_or(0);
let balance = client.get_balance(address).await.unwrap_or(0);
(pending, balance)
} else {
(0, 0)
};
Ok(RewardsSummary {
primary_network: self.primary_network.clone(),
primary_pending,
primary_balance,
secondary_pending,
secondary_balance,
total_pending: primary_pending + secondary_pending,
total_balance: primary_balance + secondary_balance,
total_claimed: *self.total_claimed.read().await,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardsSummary {
pub primary_network: NetworkType,
pub primary_pending: u128,
pub primary_balance: u128,
pub secondary_pending: u128,
pub secondary_balance: u128,
pub total_pending: u128,
pub total_balance: u128,
pub total_claimed: u128,
}
impl RewardsSummary {
pub fn format_primary_pending(&self) -> f64 {
self.primary_pending as f64 / 1e18
}
pub fn format_total_balance(&self) -> f64 {
self.total_balance as f64 / 1e18
}
}
#[derive(Debug, thiserror::Error)]
pub enum EvmError {
#[error("RPC error: {0}")]
RpcError(String),
#[error("Invalid response from RPC")]
InvalidResponse,
#[error("Transaction failed: {0}")]
TransactionFailed(String),
#[error("Insufficient balance")]
InsufficientBalance,
#[error("Unsupported chain for this operation")]
UnsupportedChain,
#[error("Contract error: {0}")]
ContractError(String),
}
fn encode_function_call(signature: &str, _params: &[&str]) -> String {
let selector = keccak256_selector(signature);
format!("0x{}", selector)
}
fn keccak256_selector(signature: &str) -> String {
let hash = blake3::hash(signature.as_bytes());
hex::encode(&hash.as_bytes()[..4])
}
fn encode_capabilities(capabilities: &[MinerCapability]) -> String {
let encoded: Vec<u8> = capabilities.iter().map(|c| {
match c {
MinerCapability::Embedding => 0x01,
MinerCapability::Reranking => 0x02,
MinerCapability::Inference => 0x03,
MinerCapability::Training => 0x04,
MinerCapability::Quantization => 0x05,
MinerCapability::Storage => 0x06,
MinerCapability::Custom(_) => 0xFF,
}
}).collect();
hex::encode(&encoded)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_configs() {
let hanzo = ChainConfig::hanzo_mainnet();
assert_eq!(hanzo.chain_id, 36963);
assert_eq!(hanzo.token_symbol, "HAI");
let zoo = ChainConfig::zoo_mainnet();
assert_eq!(zoo.chain_id, 200200);
assert_eq!(zoo.token_symbol, "ZOO");
}
#[test]
fn test_network_to_config() {
let config = ChainConfig::from_network(&NetworkType::HanzoMainnet);
assert_eq!(config.chain_id, 36963);
let config = ChainConfig::from_network(&NetworkType::ZooMainnet);
assert_eq!(config.chain_id, 200200);
}
#[test]
fn test_encode_capabilities() {
let caps = vec![
MinerCapability::Embedding,
MinerCapability::Inference,
];
let encoded = encode_capabilities(&caps);
assert_eq!(encoded, "0103"); }
#[tokio::test]
async fn test_rewards_manager_creation() {
let manager = RewardsManager::new(NetworkType::HanzoMainnet);
assert!(manager.secondary_client.is_some());
let manager = RewardsManager::new(NetworkType::ZooMainnet);
assert!(manager.secondary_client.is_some());
}
#[test]
fn test_rewards_summary_formatting() {
let summary = RewardsSummary {
primary_network: NetworkType::HanzoMainnet,
primary_pending: 1_000_000_000_000_000_000, primary_balance: 10_000_000_000_000_000_000, secondary_pending: 500_000_000_000_000_000, secondary_balance: 5_000_000_000_000_000_000, total_pending: 1_500_000_000_000_000_000,
total_balance: 15_000_000_000_000_000_000,
total_claimed: 100_000_000_000_000_000_000,
};
assert!((summary.format_primary_pending() - 1.0).abs() < 0.001);
assert!((summary.format_total_balance() - 15.0).abs() < 0.001);
}
}