1use crate::error::BitcoinError;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub enum RskNetwork {
18 Mainnet,
20 Testnet,
22 Regtest,
24}
25
26impl RskNetwork {
27 pub fn rpc_url(&self) -> &str {
29 match self {
30 RskNetwork::Mainnet => "https://public-node.rsk.co",
31 RskNetwork::Testnet => "https://public-node.testnet.rsk.co",
32 RskNetwork::Regtest => "http://localhost:4444",
33 }
34 }
35
36 pub fn chain_id(&self) -> u64 {
38 match self {
39 RskNetwork::Mainnet => 30,
40 RskNetwork::Testnet => 31,
41 RskNetwork::Regtest => 33,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48pub struct RskAddress {
49 pub address: String,
51}
52
53impl RskAddress {
54 pub fn new(address: String) -> Result<Self, BitcoinError> {
56 if !address.starts_with("0x") {
58 return Err(BitcoinError::InvalidAddress(
59 "RSK address must start with 0x".to_string(),
60 ));
61 }
62
63 if address.len() != 42 {
64 return Err(BitcoinError::InvalidAddress(
65 "RSK address must be 42 characters (0x + 40 hex)".to_string(),
66 ));
67 }
68
69 Ok(Self { address })
70 }
71
72 pub fn to_lowercase(&self) -> String {
74 self.address.to_lowercase()
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum PegOperation {
81 PegIn,
83 PegOut,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89pub enum PegStatus {
90 Pending,
92 Confirming,
94 Processing,
96 Completed,
98 Failed,
100 Refunded,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct PegInTransaction {
107 pub id: Uuid,
109 pub btc_tx_id: String,
111 pub amount: u64,
113 pub btc_address: String,
115 pub rsk_address: RskAddress,
117 pub rsk_tx_hash: Option<String>,
119 pub btc_confirmations: u32,
121 pub status: PegStatus,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct PegOutTransaction {
128 pub id: Uuid,
130 pub rsk_tx_hash: String,
132 pub amount: u64,
134 pub rsk_address: RskAddress,
136 pub btc_address: String,
138 pub btc_tx_id: Option<String>,
140 pub rsk_confirmations: u32,
142 pub status: PegStatus,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct PegConfig {
149 pub min_amount: u64,
151 pub max_amount: u64,
153 pub btc_confirmations_required: u32,
155 pub rsk_confirmations_required: u32,
157 pub federation_address: String,
159}
160
161impl Default for PegConfig {
162 fn default() -> Self {
163 Self {
164 min_amount: 5_000, max_amount: 1_000_000_000, btc_confirmations_required: 100,
167 rsk_confirmations_required: 100,
168 federation_address: String::new(),
169 }
170 }
171}
172
173pub struct RskClient {
175 network: RskNetwork,
177 #[allow(dead_code)]
179 rpc_url: String,
180 #[allow(dead_code)]
182 http_client: reqwest::Client,
183}
184
185impl RskClient {
186 pub fn new(network: RskNetwork) -> Self {
188 Self {
189 network,
190 rpc_url: network.rpc_url().to_string(),
191 http_client: reqwest::Client::new(),
192 }
193 }
194
195 pub fn network(&self) -> RskNetwork {
197 self.network
198 }
199
200 pub fn validate_address(&self, address: &str) -> Result<RskAddress, BitcoinError> {
202 RskAddress::new(address.to_string())
203 }
204
205 pub async fn get_balance(&self, address: &RskAddress) -> Result<u64, BitcoinError> {
207 let _ = address;
210 Ok(0)
211 }
212
213 pub async fn get_transaction_count(&self, address: &RskAddress) -> Result<u64, BitcoinError> {
215 let _ = address;
218 Ok(0)
219 }
220
221 pub async fn get_transaction_receipt(
223 &self,
224 tx_hash: &str,
225 ) -> Result<RskTransactionReceipt, BitcoinError> {
226 let _ = tx_hash;
229 Err(BitcoinError::TransactionNotFound(
230 "Not implemented".to_string(),
231 ))
232 }
233
234 pub async fn get_block_number(&self) -> Result<u64, BitcoinError> {
236 Ok(0)
239 }
240
241 pub async fn send_raw_transaction(&self, tx_hex: &str) -> Result<String, BitcoinError> {
243 let _ = tx_hex;
246 Err(BitcoinError::BroadcastFailed("Not implemented".to_string()))
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct RskTransactionReceipt {
253 pub transaction_hash: String,
255 pub block_number: u64,
257 pub block_hash: String,
259 pub from: RskAddress,
261 pub to: Option<RskAddress>,
263 pub gas_used: u64,
265 pub status: u8,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct RskContractDeployment {
272 pub id: Uuid,
274 pub bytecode: String,
276 pub constructor_args: Vec<String>,
278 pub deployer: RskAddress,
280 pub gas_limit: u64,
282 pub gas_price: u64,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct RskDeploymentResult {
289 pub deployment_id: Uuid,
291 pub tx_hash: Option<String>,
293 pub contract_address: Option<RskAddress>,
295 pub status: DeploymentStatus,
297 pub error: Option<String>,
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
303pub enum DeploymentStatus {
304 Pending,
306 Broadcast,
308 Confirmed,
310 Failed,
312}
313
314pub struct PegManager {
316 config: PegConfig,
318 rsk_client: RskClient,
320 peg_ins: HashMap<Uuid, PegInTransaction>,
322 peg_outs: HashMap<Uuid, PegOutTransaction>,
324}
325
326impl PegManager {
327 pub fn new(config: PegConfig, rsk_client: RskClient) -> Self {
329 Self {
330 config,
331 rsk_client,
332 peg_ins: HashMap::new(),
333 peg_outs: HashMap::new(),
334 }
335 }
336
337 pub fn initiate_peg_in(
339 &mut self,
340 btc_tx_id: String,
341 amount: u64,
342 btc_address: String,
343 rsk_address: RskAddress,
344 ) -> Result<PegInTransaction, BitcoinError> {
345 if amount < self.config.min_amount {
347 return Err(BitcoinError::Validation(format!(
348 "Amount {} is below minimum {}",
349 amount, self.config.min_amount
350 )));
351 }
352
353 if amount > self.config.max_amount {
354 return Err(BitcoinError::Validation(format!(
355 "Amount {} exceeds maximum {}",
356 amount, self.config.max_amount
357 )));
358 }
359
360 let tx = PegInTransaction {
361 id: Uuid::new_v4(),
362 btc_tx_id,
363 amount,
364 btc_address,
365 rsk_address,
366 rsk_tx_hash: None,
367 btc_confirmations: 0,
368 status: PegStatus::Pending,
369 };
370
371 self.peg_ins.insert(tx.id, tx.clone());
372 Ok(tx)
373 }
374
375 pub fn initiate_peg_out(
377 &mut self,
378 rsk_tx_hash: String,
379 amount: u64,
380 rsk_address: RskAddress,
381 btc_address: String,
382 ) -> Result<PegOutTransaction, BitcoinError> {
383 if amount < self.config.min_amount {
385 return Err(BitcoinError::Validation(format!(
386 "Amount {} is below minimum {}",
387 amount, self.config.min_amount
388 )));
389 }
390
391 if amount > self.config.max_amount {
392 return Err(BitcoinError::Validation(format!(
393 "Amount {} exceeds maximum {}",
394 amount, self.config.max_amount
395 )));
396 }
397
398 let tx = PegOutTransaction {
399 id: Uuid::new_v4(),
400 rsk_tx_hash,
401 amount,
402 rsk_address,
403 btc_address,
404 btc_tx_id: None,
405 rsk_confirmations: 0,
406 status: PegStatus::Pending,
407 };
408
409 self.peg_outs.insert(tx.id, tx.clone());
410 Ok(tx)
411 }
412
413 pub fn update_peg_in(
415 &mut self,
416 id: Uuid,
417 btc_confirmations: u32,
418 rsk_tx_hash: Option<String>,
419 ) -> Result<(), BitcoinError> {
420 let tx = self
421 .peg_ins
422 .get_mut(&id)
423 .ok_or_else(|| BitcoinError::TransactionNotFound(id.to_string()))?;
424
425 tx.btc_confirmations = btc_confirmations;
426
427 if let Some(hash) = rsk_tx_hash {
428 tx.rsk_tx_hash = Some(hash);
429 }
430
431 if btc_confirmations >= self.config.btc_confirmations_required {
433 if tx.rsk_tx_hash.is_some() {
434 tx.status = PegStatus::Completed;
435 } else {
436 tx.status = PegStatus::Processing;
437 }
438 } else {
439 tx.status = PegStatus::Confirming;
440 }
441
442 Ok(())
443 }
444
445 pub fn update_peg_out(
447 &mut self,
448 id: Uuid,
449 rsk_confirmations: u32,
450 btc_tx_id: Option<String>,
451 ) -> Result<(), BitcoinError> {
452 let tx = self
453 .peg_outs
454 .get_mut(&id)
455 .ok_or_else(|| BitcoinError::TransactionNotFound(id.to_string()))?;
456
457 tx.rsk_confirmations = rsk_confirmations;
458
459 if let Some(txid) = btc_tx_id {
460 tx.btc_tx_id = Some(txid);
461 }
462
463 if rsk_confirmations >= self.config.rsk_confirmations_required {
465 if tx.btc_tx_id.is_some() {
466 tx.status = PegStatus::Completed;
467 } else {
468 tx.status = PegStatus::Processing;
469 }
470 } else {
471 tx.status = PegStatus::Confirming;
472 }
473
474 Ok(())
475 }
476
477 pub fn get_peg_in(&self, id: &Uuid) -> Option<&PegInTransaction> {
479 self.peg_ins.get(id)
480 }
481
482 pub fn get_peg_out(&self, id: &Uuid) -> Option<&PegOutTransaction> {
484 self.peg_outs.get(id)
485 }
486
487 pub fn rsk_client(&self) -> &RskClient {
489 &self.rsk_client
490 }
491
492 pub fn federation_address(&self) -> &str {
494 &self.config.federation_address
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn test_rsk_address_validation() {
504 let addr = RskAddress::new("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0".to_string());
505 assert!(addr.is_ok());
506
507 let invalid = RskAddress::new("742d35Cc6634C0532925a3b844Bc9e7595f0bEb0".to_string());
508 assert!(invalid.is_err());
509
510 let too_short = RskAddress::new("0x742d35Cc".to_string());
511 assert!(too_short.is_err());
512 }
513
514 #[test]
515 fn test_rsk_network_chain_ids() {
516 assert_eq!(RskNetwork::Mainnet.chain_id(), 30);
517 assert_eq!(RskNetwork::Testnet.chain_id(), 31);
518 assert_eq!(RskNetwork::Regtest.chain_id(), 33);
519 }
520
521 #[test]
522 fn test_peg_config_defaults() {
523 let config = PegConfig::default();
524 assert_eq!(config.min_amount, 5_000);
525 assert_eq!(config.max_amount, 1_000_000_000);
526 assert_eq!(config.btc_confirmations_required, 100);
527 assert_eq!(config.rsk_confirmations_required, 100);
528 }
529
530 #[test]
531 fn test_peg_manager_peg_in() {
532 let config = PegConfig::default();
533 let client = RskClient::new(RskNetwork::Testnet);
534 let mut manager = PegManager::new(config, client);
535
536 let rsk_addr =
537 RskAddress::new("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0".to_string()).unwrap();
538
539 let result = manager.initiate_peg_in(
540 "btc_txid".to_string(),
541 100_000,
542 "bc1qtest".to_string(),
543 rsk_addr,
544 );
545
546 assert!(result.is_ok());
547 let tx = result.unwrap();
548 assert_eq!(tx.amount, 100_000);
549 assert_eq!(tx.status, PegStatus::Pending);
550 }
551
552 #[test]
553 fn test_peg_manager_amount_validation() {
554 let config = PegConfig::default();
555 let client = RskClient::new(RskNetwork::Testnet);
556 let mut manager = PegManager::new(config, client);
557
558 let rsk_addr =
559 RskAddress::new("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0".to_string()).unwrap();
560
561 let result = manager.initiate_peg_in(
563 "btc_txid".to_string(),
564 100,
565 "bc1qtest".to_string(),
566 rsk_addr.clone(),
567 );
568 assert!(result.is_err());
569
570 let result = manager.initiate_peg_in(
572 "btc_txid".to_string(),
573 2_000_000_000,
574 "bc1qtest".to_string(),
575 rsk_addr,
576 );
577 assert!(result.is_err());
578 }
579
580 #[test]
581 fn test_peg_in_status_updates() {
582 let config = PegConfig::default();
583 let client = RskClient::new(RskNetwork::Testnet);
584 let mut manager = PegManager::new(config, client);
585
586 let rsk_addr =
587 RskAddress::new("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0".to_string()).unwrap();
588
589 let tx = manager
590 .initiate_peg_in(
591 "btc_txid".to_string(),
592 100_000,
593 "bc1qtest".to_string(),
594 rsk_addr,
595 )
596 .unwrap();
597
598 manager.update_peg_in(tx.id, 50, None).unwrap();
600 let updated = manager.get_peg_in(&tx.id).unwrap();
601 assert_eq!(updated.status, PegStatus::Confirming);
602
603 manager
605 .update_peg_in(tx.id, 100, Some("rsk_tx_hash".to_string()))
606 .unwrap();
607 let completed = manager.get_peg_in(&tx.id).unwrap();
608 assert_eq!(completed.status, PegStatus::Completed);
609 }
610
611 #[test]
612 fn test_rsk_client_network() {
613 let client = RskClient::new(RskNetwork::Mainnet);
614 assert_eq!(client.network(), RskNetwork::Mainnet);
615 }
616}