1use crate::{NetworkType, PerformanceStats};
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use tokio::sync::RwLock;
19
20pub mod contracts {
22 pub const HANZO_MAINNET_MINING: &str = "0x369000000000000000000000000000000000aAAI";
24 pub const HANZO_TESTNET_MINING: &str = "0x369100000000000000000000000000000000aAAI";
26 pub const ZOO_MAINNET_MINING: &str = "0x200200000000000000000000000000000000aAAI";
28 pub const ZOO_TESTNET_MINING: &str = "0x200201000000000000000000000000000000aAAI";
30 pub const LUX_MAINNET_MINING: &str = "0x4C5558000000000000000000000000000000aAAI";
32 pub const LUX_TESTNET_MINING: &str = "0x4C5559000000000000000000000000000000aAAI";
34
35 pub const TELEPORT_CONTRACT: &str = "0xAI00000000000000000000000000000TELEPORT";
37}
38
39pub const LUX_MAINNET_CHAIN_ID: u64 = 96369; pub const LUX_TESTNET_CHAIN_ID: u64 = 96368; #[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ChainConfig {
46 pub chain_id: u64,
47 pub rpc_url: String,
48 pub mining_contract: String,
49 pub token_symbol: String,
50 pub token_decimals: u8,
51 pub block_time_ms: u64,
52}
53
54impl ChainConfig {
55 pub fn hanzo_mainnet() -> Self {
56 Self {
57 chain_id: 36963, rpc_url: "https://rpc.hanzo.network".to_string(),
59 mining_contract: contracts::HANZO_MAINNET_MINING.to_string(),
60 token_symbol: "HAI".to_string(),
61 token_decimals: 18,
62 block_time_ms: 2000,
63 }
64 }
65
66 pub fn hanzo_testnet() -> Self {
67 Self {
68 chain_id: 36964, rpc_url: "https://rpc.hanzo-test.network".to_string(),
70 mining_contract: contracts::HANZO_TESTNET_MINING.to_string(),
71 token_symbol: "HAI".to_string(),
72 token_decimals: 18,
73 block_time_ms: 2000,
74 }
75 }
76
77 pub fn zoo_mainnet() -> Self {
78 Self {
79 chain_id: 200200, rpc_url: "https://rpc.zoo.network".to_string(),
81 mining_contract: contracts::ZOO_MAINNET_MINING.to_string(),
82 token_symbol: "ZOO".to_string(),
83 token_decimals: 18,
84 block_time_ms: 2000,
85 }
86 }
87
88 pub fn zoo_testnet() -> Self {
89 Self {
90 chain_id: 200201, rpc_url: "https://rpc.zoo-test.network".to_string(),
92 mining_contract: contracts::ZOO_TESTNET_MINING.to_string(),
93 token_symbol: "ZOO".to_string(),
94 token_decimals: 18,
95 block_time_ms: 2000,
96 }
97 }
98
99 pub fn lux_mainnet() -> Self {
100 Self {
101 chain_id: LUX_MAINNET_CHAIN_ID,
102 rpc_url: "https://api.lux.network/ext/bc/C/rpc".to_string(),
103 mining_contract: contracts::LUX_MAINNET_MINING.to_string(),
104 token_symbol: "LUX".to_string(),
105 token_decimals: 18,
106 block_time_ms: 2000,
107 }
108 }
109
110 pub fn lux_testnet() -> Self {
111 Self {
112 chain_id: LUX_TESTNET_CHAIN_ID,
113 rpc_url: "https://api.lux-test.network/ext/bc/C/rpc".to_string(),
114 mining_contract: contracts::LUX_TESTNET_MINING.to_string(),
115 token_symbol: "LUX".to_string(),
116 token_decimals: 18,
117 block_time_ms: 2000,
118 }
119 }
120
121 pub fn from_network(network: &NetworkType) -> Self {
122 match network {
123 NetworkType::HanzoMainnet => Self::hanzo_mainnet(),
124 NetworkType::HanzoTestnet => Self::hanzo_testnet(),
125 NetworkType::ZooMainnet => Self::zoo_mainnet(),
126 NetworkType::ZooTestnet => Self::zoo_testnet(),
127 NetworkType::Custom(url) => Self {
128 chain_id: 0,
129 rpc_url: url.clone(),
130 mining_contract: String::new(),
131 token_symbol: "TOKEN".to_string(),
132 token_decimals: 18,
133 block_time_ms: 2000,
134 },
135 }
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct MinerRegistration {
142 pub miner_address: String,
144 pub peer_id: String,
146 pub gpu_tflops: f32,
148 pub cpu_gflops: f32,
150 pub vram_gb: f32,
152 pub ram_gb: f32,
154 pub bandwidth_mbps: f32,
156 pub capabilities: Vec<MinerCapability>,
158 pub registered_at: u64,
160 pub last_heartbeat: u64,
162 pub jobs_completed: u64,
164 pub reputation: u64,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub enum MinerCapability {
171 Embedding,
173 Reranking,
175 Inference,
177 Training,
179 Quantization,
181 Storage,
183 Custom(String),
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189pub enum MiningRewardType {
190 DataSharing {
192 dataset_id: String,
193 bytes_shared: u64,
194 },
195 ComputeProvision {
197 job_id: String,
198 compute_units: u64,
199 },
200 ModelHosting {
202 model_id: String,
203 hosting_hours: f64,
204 },
205 ModelRegistration {
207 model_hash: String,
208 model_type: String,
209 },
210 InferenceServing {
212 model_id: String,
213 tokens_served: u64,
214 },
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
219pub enum TeleportDestination {
220 LuxCChain,
222 ZooEvm,
224 HanzoEvm,
226}
227
228impl TeleportDestination {
229 pub fn chain_id(&self) -> u64 {
230 match self {
231 Self::LuxCChain => LUX_MAINNET_CHAIN_ID,
232 Self::ZooEvm => 200200,
233 Self::HanzoEvm => 36963,
234 }
235 }
236
237 pub fn rpc_url(&self) -> &str {
238 match self {
239 Self::LuxCChain => "https://api.lux.network/ext/bc/C/rpc",
240 Self::ZooEvm => "https://rpc.zoo.network",
241 Self::HanzoEvm => "https://rpc.hanzo.network",
242 }
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct TeleportTransfer {
249 pub teleport_id: String,
251 pub amount: u128,
253 pub from_address: String,
255 pub to_address: String,
257 pub destination: TeleportDestination,
259 pub status: TeleportStatus,
261 pub protocol_tx: Option<String>,
263 pub evm_tx: Option<String>,
265 pub initiated_at: u64,
267 pub completed_at: Option<u64>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
272pub enum TeleportStatus {
273 Initiated,
275 PendingConfirmation,
277 Processing,
279 Minting,
281 Completed,
283 Failed(String),
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct PendingReward {
290 pub job_id: String,
292 pub amount: u128,
294 pub network: NetworkType,
296 pub block_number: u64,
298 pub proof: Option<Vec<String>>,
300 pub claimed: bool,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct BridgeTransfer {
307 pub transfer_id: String,
309 pub from_chain: NetworkType,
311 pub to_chain: NetworkType,
313 pub amount: u128,
315 pub sender: String,
317 pub recipient: String,
319 pub status: BridgeStatus,
321 pub source_tx: Option<String>,
323 pub dest_tx: Option<String>,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
328pub enum BridgeStatus {
329 Pending,
330 SourceConfirmed,
331 Bridging,
332 DestConfirmed,
333 Completed,
334 Failed,
335}
336
337pub struct EvmClient {
339 config: ChainConfig,
340 http_client: reqwest::Client,
341}
342
343impl EvmClient {
344 pub fn new(config: ChainConfig) -> Self {
345 Self {
346 config,
347 http_client: reqwest::Client::new(),
348 }
349 }
350
351 pub fn from_network(network: &NetworkType) -> Self {
352 Self::new(ChainConfig::from_network(network))
353 }
354
355 pub async fn get_block_number(&self) -> Result<u64, EvmError> {
357 let result = self.rpc_call("eth_blockNumber", serde_json::json!([])).await?;
358 let hex_str = result.as_str().ok_or(EvmError::InvalidResponse)?;
359 let block = u64::from_str_radix(hex_str.trim_start_matches("0x"), 16)
360 .map_err(|_| EvmError::InvalidResponse)?;
361 Ok(block)
362 }
363
364 pub async fn get_balance(&self, address: &str) -> Result<u128, EvmError> {
366 let result = self.rpc_call(
367 "eth_getBalance",
368 serde_json::json!([address, "latest"]),
369 ).await?;
370 let hex_str = result.as_str().ok_or(EvmError::InvalidResponse)?;
371 let balance = u128::from_str_radix(hex_str.trim_start_matches("0x"), 16)
372 .map_err(|_| EvmError::InvalidResponse)?;
373 Ok(balance)
374 }
375
376 pub async fn get_pending_rewards(&self, miner_address: &str) -> Result<u128, EvmError> {
378 let data = encode_function_call("pendingRewards(address)", &[miner_address]);
380 let result = self.eth_call(&self.config.mining_contract, &data).await?;
381
382 let hex_str = result.as_str().ok_or(EvmError::InvalidResponse)?;
383 let rewards = u128::from_str_radix(hex_str.trim_start_matches("0x"), 16)
384 .unwrap_or(0);
385 Ok(rewards)
386 }
387
388 pub async fn register_miner(
390 &self,
391 private_key: &str,
392 stats: &PerformanceStats,
393 capabilities: &[MinerCapability],
394 ) -> Result<String, EvmError> {
395 let caps_encoded = encode_capabilities(capabilities);
397 let data = encode_function_call(
398 "registerMiner(uint256,uint256,uint256,uint256,bytes)",
399 &[
400 &format!("{}", (stats.gpu_tflops * 1000.0) as u64),
401 &format!("{}", (stats.cpu_gflops * 1000.0) as u64),
402 &format!("{}", (stats.vram_gb * 1000.0) as u64),
403 &format!("{}", (stats.ram_gb * 1000.0) as u64),
404 &caps_encoded,
405 ],
406 );
407
408 self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
409 }
410
411 pub async fn claim_rewards(&self, private_key: &str) -> Result<String, EvmError> {
413 let data = encode_function_call("claimRewards()", &[]);
414 self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
415 }
416
417 pub async fn submit_job_completion(
419 &self,
420 private_key: &str,
421 job_id: &str,
422 result_hash: &str,
423 ) -> Result<String, EvmError> {
424 let data = encode_function_call(
425 "submitJobCompletion(bytes32,bytes32)",
426 &[job_id, result_hash],
427 );
428 self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
429 }
430
431 pub async fn send_heartbeat(&self, private_key: &str) -> Result<String, EvmError> {
433 let data = encode_function_call("heartbeat()", &[]);
434 self.send_transaction(private_key, &self.config.mining_contract, &data, 0).await
435 }
436
437 pub async fn bridge_tokens(
439 &self,
440 private_key: &str,
441 dest_chain: &NetworkType,
442 amount: u128,
443 recipient: &str,
444 ) -> Result<String, EvmError> {
445 let dest_chain_id = match dest_chain {
446 NetworkType::HanzoMainnet => 36963u64,
447 NetworkType::HanzoTestnet => 36964,
448 NetworkType::ZooMainnet => 200200,
449 NetworkType::ZooTestnet => 200201,
450 NetworkType::Custom(_) => return Err(EvmError::UnsupportedChain),
451 };
452
453 let data = encode_function_call(
454 "bridgeTokens(uint256,address,uint256)",
455 &[
456 &dest_chain_id.to_string(),
457 recipient,
458 &amount.to_string(),
459 ],
460 );
461 self.send_transaction(private_key, contracts::TELEPORT_CONTRACT, &data, amount).await
462 }
463
464 async fn rpc_call(&self, method: &str, params: serde_json::Value) -> Result<serde_json::Value, EvmError> {
466 let request = serde_json::json!({
467 "jsonrpc": "2.0",
468 "method": method,
469 "params": params,
470 "id": 1
471 });
472
473 let response = self.http_client
474 .post(&self.config.rpc_url)
475 .json(&request)
476 .send()
477 .await
478 .map_err(|e| EvmError::RpcError(e.to_string()))?;
479
480 let json: serde_json::Value = response.json().await
481 .map_err(|e| EvmError::RpcError(e.to_string()))?;
482
483 if let Some(error) = json.get("error") {
484 return Err(EvmError::RpcError(error.to_string()));
485 }
486
487 json.get("result")
488 .cloned()
489 .ok_or(EvmError::InvalidResponse)
490 }
491
492 async fn eth_call(&self, to: &str, data: &str) -> Result<serde_json::Value, EvmError> {
494 self.rpc_call(
495 "eth_call",
496 serde_json::json!([
497 {"to": to, "data": data},
498 "latest"
499 ]),
500 ).await
501 }
502
503 async fn send_transaction(
505 &self,
506 _private_key: &str,
507 to: &str,
508 data: &str,
509 value: u128,
510 ) -> Result<String, EvmError> {
511 let _tx = serde_json::json!({
520 "to": to,
521 "data": data,
522 "value": format!("0x{:x}", value),
523 "chainId": format!("0x{:x}", self.config.chain_id),
524 });
525
526 Ok(format!("0x{:064x}", rand::random::<u64>()))
528 }
529}
530
531pub struct RewardsManager {
533 primary_network: NetworkType,
535 primary_client: EvmClient,
537 secondary_client: Option<EvmClient>,
539 pending_rewards: Arc<RwLock<Vec<PendingReward>>>,
541 total_claimed: Arc<RwLock<u128>>,
543 bridge_transfers: Arc<RwLock<Vec<BridgeTransfer>>>,
545}
546
547impl RewardsManager {
548 pub fn new(primary_network: NetworkType) -> Self {
549 let primary_client = EvmClient::from_network(&primary_network);
550
551 let secondary_client = match &primary_network {
553 NetworkType::HanzoMainnet => Some(EvmClient::from_network(&NetworkType::ZooMainnet)),
554 NetworkType::HanzoTestnet => Some(EvmClient::from_network(&NetworkType::ZooTestnet)),
555 NetworkType::ZooMainnet => Some(EvmClient::from_network(&NetworkType::HanzoMainnet)),
556 NetworkType::ZooTestnet => Some(EvmClient::from_network(&NetworkType::HanzoTestnet)),
557 NetworkType::Custom(_) => None,
558 };
559
560 Self {
561 primary_network,
562 primary_client,
563 secondary_client,
564 pending_rewards: Arc::new(RwLock::new(Vec::new())),
565 total_claimed: Arc::new(RwLock::new(0)),
566 bridge_transfers: Arc::new(RwLock::new(Vec::new())),
567 }
568 }
569
570 pub async fn refresh_pending_rewards(&self, miner_address: &str) -> Result<u128, EvmError> {
572 let primary_rewards = self.primary_client.get_pending_rewards(miner_address).await?;
573
574 let secondary_rewards = if let Some(client) = &self.secondary_client {
575 client.get_pending_rewards(miner_address).await.unwrap_or(0)
576 } else {
577 0
578 };
579
580 Ok(primary_rewards + secondary_rewards)
581 }
582
583 pub async fn claim_primary_rewards(&self, private_key: &str) -> Result<String, EvmError> {
585 let tx_hash = self.primary_client.claim_rewards(private_key).await?;
586 Ok(tx_hash)
587 }
588
589 pub async fn claim_secondary_rewards(&self, private_key: &str) -> Result<Option<String>, EvmError> {
591 if let Some(client) = &self.secondary_client {
592 let tx_hash = client.claim_rewards(private_key).await?;
593 Ok(Some(tx_hash))
594 } else {
595 Ok(None)
596 }
597 }
598
599 pub async fn claim_all_rewards(&self, private_key: &str) -> Result<Vec<String>, EvmError> {
601 let mut tx_hashes = Vec::new();
602
603 let primary_tx = self.primary_client.claim_rewards(private_key).await?;
605 tx_hashes.push(primary_tx);
606
607 if let Some(client) = &self.secondary_client {
609 if let Ok(tx) = client.claim_rewards(private_key).await {
610 tx_hashes.push(tx);
611 }
612 }
613
614 Ok(tx_hashes)
615 }
616
617 pub async fn bridge_to_secondary(
619 &self,
620 private_key: &str,
621 amount: u128,
622 recipient: &str,
623 ) -> Result<String, EvmError> {
624 let dest_chain = match &self.primary_network {
625 NetworkType::HanzoMainnet => NetworkType::ZooMainnet,
626 NetworkType::HanzoTestnet => NetworkType::ZooTestnet,
627 NetworkType::ZooMainnet => NetworkType::HanzoMainnet,
628 NetworkType::ZooTestnet => NetworkType::HanzoTestnet,
629 NetworkType::Custom(_) => return Err(EvmError::UnsupportedChain),
630 };
631
632 self.primary_client.bridge_tokens(private_key, &dest_chain, amount, recipient).await
633 }
634
635 pub async fn get_total_balance(&self, address: &str) -> Result<u128, EvmError> {
637 let primary_balance = self.primary_client.get_balance(address).await?;
638
639 let secondary_balance = if let Some(client) = &self.secondary_client {
640 client.get_balance(address).await.unwrap_or(0)
641 } else {
642 0
643 };
644
645 Ok(primary_balance + secondary_balance)
646 }
647
648 pub async fn get_rewards_summary(&self, address: &str) -> Result<RewardsSummary, EvmError> {
650 let primary_pending = self.primary_client.get_pending_rewards(address).await?;
651 let primary_balance = self.primary_client.get_balance(address).await?;
652
653 let (secondary_pending, secondary_balance) = if let Some(client) = &self.secondary_client {
654 let pending = client.get_pending_rewards(address).await.unwrap_or(0);
655 let balance = client.get_balance(address).await.unwrap_or(0);
656 (pending, balance)
657 } else {
658 (0, 0)
659 };
660
661 Ok(RewardsSummary {
662 primary_network: self.primary_network.clone(),
663 primary_pending,
664 primary_balance,
665 secondary_pending,
666 secondary_balance,
667 total_pending: primary_pending + secondary_pending,
668 total_balance: primary_balance + secondary_balance,
669 total_claimed: *self.total_claimed.read().await,
670 })
671 }
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize)]
675pub struct RewardsSummary {
676 pub primary_network: NetworkType,
677 pub primary_pending: u128,
678 pub primary_balance: u128,
679 pub secondary_pending: u128,
680 pub secondary_balance: u128,
681 pub total_pending: u128,
682 pub total_balance: u128,
683 pub total_claimed: u128,
684}
685
686impl RewardsSummary {
687 pub fn format_primary_pending(&self) -> f64 {
689 self.primary_pending as f64 / 1e18
690 }
691
692 pub fn format_total_balance(&self) -> f64 {
693 self.total_balance as f64 / 1e18
694 }
695}
696
697#[derive(Debug, thiserror::Error)]
699pub enum EvmError {
700 #[error("RPC error: {0}")]
701 RpcError(String),
702 #[error("Invalid response from RPC")]
703 InvalidResponse,
704 #[error("Transaction failed: {0}")]
705 TransactionFailed(String),
706 #[error("Insufficient balance")]
707 InsufficientBalance,
708 #[error("Unsupported chain for this operation")]
709 UnsupportedChain,
710 #[error("Contract error: {0}")]
711 ContractError(String),
712}
713
714fn encode_function_call(signature: &str, _params: &[&str]) -> String {
716 let selector = keccak256_selector(signature);
719 format!("0x{}", selector)
720}
721
722fn keccak256_selector(signature: &str) -> String {
723 let hash = blake3::hash(signature.as_bytes());
726 hex::encode(&hash.as_bytes()[..4])
727}
728
729fn encode_capabilities(capabilities: &[MinerCapability]) -> String {
730 let encoded: Vec<u8> = capabilities.iter().map(|c| {
732 match c {
733 MinerCapability::Embedding => 0x01,
734 MinerCapability::Reranking => 0x02,
735 MinerCapability::Inference => 0x03,
736 MinerCapability::Training => 0x04,
737 MinerCapability::Quantization => 0x05,
738 MinerCapability::Storage => 0x06,
739 MinerCapability::Custom(_) => 0xFF,
740 }
741 }).collect();
742 hex::encode(&encoded)
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748
749 #[test]
750 fn test_chain_configs() {
751 let hanzo = ChainConfig::hanzo_mainnet();
752 assert_eq!(hanzo.chain_id, 36963);
753 assert_eq!(hanzo.token_symbol, "HAI");
754
755 let zoo = ChainConfig::zoo_mainnet();
756 assert_eq!(zoo.chain_id, 200200);
757 assert_eq!(zoo.token_symbol, "ZOO");
758 }
759
760 #[test]
761 fn test_network_to_config() {
762 let config = ChainConfig::from_network(&NetworkType::HanzoMainnet);
763 assert_eq!(config.chain_id, 36963);
764
765 let config = ChainConfig::from_network(&NetworkType::ZooMainnet);
766 assert_eq!(config.chain_id, 200200);
767 }
768
769 #[test]
770 fn test_encode_capabilities() {
771 let caps = vec![
772 MinerCapability::Embedding,
773 MinerCapability::Inference,
774 ];
775 let encoded = encode_capabilities(&caps);
776 assert_eq!(encoded, "0103"); }
778
779 #[tokio::test]
780 async fn test_rewards_manager_creation() {
781 let manager = RewardsManager::new(NetworkType::HanzoMainnet);
782 assert!(manager.secondary_client.is_some());
783
784 let manager = RewardsManager::new(NetworkType::ZooMainnet);
785 assert!(manager.secondary_client.is_some());
786 }
787
788 #[test]
789 fn test_rewards_summary_formatting() {
790 let summary = RewardsSummary {
791 primary_network: NetworkType::HanzoMainnet,
792 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,
797 total_balance: 15_000_000_000_000_000_000,
798 total_claimed: 100_000_000_000_000_000_000,
799 };
800
801 assert!((summary.format_primary_pending() - 1.0).abs() < 0.001);
802 assert!((summary.format_total_balance() - 15.0).abs() < 0.001);
803 }
804}