Skip to main content

soon_migrate/
oracle.rs

1//! Oracle detection and analysis module for the SOON Network migration tool.
2//!
3//! This module provides functionality to detect and analyze oracle usage in Solana programs,
4//! with support for various oracle providers like Pyth, Switchboard, and Chainlink.
5
6use crate::errors::MigrationError;
7use colored::*;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fs;
11use std::path::Path;
12
13/// Represents a detected oracle usage in the codebase.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct OracleDetection {
16    /// Type of the detected oracle
17    pub oracle_type: OracleType,
18    /// Confidence level of the detection
19    pub confidence: ConfidenceLevel,
20    /// Locations in the code where this oracle was detected
21    pub locations: Vec<DetectionLocation>,
22 /// Suggested migration steps for this oracle
23    pub migration_suggestion: String,
24}
25
26/// Types of oracles that can be detected in the codebase.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum OracleType {
29    /// Pyth Network oracle
30    Pyth,
31    /// Switchboard oracle
32    Switchboard,
33    /// Chainlink oracle
34    Chainlink,
35    /// DIA oracle
36    DIA,
37    /// RedStone oracle
38    RedStone,
39    /// APRO oracle
40    APRO,
41    /// Unknown oracle type
42    Unknown,
43}
44
45/// Confidence level for oracle detections
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum ConfidenceLevel {
48    /// High confidence - Found in dependencies and code usage
49    High,
50    /// Medium confidence - Found in dependencies OR clear code patterns
51    Medium,
52    /// Low confidence - Found in comments or weak patterns
53    Low,
54}
55
56/// Represents a location in the source code where an oracle was detected
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct DetectionLocation {
59    /// Path to the file where the oracle was detected
60    pub file_path: String,
61    /// Line number where the oracle was detected (if available)
62    pub line_number: Option<usize>,
63    /// The code pattern that was matched
64    pub pattern_matched: String,
65    /// Additional context about the detection
66    pub context: String,
67}
68
69/// A report containing all detected oracles and migration recommendations
70#[derive(Debug, Serialize, Deserialize)]
71pub struct OracleReport {
72    /// List of all detected oracles in the project
73    pub detected_oracles: Vec<OracleDetection>,
74    /// List of recommendations for migrating the detected oracles
75    pub migration_recommendations: Vec<String>,
76    /// Optional APRO integration guide for the detected oracles
77    pub apro_integration_guide: Option<String>,
78}
79
80/// Detector for identifying oracle usage in Solana programs
81pub struct OracleDetector;
82
83impl OracleDetector {
84    /// Scans a Solana project for oracle usage and generates a detailed report.
85    ///
86    /// This function analyzes the project's source code and dependencies to detect
87    /// any oracle integrations, such as Pyth, Switchboard, or Chainlink.
88    ///
89    /// # Arguments
90    /// * `path` - Path to the root directory of the Solana project
91    /// * `verbose` - Whether to enable verbose output during scanning
92    ///
93    /// # Returns
94    /// A `Result` containing an `OracleReport` with the scan results, or a `MigrationError`
95    /// if the scan fails.
96    ///
97    /// # Examples
98    /// ```no_run
99    /// use soon_migrate::oracle::OracleDetector;
100    ///
101    /// let report = OracleDetector::scan_project("./my_project", true).unwrap();
102    /// println!("Found {} oracle usages", report.detected_oracles.len());
103    /// ```
104    pub fn scan_project(path: &str, verbose: bool) -> Result<OracleReport, MigrationError> {
105        if verbose {
106            println!("{}", "Scanning project for oracle usage...".cyan());
107        }
108
109        let mut detected_oracles = Vec::new();
110        
111        // Scan Cargo.toml for oracle dependencies
112        let cargo_detections = Self::scan_cargo_toml(path)?;
113        detected_oracles.extend(cargo_detections);
114
115        // Scan Rust files for oracle imports and usage
116        let code_detections = Self::scan_rust_files(path)?;
117        detected_oracles.extend(code_detections);
118
119        // Merge and deduplicate detections
120        let merged_detections = Self::merge_detections(detected_oracles);
121        
122        // Generate migration recommendations
123        let recommendations = Self::generate_recommendations(&merged_detections);
124        
125        // Generate APRO integration guide if oracles detected
126        let apro_guide = if !merged_detections.is_empty() {
127            Some(Self::generate_apro_guide(&merged_detections))
128        } else {
129            None
130        };
131
132        Ok(OracleReport {
133            detected_oracles: merged_detections,
134            migration_recommendations: recommendations,
135            apro_integration_guide: apro_guide,
136        })
137    }
138
139    /// Scan Cargo.toml for oracle-related dependencies
140    ///
141    /// # Arguments
142    /// * `path` - Path to the project directory
143    ///
144    /// # Returns
145    /// A `Result` containing a list of oracle detections or an error
146    fn scan_cargo_toml(path: &str) -> Result<Vec<OracleDetection>, MigrationError> {
147        let cargo_path = Path::new(path).join("Cargo.toml");
148        if !cargo_path.exists() {
149            return Ok(Vec::new());
150        }
151
152        let content = fs::read_to_string(&cargo_path)
153            .map_err(|e| MigrationError::OracleDetectionFailed(format!("Failed to read Cargo.toml: {}", e)))?;
154
155        let mut detections = Vec::new();
156
157        // Pyth detection
158        if content.contains("pyth-solana-receiver-sdk") {
159            detections.push(OracleDetection {
160                oracle_type: OracleType::Pyth,
161                confidence: ConfidenceLevel::High,
162                locations: vec![DetectionLocation {
163                    file_path: "Cargo.toml".to_string(),
164                    line_number: Self::find_line_number(&content, "pyth-solana-receiver-sdk"),
165                    pattern_matched: "pyth-solana-receiver-sdk".to_string(),
166                    context: "Cargo.toml dependency".to_string(),
167                }],
168                migration_suggestion: "Consider migrating to APRO oracle for SOON Network compatibility. APRO provides similar price feed functionality with better performance.".to_string(),
169            });
170        }
171
172        // Switchboard detection
173        if content.contains("switchboard-v2") || content.contains("switchboard_on_demand") {
174            let pattern = if content.contains("switchboard-v2") { "switchboard-v2" } else { "switchboard_on_demand" };
175            detections.push(OracleDetection {
176                oracle_type: OracleType::Switchboard,
177                confidence: ConfidenceLevel::High,
178                locations: vec![DetectionLocation {
179                    file_path: "Cargo.toml".to_string(),
180                    line_number: Self::find_line_number(&content, pattern),
181                    pattern_matched: pattern.to_string(),
182                    context: "Cargo.toml dependency".to_string(),
183                }],
184                migration_suggestion: "APRO oracle offers customizable data feeds similar to Switchboard with enhanced performance on SOON Network.".to_string(),
185            });
186        }
187
188        // Chainlink detection
189        if content.contains("chainlink_solana") {
190            detections.push(OracleDetection {
191                oracle_type: OracleType::Chainlink,
192                confidence: ConfidenceLevel::High,
193                locations: vec![DetectionLocation {
194                    file_path: "Cargo.toml".to_string(),
195                    line_number: Self::find_line_number(&content, "chainlink_solana"),
196                    pattern_matched: "chainlink_solana".to_string(),
197                    context: "Cargo.toml dependency".to_string(),
198                }],
199                migration_suggestion: "APRO oracle provides reliable price feeds compatible with Chainlink's API patterns for seamless migration.".to_string(),
200            });
201        }
202
203        Ok(detections)
204    }
205
206    /// Scan Rust source files for oracle usage patterns
207    ///
208    /// # Arguments
209    /// * `path` - Path to the project directory
210    ///
211    /// # Returns
212    /// A `Result` containing a list of oracle detections or an error
213    fn scan_rust_files(path: &str) -> Result<Vec<OracleDetection>, MigrationError> {
214        let mut detections = Vec::new();
215        
216        // Walk through all .rs files
217        Self::walk_directory(Path::new(path), &mut |file_path| {
218            if let Some(extension) = file_path.extension() {
219                if extension == "rs" {
220                    if let Ok(content) = fs::read_to_string(file_path) {
221                        detections.extend(Self::scan_rust_content(&content, file_path)?);
222                    }
223                }
224            }
225            Ok(())
226        })?;
227
228        Ok(detections)
229    }
230
231    /// Scan Rust source code content for oracle patterns
232    ///
233    /// # Arguments
234    /// * `content` - The Rust source code to scan
235    /// * `file_path` - Path to the source file (for reporting)
236    ///
237    /// # Returns
238    /// A `Result` containing a list of oracle detections or an error
239    fn scan_rust_content(content: &str, file_path: &Path) -> Result<Vec<OracleDetection>, MigrationError> {
240        let mut detections = Vec::new();
241        let file_path_str = file_path.to_string_lossy().to_string();
242
243        // Pyth patterns
244        let pyth_patterns = [
245            "use pyth_solana_receiver_sdk",
246            "PriceUpdateV2",
247            "get_price_no_older_than",
248            "pyth_solana_receiver_sdk::",
249        ];
250
251        for pattern in &pyth_patterns {
252            if content.contains(pattern) {
253                detections.push(OracleDetection {
254                    oracle_type: OracleType::Pyth,
255                    confidence: ConfidenceLevel::High,
256                    locations: vec![DetectionLocation {
257                        file_path: file_path_str.clone(),
258                        line_number: Self::find_line_number(content, pattern),
259                        pattern_matched: pattern.to_string(),
260                        context: format!("Rust code usage: {}", pattern),
261                    }],
262                    migration_suggestion: "Replace Pyth price feeds with APRO oracle integration. See APRO documentation for migration guide.".to_string(),
263                });
264                break; // Only add one detection per oracle type per file
265            }
266        }
267
268        // Switchboard patterns
269        let switchboard_patterns = [
270            "use switchboard_v2",
271            "use switchboard_on_demand",
272            "AggregatorAccountData",
273            "get_result",
274            "switchboard_v2::",
275        ];
276
277        for pattern in &switchboard_patterns {
278            if content.contains(pattern) {
279                detections.push(OracleDetection {
280                    oracle_type: OracleType::Switchboard,
281                    confidence: ConfidenceLevel::High,
282                    locations: vec![DetectionLocation {
283                        file_path: file_path_str.clone(),
284                        line_number: Self::find_line_number(content, pattern),
285                        pattern_matched: pattern.to_string(),
286                        context: format!("Rust code usage: {}", pattern),
287                    }],
288                    migration_suggestion: "Replace Switchboard aggregators with APRO oracle data feeds for SOON Network compatibility.".to_string(),
289                });
290                break;
291            }
292        }
293
294        // Chainlink patterns
295        let chainlink_patterns = [
296            "use chainlink_solana",
297            "latest_round_data",
298            "chainlink_solana::",
299            "chainlink::",
300        ];
301
302        for pattern in &chainlink_patterns {
303            if content.contains(pattern) {
304                detections.push(OracleDetection {
305                    oracle_type: OracleType::Chainlink,
306                    confidence: ConfidenceLevel::High,
307                    locations: vec![DetectionLocation {
308                        file_path: file_path_str.clone(),
309                        line_number: Self::find_line_number(content, pattern),
310                        pattern_matched: pattern.to_string(),
311                        context: format!("Rust code usage: {}", pattern),
312                    }],
313                    migration_suggestion: "Migrate Chainlink price feeds to APRO oracle for enhanced performance on SOON Network.".to_string(),
314                });
315                break;
316            }
317        }
318
319        // DIA patterns (weaker detection)
320        let dia_patterns = [
321            "CoinInfo",
322            "// DIA",
323            "dia oracle",
324            "DIA Oracle",
325        ];
326
327        for pattern in &dia_patterns {
328            if content.to_lowercase().contains(&pattern.to_lowercase()) {
329                detections.push(OracleDetection {
330                    oracle_type: OracleType::DIA,
331                    confidence: ConfidenceLevel::Low,
332                    locations: vec![DetectionLocation {
333                        file_path: file_path_str.clone(),
334                        line_number: Self::find_line_number(content, pattern),
335                        pattern_matched: pattern.to_string(),
336                        context: format!("Potential DIA usage: {}", pattern),
337                    }],
338                    migration_suggestion: "If using DIA oracle, consider migrating to APRO for better SOON Network integration.".to_string(),
339                });
340                break;
341            }
342        }
343
344        // RedStone patterns (weaker detection)
345        let redstone_patterns = [
346            "redstone",
347            "RedStone",
348            "// RedStone",
349            "wormhole",
350        ];
351
352        for pattern in &redstone_patterns {
353            if content.contains(pattern) {
354                detections.push(OracleDetection {
355                    oracle_type: OracleType::RedStone,
356                    confidence: ConfidenceLevel::Low,
357                    locations: vec![DetectionLocation {
358                        file_path: file_path_str.clone(),
359                        line_number: Self::find_line_number(content, pattern),
360                        pattern_matched: pattern.to_string(),
361                        context: format!("Potential RedStone usage: {}", pattern),
362                    }],
363                    migration_suggestion: "If using RedStone oracle, APRO provides similar RWA oracle capabilities for SOON Network.".to_string(),
364                });
365                break;
366            }
367        }
368
369        Ok(detections)
370    }
371
372    /// Recursively walk a directory and apply a callback to each file
373    ///
374    /// # Arguments
375    /// * `dir` - Directory to walk
376    /// * `callback` - Callback function to apply to each file
377    ///
378    /// # Returns
379    /// A `Result` indicating success or an error
380    fn walk_directory<F>(dir: &Path, callback: &mut F) -> Result<(), MigrationError>
381    where
382        F: FnMut(&Path) -> Result<(), MigrationError>,
383    {
384        if dir.is_dir() {
385            let entries = fs::read_dir(dir)
386                .map_err(|e| MigrationError::OracleDetectionFailed(format!("Failed to read directory: {}", e)))?;
387            
388            for entry in entries {
389                let entry = entry
390                    .map_err(|e| MigrationError::OracleDetectionFailed(format!("Failed to read directory entry: {}", e)))?;
391                let path = entry.path();
392                
393                if path.is_dir() {
394                    // Skip common directories that won't contain oracle code
395                    if let Some(dir_name) = path.file_name() {
396                        let dir_str = dir_name.to_string_lossy();
397                        if dir_str == "target" || dir_str == "node_modules" || dir_str == ".git" {
398                            continue;
399                        }
400                    }
401                    Self::walk_directory(&path, callback)?;
402                } else {
403                    callback(&path)?;
404                }
405            }
406        }
407        Ok(())
408    }
409
410    /// Find the line number of a pattern in a string
411    ///
412    /// # Arguments
413    /// * `content` - The content to search in
414    /// * `pattern` - The pattern to search for
415    ///
416    /// # Returns
417    /// The line number where the pattern was found, or `None` if not found
418    fn find_line_number(content: &str, pattern: &str) -> Option<usize> {
419        content
420            .lines()
421            .enumerate()
422            .find(|(_, line)| line.contains(pattern))
423            .map(|(i, _)| i + 1)
424    }
425
426    /// Merge duplicate oracle detections
427    ///
428    /// # Arguments
429    /// * `detections` - List of detections to merge
430    ///
431    /// # Returns
432    /// A deduplicated list of detections with merged locations
433    fn merge_detections(detections: Vec<OracleDetection>) -> Vec<OracleDetection> {
434        let mut oracle_map: HashMap<String, OracleDetection> = HashMap::new();
435        
436        for detection in detections {
437            let key = format!("{:?}", detection.oracle_type);
438            
439            if let Some(existing) = oracle_map.get_mut(&key) {
440                // Merge locations
441                existing.locations.extend(detection.locations);
442                // Use highest confidence
443                if matches!(detection.confidence, ConfidenceLevel::High) {
444                    existing.confidence = ConfidenceLevel::High;
445                } else if matches!(detection.confidence, ConfidenceLevel::Medium) && matches!(existing.confidence, ConfidenceLevel::Low) {
446                    existing.confidence = ConfidenceLevel::Medium;
447                }
448            } else {
449                oracle_map.insert(key, detection);
450            }
451        }
452        
453        oracle_map.into_values().collect()
454    }
455
456    /// Generate migration recommendations based on detected oracles
457    ///
458    /// # Arguments
459    /// * `detections` - List of detected oracles
460    ///
461    /// # Returns
462    /// A list of migration recommendations
463    fn generate_recommendations(detections: &[OracleDetection]) -> Vec<String> {
464        let mut recommendations = Vec::new();
465        
466        if detections.is_empty() {
467            recommendations.push("No oracle usage detected. Your project should migrate smoothly to SOON Network.".to_string());
468            return recommendations;
469        }
470
471        recommendations.push("🔍 Oracle usage detected in your project. Consider these migration steps:".to_string());
472        recommendations.push("".to_string());
473
474        for detection in detections {
475            let confidence_icon = match detection.confidence {
476                ConfidenceLevel::High => "🔴",
477                ConfidenceLevel::Medium => "🟡",
478                ConfidenceLevel::Low => "🟢",
479            };
480            
481            recommendations.push(format!("{} {:?} Oracle detected with {} confidence", 
482                confidence_icon, detection.oracle_type, 
483                format!("{:?}", detection.confidence).to_lowercase()));
484            
485            for location in &detection.locations {
486                if let Some(line_num) = location.line_number {
487                    recommendations.push(format!("   📁 {}:{} - {}", location.file_path, line_num, location.pattern_matched));
488                } else {
489                    recommendations.push(format!("   📁 {} - {}", location.file_path, location.pattern_matched));
490                }
491            }
492            recommendations.push(format!("   💡 {}", detection.migration_suggestion));
493            recommendations.push("".to_string());
494        }
495
496        recommendations.push("📚 Next steps:".to_string());
497        recommendations.push("1. Review the APRO oracle integration guide generated below".to_string());
498        recommendations.push("2. Update your dependencies to use APRO oracle SDK".to_string());
499        recommendations.push("3. Replace oracle-specific code with APRO equivalents".to_string());
500        recommendations.push("4. Test your price feed integrations on SOON devnet".to_string());
501
502        recommendations
503    }
504
505    /// Generate an APRO integration guide for the detected oracles
506    ///
507    /// # Arguments
508    /// * `detections` - List of detected oracles
509    ///
510    /// # Returns
511    /// A formatted APRO integration guide
512    fn generate_apro_guide(detections: &[OracleDetection]) -> String {
513        let mut guide = String::new();
514        
515        guide.push_str("# APRO Oracle Integration Guide for SOON Network\n\n");
516        guide.push_str("## Overview\n");
517        guide.push_str("APRO has chosen SOON as their first SVM chain to support oracle services. ");
518        guide.push_str("This guide will help you migrate your existing oracle integrations.\n\n");
519        
520        guide.push_str("## Program IDs\n");
521        guide.push_str("```\n");
522        guide.push_str("Devnet:  4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY\n");
523        guide.push_str("Mainnet: 4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY\n");
524        guide.push_str("```\n\n");
525        
526        guide.push_str("## API Endpoints\n");
527        guide.push_str("```\n");
528        guide.push_str("Devnet:  https://live-api-test.apro.com\n");
529        guide.push_str("Mainnet: https://live-api.apro.com\n");
530        guide.push_str("```\n\n");
531
532        // Add specific migration examples based on detected oracles
533        for detection in detections {
534            match detection.oracle_type {
535                OracleType::Pyth => {
536                    guide.push_str("## Migrating from Pyth\n");
537                    guide.push_str("Replace your Pyth price feed calls:\n");
538                    guide.push_str("```rust\n");
539                    guide.push_str("// Before (Pyth)\n");
540                    guide.push_str("use pyth_solana_receiver_sdk::PriceUpdateV2;\n");
541                    guide.push_str("let price = price_update.get_price_no_older_than(&clock, max_age)?;\n\n");
542                    guide.push_str("// After (APRO)\n");
543                    guide.push_str("use oracle_sdk::load_price_feed_from_account_info;\n");
544                    guide.push_str("let price_feed = load_price_feed_from_account_info(&price_account)?;\n");
545                    guide.push_str("let price = price_feed.benchmark_price;\n");
546                    guide.push_str("```\n\n");
547                }
548                OracleType::Switchboard => {
549                    guide.push_str("## Migrating from Switchboard\n");
550                    guide.push_str("Replace your Switchboard aggregator calls:\n");
551                    guide.push_str("```rust\n");
552                    guide.push_str("// Before (Switchboard)\n");
553                    guide.push_str("use switchboard_v2::AggregatorAccountData;\n");
554                    guide.push_str("let result = aggregator.get_result()?;\n\n");
555                    guide.push_str("// After (APRO)\n");
556                    guide.push_str("use oracle_sdk::load_price_feed_from_account_info;\n");
557                    guide.push_str("let price_feed = load_price_feed_from_account_info(&price_account)?;\n");
558                    guide.push_str("let result = price_feed.benchmark_price;\n");
559                    guide.push_str("```\n\n");
560                }
561                OracleType::Chainlink => {
562                    guide.push_str("## Migrating from Chainlink\n");
563                    guide.push_str("Replace your Chainlink price feed calls:\n");
564                    guide.push_str("```rust\n");
565                    guide.push_str("// Before (Chainlink)\n");
566                    guide.push_str("use chainlink_solana as chainlink;\n");
567                    guide.push_str("let round_data = chainlink::latest_round_data(ctx, &feed_account)?;\n\n");
568                    guide.push_str("// After (APRO)\n");
569                    guide.push_str("use oracle_sdk::load_price_feed_from_account_info;\n");
570                    guide.push_str("let price_feed = load_price_feed_from_account_info(&price_account)?;\n");
571                    guide.push_str("let price = price_feed.benchmark_price;\n");
572                    guide.push_str("```\n\n");
573                }
574                _ => {}
575            }
576        }
577
578        guide.push_str("## Available Price Feeds (Devnet)\n");
579        guide.push_str("- BTC/USD: 0x0003665949c883f9e0f6f002eac32e00bd59dfe6c34e92a91c37d6a8322d6489\n");
580        guide.push_str("- ETH/USD: 0x0003555ace6b39aae1b894097d0a9fc17f504c62fea598fa206cc6f5088e6e45\n");
581        guide.push_str("- SOL/USD: 0x000343ec7f6691d6bf679978bab5c093fa45ee74c0baac6cc75649dc59cc21d3\n");
582        guide.push_str("- USDT/USD: 0x00039a0c0be4e43cacda1599ac414205651f4a62b614b6be9e5318a182c33eb0\n");
583        guide.push_str("- USDC/USD: 0x00034b881a0c0fff844177f881a313ff894bfc6093d33b5514e34d7faa41b7ef\n\n");
584
585        guide.push_str("## Getting Started\n");
586        guide.push_str("1. Contact APRO BD team for authorization:\n");
587        guide.push_str("   - Email: bd@apro.com\n");
588        guide.push_str("   - Telegram: Head of Business Development\n");
589        guide.push_str("2. Add APRO oracle SDK to your Cargo.toml\n");
590        guide.push_str("3. Update your price feed integration code\n");
591        guide.push_str("4. Test on SOON devnet before mainnet deployment\n\n");
592
593        guide.push_str("For detailed integration examples, see the complete APRO documentation.\n");
594
595        guide
596    }
597
598    /// Print a formatted oracle detection report
599    ///
600    /// # Arguments
601    /// * `report` - The oracle report to print
602    /// * `verbose` - Whether to include detailed information
603    pub fn print_report(report: &OracleReport, verbose: bool) {
604        println!("{}", "=== Oracle Detection Report ===".bold().cyan());
605        println!();
606
607        if report.detected_oracles.is_empty() {
608            println!("{} No oracle usage detected", "✅".green());
609            println!("Your project should migrate smoothly to SOON Network without oracle changes.");
610            return;
611        }
612
613        println!("{} {} oracle(s) detected:", "🔍".yellow(), report.detected_oracles.len());
614        println!();
615
616        for detection in &report.detected_oracles {
617            let confidence_color = match detection.confidence {
618                ConfidenceLevel::High => "red",
619                ConfidenceLevel::Medium => "yellow", 
620                ConfidenceLevel::Low => "green",
621            };
622
623            println!("{} {:?} Oracle ({})", 
624                match detection.confidence {
625                    ConfidenceLevel::High => "🔴",
626                    ConfidenceLevel::Medium => "🟡",
627                    ConfidenceLevel::Low => "🟢",
628                },
629                detection.oracle_type,
630                format!("{:?} confidence", detection.confidence).color(confidence_color)
631            );
632
633            if verbose {
634                for location in &detection.locations {
635                    if let Some(line) = location.line_number {
636                        println!("  📁 {}:{} - {}", location.file_path, line, location.pattern_matched);
637                    } else {
638                        println!("  📁 {} - {}", location.file_path, location.pattern_matched);
639                    }
640                }
641            }
642
643            println!("  💡 {}", detection.migration_suggestion);
644            println!();
645        }
646
647        println!("{}", "=== Migration Recommendations ===".bold().cyan());
648        for rec in &report.migration_recommendations {
649            if rec.starts_with("🔍") || rec.starts_with("📚") {
650                println!("{}", rec.bold());
651            } else if rec.is_empty() {
652                println!();
653            } else {
654                println!("{}", rec);
655            }
656        }
657
658        if report.apro_integration_guide.is_some() {
659            println!();
660            println!("{}", "💡 Run with --show-guide to see the complete APRO integration guide".yellow());
661        }
662    }
663
664    /// Print the APRO integration guide if available
665    ///
666    /// # Arguments
667    /// * `report` - The oracle report containing the guide
668    pub fn print_integration_guide(report: &OracleReport) {
669        if let Some(guide) = &report.apro_integration_guide {
670            println!("{}", guide);
671        } else {
672            println!("No oracle integration guide available.");
673        }
674    }
675}
676
677impl std::fmt::Display for OracleType {
678    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679        match self {
680            OracleType::Pyth => write!(f, "Pyth"),
681            OracleType::Switchboard => write!(f, "Switchboard"), 
682            OracleType::Chainlink => write!(f, "Chainlink"),
683            OracleType::DIA => write!(f, "DIA"),
684            OracleType::RedStone => write!(f, "RedStone"),
685            OracleType::APRO => write!(f, "APRO"),
686            OracleType::Unknown => write!(f, "Unknown"),
687        }
688    }
689}