Skip to main content

fnzero_safe/
interactive.rs

1//! Interactive Menu Module
2//!
3//! Provides a simple interactive interface - no need to memorize commands
4//! 提供简单的交互式界面 - 无需记住命令
5
6use colored::*;
7use solana_sdk::signature::Keypair;
8use solana_sdk::signer::Signer;
9use std::io::{self, Write};
10
11use crate::KeyManager;
12
13/// Search for a keystore file recursively in subdirectories (max 3 levels deep).
14/// Returns the first match as a relative path, or None if not found.
15/// Skips directories named "dev" and prefers "prod" paths.
16fn search_keystore_in_subdirs(filename: &str) -> Option<String> {
17    fn search(
18        dir: &std::path::Path,
19        filename: &str,
20        depth: usize,
21        max_depth: usize,
22        results: &mut Vec<String>,
23    ) {
24        if depth > max_depth {
25            return;
26        }
27        if let Ok(entries) = std::fs::read_dir(dir) {
28            for entry in entries.flatten() {
29                let path = entry.path();
30                if path.is_dir() {
31                    let name = path.file_name().unwrap_or_default().to_string_lossy();
32                    // Skip hidden dirs, common large dirs, and "dev" dirs
33                    if name.starts_with('.')
34                        || name == "target"
35                        || name == "node_modules"
36                        || name == "dev"
37                    {
38                        continue;
39                    }
40                    search(&path, filename, depth + 1, max_depth, results);
41                } else if path.file_name().unwrap_or_default() == filename {
42                    results.push(path.to_string_lossy().to_string());
43                }
44            }
45        }
46    }
47    let mut results = Vec::new();
48    search(std::path::Path::new("."), filename, 0, 3, &mut results);
49    // Prefer prod path
50    results
51        .into_iter()
52        .find(|p| p.contains("prod"))
53        .or_else(|| {
54            // Fallback: return first non-dev result (shouldn't happen since we skip dev dirs)
55            let mut results = Vec::new();
56            search(std::path::Path::new("."), filename, 0, 3, &mut results);
57            results.into_iter().next()
58        })
59}
60
61/// Language selection
62#[derive(Clone, Copy, PartialEq)]
63pub enum Language {
64    English,
65    Chinese,
66}
67
68/// Text strings for bilingual UI
69struct Texts {
70    // Main menu
71    title: &'static str,
72    core_functions: &'static str,
73    create_plain: &'static str,
74    create_encrypted: &'static str,
75    decrypt: &'static str,
76    exit: &'static str,
77    select_option: &'static str,
78    goodbye: &'static str,
79    invalid_option: &'static str,
80    continue_use: &'static str,
81
82    // Plain key creation
83    create_plain_title: &'static str,
84    keypair_generated: &'static str,
85    public_key: &'static str,
86    private_key: &'static str,
87    output_method: &'static str,
88    display_only: &'static str,
89    save_to_file: &'static str,
90    select: &'static str,
91    file_path: &'static str,
92    file_saved: &'static str,
93    security_warning: &'static str,
94    plaintext_warning: &'static str,
95    save_securely: &'static str,
96    dont_share: &'static str,
97    recommend_encrypted: &'static str,
98
99    // Encrypted key creation
100    create_encrypted_title: &'static str,
101    choose_method: &'static str,
102    generate_new: &'static str,
103    import_existing: &'static str,
104    generating: &'static str,
105    enter_private_key: &'static str,
106    private_key_empty: &'static str,
107    keypair_ready: &'static str,
108    keystore_recommended: &'static str,
109    show_encrypted_string: &'static str,
110    keystore_created: &'static str,
111    private_key_encrypted: &'static str,
112    important_note: &'static str,
113    keep_safe: &'static str,
114    lost_password_warning: &'static str,
115    backup_recommended: &'static str,
116    encrypted_private_key: &'static str,
117    keep_safe_both: &'static str,
118
119    // Key decryption
120    decrypt_title: &'static str,
121    input_method: &'static str,
122    from_keystore: &'static str,
123    from_encrypted_string: &'static str,
124    encrypted_key: &'static str,
125    enter_password: &'static str,
126    decrypt_success: &'static str,
127    file_not_exist: &'static str,
128    dont_share_warning: &'static str,
129    delete_plaintext: &'static str,
130    use_encryption: &'static str,
131
132    // Password
133    set_password: &'static str,
134    new_password: &'static str,
135    confirm_password: &'static str,
136    password_empty: &'static str,
137    password_min_length: &'static str,
138    password_mismatch: &'static str,
139    password_set: &'static str,
140
141    // Errors
142    invalid_choice: &'static str,
143    write_failed: &'static str,
144}
145
146impl Texts {
147    fn chinese() -> Self {
148        Self {
149            title: "  FnzSafe - Solana 密钥管理工具",
150            core_functions: "核心功能 (只需3个操作):",
151            create_plain: "  {}  创建明文私钥",
152            create_encrypted: "  {}  创建加密私钥(bot)",
153            decrypt: "  {}  解密私钥",
154            exit: "  {}  退出",
155            select_option: "请输入选项 [0-14]: ",
156            goodbye: "👋 再见!",
157            invalid_option: "❌ 无效选项,请重新选择",
158            continue_use: "是否继续使用? [Y/n]: ",
159
160            create_plain_title: "  创建明文私钥",
161            keypair_generated: "✅ 密钥对生成成功!",
162            public_key: "公钥地址:",
163            private_key: "私钥:",
164            output_method: "输出方式:",
165            display_only: "  1. 仅显示 (当前已显示)",
166            save_to_file: "  2. 保存到文件",
167            select: "请选择 [1/2]: ",
168            file_path: "文件路径 (默认: keypair.json): ",
169            file_saved: "✅ 已保存到文件",
170            security_warning: "⚠️  安全警告:",
171            plaintext_warning: "  • 明文私钥非常不安全",
172            save_securely: "  • 请立即保存到安全位置",
173            dont_share: "  • 不要分享给任何人",
174            recommend_encrypted: "  • 建议使用 '创建加密私钥' 功能",
175
176            create_encrypted_title: "  创建加密私钥",
177            choose_method: "选择方式:",
178            generate_new: "  1. 生成新的密钥对并加密",
179            import_existing: "  2. 导入现有私钥并加密",
180            generating: "🎲 生成新的密钥对...",
181            enter_private_key: "请输入私钥 (base58 格式): ",
182            private_key_empty: "私钥不能为空",
183            keypair_ready: "✅ 密钥对准备完成",
184            keystore_recommended: "  1. 保存为 Keystore 文件 (推荐)",
185            show_encrypted_string: "  2. 显示加密字符串",
186            keystore_created: "  ✅ Keystore 创建成功!",
187            private_key_encrypted: "🔒 私钥已加密保存",
188            important_note: "⚠️  重要提示:",
189            keep_safe: "  • 请妥善保管 Keystore 文件和密码",
190            lost_password_warning: "  • 丢失密码将无法恢复钱包",
191            backup_recommended: "  • 建议备份到安全位置",
192            encrypted_private_key: "加密后的私钥:",
193            keep_safe_both: "⚠️  提示: 请妥善保管加密私钥和密码",
194
195            decrypt_title: "  解密私钥",
196            input_method: "输入方式:",
197            from_keystore: "  1. 从 Keystore 文件读取",
198            from_encrypted_string: "  2. 输入加密字符串",
199            encrypted_key: "加密的私钥: ",
200            enter_password: "请输入密码: ",
201            decrypt_success: "  ✅ 解密成功!",
202            file_not_exist: "文件不存在: {}",
203            dont_share_warning: "  • 请勿分享私钥给任何人",
204            delete_plaintext: "  • 使用完毕后请立即删除明文私钥文件",
205            use_encryption: "  • 建议使用加密方式保存",
206
207            set_password: "设置加密密码 (至少 10 个字符):",
208            new_password: "新密码: ",
209            confirm_password: "确认密码: ",
210            password_empty: "密码不能为空",
211            password_min_length: "密码长度至少 10 个字符",
212            password_mismatch: "两次密码不一致",
213            password_set: "✅ 密码设置成功",
214
215            invalid_choice: "无效选项",
216            write_failed: "写入文件失败: {}",
217        }
218    }
219
220    fn english() -> Self {
221        Self {
222            title: "  FnzSafe - Solana Key Management Tool",
223            core_functions: "Core Functions (3 operations):",
224            create_plain: "  {}  Create Plain Private Key",
225            create_encrypted: "  {}  Create Encrypted Private Key (Bot)",
226            decrypt: "  {}  Decrypt Private Key",
227            exit: "  {}  Exit",
228            select_option: "Select option [0-14]: ",
229            goodbye: "👋 Goodbye!",
230            invalid_option: "❌ Invalid option, please try again",
231            continue_use: "Continue? [Y/n]: ",
232
233            create_plain_title: "  Create Plain Private Key",
234            keypair_generated: "✅ Keypair generated successfully!",
235            public_key: "Public Key:",
236            private_key: "Private Key:",
237            output_method: "Output Method:",
238            display_only: "  1. Display Only (already shown)",
239            save_to_file: "  2. Save to File",
240            select: "Select [1/2]: ",
241            file_path: "File path (default: keypair.json): ",
242            file_saved: "✅ Saved to file",
243            security_warning: "⚠️  Security Warning:",
244            plaintext_warning: "  • Plaintext private key is very insecure",
245            save_securely: "  • Save to a secure location immediately",
246            dont_share: "  • Never share with anyone",
247            recommend_encrypted: "  • Consider using 'Create Encrypted Private Key'",
248
249            create_encrypted_title: "  Create Encrypted Private Key",
250            choose_method: "Choose Method:",
251            generate_new: "  1. Generate new keypair and encrypt",
252            import_existing: "  2. Import existing private key and encrypt",
253            generating: "🎲 Generating new keypair...",
254            enter_private_key: "Enter private key (base58 format): ",
255            private_key_empty: "Private key cannot be empty",
256            keypair_ready: "✅ Keypair ready",
257            keystore_recommended: "  1. Save as Keystore file (Recommended)",
258            show_encrypted_string: "  2. Show encrypted string",
259            keystore_created: "  ✅ Keystore created successfully!",
260            private_key_encrypted: "🔒 Private key encrypted and saved",
261            important_note: "⚠️  Important:",
262            keep_safe: "  • Keep Keystore file and password safe",
263            lost_password_warning: "  • Lost password = lost wallet",
264            backup_recommended: "  • Backup to a secure location",
265            encrypted_private_key: "Encrypted Private Key:",
266            keep_safe_both: "⚠️  Note: Keep encrypted key and password safe",
267
268            decrypt_title: "  Decrypt Private Key",
269            input_method: "Input Method:",
270            from_keystore: "  1. From Keystore file",
271            from_encrypted_string: "  2. Enter encrypted string",
272            encrypted_key: "Encrypted key: ",
273            enter_password: "Enter password: ",
274            decrypt_success: "  ✅ Decryption successful!",
275            file_not_exist: "File not found: {}",
276            dont_share_warning: "  • Never share private key with anyone",
277            delete_plaintext: "  • Delete plaintext key file after use",
278            use_encryption: "  • Consider using encryption for storage",
279
280            set_password: "Set encryption password (minimum 10 characters):",
281            new_password: "New password: ",
282            confirm_password: "Confirm password: ",
283            password_empty: "Password cannot be empty",
284            password_min_length: "Password must be at least 10 characters",
285            password_mismatch: "Passwords do not match",
286            password_set: "✅ Password set successfully",
287
288            invalid_choice: "Invalid choice",
289            write_failed: "Write failed: {}",
290        }
291    }
292}
293
294/// 选择语言
295fn select_language() -> Result<Language, String> {
296    println!("\n{}", "=".repeat(50).cyan());
297    println!("{}", "  Language / 语言选择".cyan().bold());
298    println!("{}", "=".repeat(50).cyan());
299    println!();
300    println!("  {}  English", "1.".green().bold());
301    println!("  {}  中文", "2.".green().bold());
302    println!();
303    print!("Select / 选择 [1/2]: ");
304    io::stdout().flush().map_err(|e| e.to_string())?;
305
306    let mut choice = String::new();
307    io::stdin()
308        .read_line(&mut choice)
309        .map_err(|e| e.to_string())?;
310    let choice = choice.trim();
311
312    match choice {
313        "1" => Ok(Language::English),
314        "2" => Ok(Language::Chinese),
315        _ => {
316            println!("\n{}", "❌ Invalid option / 无效选项".red());
317            select_language()
318        }
319    }
320}
321
322/// Session state to hold unlocked keypair
323struct SessionState {
324    keypair: Option<Keypair>,
325    keystore_path: Option<String>,
326}
327
328impl SessionState {
329    fn new() -> Self {
330        Self {
331            keypair: None,
332            keystore_path: None,
333        }
334    }
335
336    fn is_unlocked(&self) -> bool {
337        self.keypair.is_some()
338    }
339
340    fn unlock(&mut self, keypair: Keypair, path: String) {
341        self.keypair = Some(keypair);
342        self.keystore_path = Some(path);
343    }
344
345    fn get_keypair(&self) -> Option<&Keypair> {
346        self.keypair.as_ref()
347    }
348
349    fn lock(&mut self) {
350        self.keypair = None;
351        self.keystore_path = None;
352    }
353}
354
355/// 显示主菜单并处理用户选择
356pub fn show_main_menu() -> Result<(), String> {
357    // 首先选择语言
358    let lang = select_language()?;
359    let texts = match lang {
360        Language::Chinese => Texts::chinese(),
361        Language::English => Texts::english(),
362    };
363
364    // Create session state to hold unlocked keypair
365    let mut session = SessionState::new();
366
367    loop {
368        println!("\n{}", "=".repeat(50).cyan());
369        println!("{}", texts.title.cyan().bold());
370        println!("{}", "=".repeat(50).cyan());
371        println!();
372        println!("{}", texts.core_functions);
373        println!();
374        println!("  {}  {}", "1.".green().bold(), &texts.create_plain[6..]);
375        println!(
376            "  {}  {}",
377            "2.".green().bold(),
378            &texts.create_encrypted[6..]
379        );
380        println!("  {}  {}", "3.".green().bold(), &texts.decrypt[6..]);
381
382        // Show unlock/lock status
383        println!();
384        if session.is_unlocked() {
385            if lang == Language::Chinese {
386                println!(
387                    "  🔓 {} {}",
388                    "钱包已解锁:".green().bold(),
389                    session
390                        .get_keypair()
391                        .unwrap()
392                        .pubkey()
393                        .to_string()
394                        .bright_white()
395                );
396                println!("  {}  {}", "L.".yellow().bold(), "锁定钱包".yellow());
397            } else {
398                println!(
399                    "  🔓 {} {}",
400                    "Wallet Unlocked:".green().bold(),
401                    session
402                        .get_keypair()
403                        .unwrap()
404                        .pubkey()
405                        .to_string()
406                        .bright_white()
407                );
408                println!("  {}  {}", "L.".yellow().bold(), "Lock Wallet".yellow());
409            }
410        } else if lang == Language::Chinese {
411            println!("  🔒 {} {}", "钱包状态:".red(), "未解锁".red());
412            println!(
413                "  {}  {}",
414                "U.".green().bold(),
415                "解锁钱包(用于Solana操作)".green()
416            );
417        } else {
418            println!("  🔒 {} {}", "Wallet Status:".red(), "Locked".red());
419            println!(
420                "  {}  {}",
421                "U.".green().bold(),
422                "Unlock Wallet (for Solana Operations)".green()
423            );
424        }
425
426        // Advanced security features
427        #[cfg(feature = "2fa")]
428        {
429            println!();
430            if lang == Language::Chinese {
431                println!("{}", "  高级安全功能:".bright_magenta().bold());
432            } else {
433                println!("{}", "  Advanced Security:".bright_magenta().bold());
434            }
435            println!(
436                "  {}  {}",
437                "4.".bright_magenta().bold(),
438                if lang == Language::Chinese {
439                    "设置 2FA 认证"
440                } else {
441                    "Setup 2FA Authentication"
442                }
443            );
444            println!(
445                "  {}  {}",
446                "5.".bright_magenta().bold(),
447                if lang == Language::Chinese {
448                    "生成三因子钱包"
449                } else {
450                    "Generate Triple-Factor Wallet"
451                }
452            );
453            println!(
454                "  {}  {}",
455                "6.".bright_magenta().bold(),
456                if lang == Language::Chinese {
457                    "解锁三因子钱包"
458                } else {
459                    "Unlock Triple-Factor Wallet"
460                }
461            );
462        }
463
464        // Solana operations (if feature is enabled)
465        #[cfg(feature = "solana-ops")]
466        {
467            println!();
468            if lang == Language::Chinese {
469                println!("{}", "  Solana 链上操作:".bright_blue().bold());
470            } else {
471                println!("{}", "  Solana Operations:".bright_blue().bold());
472            }
473            #[cfg(feature = "2fa")]
474            {
475                println!(
476                    "  {}  {}",
477                    "7.".bright_cyan().bold(),
478                    if lang == Language::Chinese {
479                        "查询 SOL 余额"
480                    } else {
481                        "Check SOL Balance"
482                    }
483                );
484                println!(
485                    "  {}  {}",
486                    "8.".bright_cyan().bold(),
487                    if lang == Language::Chinese {
488                        "转账 SOL"
489                    } else {
490                        "Transfer SOL"
491                    }
492                );
493                println!(
494                    "  {}  {}",
495                    "9.".bright_cyan().bold(),
496                    if lang == Language::Chinese {
497                        "创建 WSOL ATA"
498                    } else {
499                        "Create WSOL ATA"
500                    }
501                );
502                println!(
503                    "  {}  {}",
504                    "10.".bright_cyan().bold(),
505                    if lang == Language::Chinese {
506                        "包装 SOL → WSOL"
507                    } else {
508                        "Wrap SOL → WSOL"
509                    }
510                );
511                println!(
512                    "  {}  {}",
513                    "11.".bright_cyan().bold(),
514                    if lang == Language::Chinese {
515                        "解包 WSOL → SOL"
516                    } else {
517                        "Unwrap WSOL → SOL"
518                    }
519                );
520                println!(
521                    "  {}  {}",
522                    "12.".bright_cyan().bold(),
523                    if lang == Language::Chinese {
524                        "关闭 WSOL ATA"
525                    } else {
526                        "Close WSOL ATA"
527                    }
528                );
529                println!(
530                    "  {}  {}",
531                    "13.".bright_cyan().bold(),
532                    if lang == Language::Chinese {
533                        "转账 SPL 代币"
534                    } else {
535                        "Transfer SPL Token"
536                    }
537                );
538                println!(
539                    "  {}  {}",
540                    "14.".bright_cyan().bold(),
541                    if lang == Language::Chinese {
542                        "创建 Nonce 账户"
543                    } else {
544                        "Create Nonce Account"
545                    }
546                );
547
548                #[cfg(feature = "sol-trade-sdk")]
549                println!(
550                    "  {}  {}",
551                    "15.".bright_magenta().bold(),
552                    if lang == Language::Chinese {
553                        "Pump.fun 卖出代币"
554                    } else {
555                        "Pump.fun Sell Tokens"
556                    }
557                );
558                #[cfg(feature = "sol-trade-sdk")]
559                println!(
560                    "  {}  {}",
561                    "16.".bright_magenta().bold(),
562                    if lang == Language::Chinese {
563                        "PumpSwap 卖出代币"
564                    } else {
565                        "PumpSwap Sell Tokens"
566                    }
567                );
568                #[cfg(feature = "sol-trade-sdk")]
569                println!(
570                    "  {}  {}",
571                    "17.".bright_magenta().bold(),
572                    if lang == Language::Chinese {
573                        "Pump.fun 返现(查看与领取)"
574                    } else {
575                        "Pump.fun Cashback (View & Claim)"
576                    }
577                );
578                #[cfg(feature = "sol-trade-sdk")]
579                println!(
580                    "  {}  {}",
581                    "18.".bright_magenta().bold(),
582                    if lang == Language::Chinese {
583                        "PumpSwap 返现(查看与领取)"
584                    } else {
585                        "PumpSwap Cashback (View & Claim)"
586                    }
587                );
588            }
589            #[cfg(not(feature = "2fa"))]
590            {
591                println!(
592                    "  {}  {}",
593                    "4.".bright_cyan().bold(),
594                    if lang == Language::Chinese {
595                        "查询 SOL 余额"
596                    } else {
597                        "Check SOL Balance"
598                    }
599                );
600                println!(
601                    "  {}  {}",
602                    "5.".bright_cyan().bold(),
603                    if lang == Language::Chinese {
604                        "转账 SOL"
605                    } else {
606                        "Transfer SOL"
607                    }
608                );
609                println!(
610                    "  {}  {}",
611                    "6.".bright_cyan().bold(),
612                    if lang == Language::Chinese {
613                        "创建 WSOL ATA"
614                    } else {
615                        "Create WSOL ATA"
616                    }
617                );
618                println!(
619                    "  {}  {}",
620                    "7.".bright_cyan().bold(),
621                    if lang == Language::Chinese {
622                        "包装 SOL → WSOL"
623                    } else {
624                        "Wrap SOL → WSOL"
625                    }
626                );
627                println!(
628                    "  {}  {}",
629                    "8.".bright_cyan().bold(),
630                    if lang == Language::Chinese {
631                        "解包 WSOL → SOL"
632                    } else {
633                        "Unwrap WSOL → SOL"
634                    }
635                );
636                println!(
637                    "  {}  {}",
638                    "9.".bright_cyan().bold(),
639                    if lang == Language::Chinese {
640                        "关闭 WSOL ATA"
641                    } else {
642                        "Close WSOL ATA"
643                    }
644                );
645                println!(
646                    "  {}  {}",
647                    "10.".bright_cyan().bold(),
648                    if lang == Language::Chinese {
649                        "转账 SPL 代币"
650                    } else {
651                        "Transfer SPL Token"
652                    }
653                );
654                println!(
655                    "  {}  {}",
656                    "11.".bright_cyan().bold(),
657                    if lang == Language::Chinese {
658                        "创建 Nonce 账户"
659                    } else {
660                        "Create Nonce Account"
661                    }
662                );
663
664                #[cfg(feature = "sol-trade-sdk")]
665                println!(
666                    "  {}  {}",
667                    "12.".bright_magenta().bold(),
668                    if lang == Language::Chinese {
669                        "Pump.fun 卖出代币"
670                    } else {
671                        "Pump.fun Sell Tokens"
672                    }
673                );
674                #[cfg(feature = "sol-trade-sdk")]
675                println!(
676                    "  {}  {}",
677                    "13.".bright_magenta().bold(),
678                    if lang == Language::Chinese {
679                        "PumpSwap 卖出代币"
680                    } else {
681                        "PumpSwap Sell Tokens"
682                    }
683                );
684                #[cfg(feature = "sol-trade-sdk")]
685                println!(
686                    "  {}  {}",
687                    "14.".bright_magenta().bold(),
688                    if lang == Language::Chinese {
689                        "Pump.fun 返现(查看与领取)"
690                    } else {
691                        "Pump.fun Cashback (View & Claim)"
692                    }
693                );
694                #[cfg(feature = "sol-trade-sdk")]
695                println!(
696                    "  {}  {}",
697                    "15.".bright_magenta().bold(),
698                    if lang == Language::Chinese {
699                        "PumpSwap 返现(查看与领取)"
700                    } else {
701                        "PumpSwap Cashback (View & Claim)"
702                    }
703                );
704            }
705        }
706
707        println!();
708        println!("  {}  {}", "0.".red().bold(), &texts.exit[6..]);
709        println!();
710        print!("{}", texts.select_option);
711        io::stdout().flush().map_err(|e| e.to_string())?;
712
713        let mut choice = String::new();
714        io::stdin()
715            .read_line(&mut choice)
716            .map_err(|e| e.to_string())?;
717        let choice = choice.trim();
718
719        match choice.to_lowercase().as_str() {
720            "1" => create_plain_key_interactive(&texts)?,
721            "2" => create_encrypted_key_interactive(&texts)?,
722            "3" => decrypt_key_interactive(&texts)?,
723
724            // Unlock/Lock wallet
725            "u" => {
726                if session.is_unlocked() {
727                    if lang == Language::Chinese {
728                        println!("\n✅ 钱包已经解锁!");
729                    } else {
730                        println!("\n✅ Wallet already unlocked!");
731                    }
732                } else if let Err(e) = unlock_wallet_interactive(&mut session, lang) {
733                    eprintln!("❌ {}", e);
734                }
735                // 解锁/锁定后直接回到菜单,不再问「是否继续使用」
736                continue;
737            }
738            "l" => {
739                if session.is_unlocked() {
740                    session.lock();
741                    if lang == Language::Chinese {
742                        println!("\n🔒 钱包已锁定");
743                    } else {
744                        println!("\n🔒 Wallet locked");
745                    }
746                } else if lang == Language::Chinese {
747                    println!("\n⚠️ 钱包未解锁");
748                } else {
749                    println!("\n⚠️ Wallet not unlocked");
750                }
751                continue;
752            }
753
754            // Advanced security features (2FA)
755            #[cfg(feature = "2fa")]
756            "4" => {
757                if let Err(e) = setup_2fa_interactive(lang) {
758                    eprintln!("❌ {}", e);
759                }
760            }
761            #[cfg(feature = "2fa")]
762            "5" => {
763                if let Err(e) = generate_triple_factor_wallet_interactive(lang) {
764                    eprintln!("❌ {}", e);
765                }
766            }
767            #[cfg(feature = "2fa")]
768            "6" => {
769                if let Err(e) = unlock_triple_factor_wallet_interactive(lang) {
770                    eprintln!("❌ {}", e);
771                }
772            }
773
774            // Solana operations
775            #[cfg(all(feature = "solana-ops", feature = "2fa"))]
776            "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" => {
777                if let Err(e) = handle_solana_operation(choice, lang, &mut session) {
778                    eprintln!("❌ {}", e);
779                }
780            }
781            #[cfg(all(feature = "solana-ops", not(feature = "2fa")))]
782            "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" => {
783                if let Err(e) = handle_solana_operation(choice, lang, &mut session) {
784                    eprintln!("❌ {}", e);
785                }
786            }
787
788            "0" => {
789                println!("\n{}", texts.goodbye.cyan());
790                break;
791            }
792            _ => {
793                println!("\n{}", texts.invalid_option.red());
794                continue;
795            }
796        }
797
798        // 询问是否继续
799        println!();
800        print!("{}", texts.continue_use);
801        io::stdout().flush().map_err(|e| e.to_string())?;
802
803        let mut continue_choice = String::new();
804        io::stdin()
805            .read_line(&mut continue_choice)
806            .map_err(|e| e.to_string())?;
807        let continue_choice = continue_choice.trim().to_lowercase();
808
809        if continue_choice == "n" || continue_choice == "no" {
810            println!("\n{}", texts.goodbye.cyan());
811            break;
812        }
813    }
814
815    Ok(())
816}
817
818/// 功能1: 创建明文私钥
819fn create_plain_key_interactive(texts: &Texts) -> Result<(), String> {
820    println!("\n{}", "=".repeat(50).yellow());
821    println!("{}", texts.create_plain_title.yellow().bold());
822    println!("{}", "=".repeat(50).yellow());
823    println!();
824
825    // 生成密钥对
826    let keypair = KeyManager::generate_keypair();
827    let pubkey = keypair.pubkey();
828    let private_key = keypair.to_base58_string();
829
830    println!("{}", texts.keypair_generated.green().bold());
831    println!();
832    println!(
833        "{} {}",
834        texts.public_key.cyan(),
835        pubkey.to_string().white().bold()
836    );
837    println!("{} {}", texts.private_key.red().bold(), private_key);
838    println!();
839
840    // 询问输出方式
841    println!("{}", texts.output_method);
842    println!("{}", texts.display_only);
843    println!("{}", texts.save_to_file);
844    println!();
845    print!("{}", texts.select);
846    io::stdout().flush().map_err(|e| e.to_string())?;
847
848    let mut output_choice = String::new();
849    io::stdin()
850        .read_line(&mut output_choice)
851        .map_err(|e| e.to_string())?;
852    let output_choice = output_choice.trim();
853
854    if output_choice == "2" {
855        print!("{}", texts.file_path);
856        io::stdout().flush().map_err(|e| e.to_string())?;
857
858        let mut file_path = String::new();
859        io::stdin()
860            .read_line(&mut file_path)
861            .map_err(|e| e.to_string())?;
862        let file_path = file_path.trim();
863        let file_path = if file_path.is_empty() {
864            "keypair.json"
865        } else {
866            file_path
867        };
868
869        // 保存为 Solana keypair JSON 格式 (数组格式)
870        let bytes = keypair.to_bytes();
871        let json = serde_json::to_string(&bytes.to_vec())
872            .map_err(|e| texts.write_failed.replace("{}", &e.to_string()))?;
873
874        std::fs::write(file_path, json)
875            .map_err(|e| texts.write_failed.replace("{}", &e.to_string()))?;
876
877        println!();
878        println!("{}", texts.file_saved.green());
879        println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
880    }
881
882    println!();
883    println!("{}", texts.security_warning.yellow().bold());
884    println!("{}", texts.plaintext_warning);
885    println!("{}", texts.save_securely);
886    println!("{}", texts.dont_share);
887    println!("{}", texts.recommend_encrypted);
888
889    Ok(())
890}
891
892/// 功能2: 创建加密私钥
893fn create_encrypted_key_interactive(texts: &Texts) -> Result<(), String> {
894    println!("\n{}", "=".repeat(50).yellow());
895    println!("{}", texts.create_encrypted_title.yellow().bold());
896    println!("{}", "=".repeat(50).yellow());
897    println!();
898
899    // 询问是生成新的还是导入现有私钥
900    println!("{}", texts.choose_method);
901    println!("{}", texts.generate_new);
902    println!("{}", texts.import_existing);
903    println!();
904    print!("{}", texts.select);
905    io::stdout().flush().map_err(|e| e.to_string())?;
906
907    let mut source_choice = String::new();
908    io::stdin()
909        .read_line(&mut source_choice)
910        .map_err(|e| e.to_string())?;
911    let source_choice = source_choice.trim();
912
913    let keypair = match source_choice {
914        "1" => {
915            // 生成新密钥对
916            println!();
917            println!("{}", texts.generating.cyan());
918            KeyManager::generate_keypair()
919        }
920        "2" => {
921            // 导入现有私钥
922            println!();
923            print!("{}", texts.enter_private_key);
924            io::stdout().flush().map_err(|e| e.to_string())?;
925
926            let mut private_key = String::new();
927            io::stdin()
928                .read_line(&mut private_key)
929                .map_err(|e| e.to_string())?;
930            let private_key = private_key.trim();
931
932            if private_key.is_empty() {
933                return Err(texts.private_key_empty.to_string());
934            }
935
936            Keypair::from_base58_string(private_key)
937        }
938        _ => {
939            return Err(texts.invalid_choice.to_string());
940        }
941    };
942
943    let pubkey = keypair.pubkey();
944
945    println!();
946    println!("{}", texts.keypair_ready.green());
947    println!("{} {}", texts.public_key.cyan(), pubkey);
948    println!();
949
950    // 获取密码
951    let password = read_password_confirmed(texts)?;
952
953    // 询问输出方式
954    println!();
955    println!("{}", texts.output_method);
956    println!("{}", texts.keystore_recommended);
957    println!("{}", texts.show_encrypted_string);
958    println!();
959    print!("{}", texts.select);
960    io::stdout().flush().map_err(|e| e.to_string())?;
961
962    let mut output_choice = String::new();
963    io::stdin()
964        .read_line(&mut output_choice)
965        .map_err(|e| e.to_string())?;
966    let output_choice = output_choice.trim();
967
968    match output_choice {
969        "1" => {
970            // 保存为文件
971            print!("{}", texts.file_path.replace("keypair", "wallet"));
972            io::stdout().flush().map_err(|e| e.to_string())?;
973
974            let mut file_path = String::new();
975            io::stdin()
976                .read_line(&mut file_path)
977                .map_err(|e| e.to_string())?;
978            let file_path = file_path.trim();
979            let file_path = if file_path.is_empty() {
980                "keystore.json"
981            } else {
982                file_path
983            };
984
985            let keystore_json = KeyManager::keypair_to_encrypted_json(&keypair, &password)?;
986            std::fs::write(file_path, keystore_json)
987                .map_err(|e| texts.write_failed.replace("{}", &e.to_string()))?;
988
989            println!();
990            println!("{}", "=".repeat(50).green());
991            println!("{}", texts.keystore_created.green().bold());
992            println!("{}", "=".repeat(50).green());
993            println!();
994            println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
995            println!("{} {}", texts.public_key.cyan(), pubkey);
996            println!("{}", texts.private_key_encrypted.green());
997            println!();
998            println!("{}", texts.important_note.yellow().bold());
999            println!("{}", texts.keep_safe);
1000            println!("{}", texts.lost_password_warning);
1001            println!("{}", texts.backup_recommended);
1002        }
1003        "2" => {
1004            // 显示加密字符串
1005            let private_key = keypair.to_base58_string();
1006            let encrypted = KeyManager::encrypt_with_password(&private_key, &password)?;
1007
1008            println!();
1009            println!("{}", texts.keypair_ready.green().bold());
1010            println!();
1011            println!("{} {}", texts.public_key.cyan(), pubkey);
1012            println!("{}", texts.encrypted_private_key.cyan());
1013            println!("{}", encrypted);
1014            println!();
1015            println!("{}", texts.keep_safe_both.yellow());
1016        }
1017        _ => {
1018            return Err(texts.invalid_choice.to_string());
1019        }
1020    }
1021
1022    Ok(())
1023}
1024
1025/// 功能3: 解密私钥
1026fn decrypt_key_interactive(texts: &Texts) -> Result<(), String> {
1027    println!("\n{}", "=".repeat(50).yellow());
1028    println!("{}", texts.decrypt_title.yellow().bold());
1029    println!("{}", "=".repeat(50).yellow());
1030    println!();
1031
1032    // 选择输入方式
1033    println!("{}", texts.input_method);
1034    println!("{}", texts.from_keystore);
1035    println!("{}", texts.from_encrypted_string);
1036    println!();
1037    print!("{}", texts.select);
1038    io::stdout().flush().map_err(|e| e.to_string())?;
1039
1040    let mut input_choice = String::new();
1041    io::stdin()
1042        .read_line(&mut input_choice)
1043        .map_err(|e| e.to_string())?;
1044    let input_choice = input_choice.trim();
1045
1046    let (private_key, pubkey) = match input_choice {
1047        "1" => {
1048            // 从文件读取
1049            print!(
1050                "{}",
1051                texts
1052                    .file_path
1053                    .trim_end_matches("(默认: keypair.json): ")
1054                    .trim_end_matches("(default: keypair.json): ")
1055            );
1056            io::stdout().flush().map_err(|e| e.to_string())?;
1057
1058            let mut file_path = String::new();
1059            io::stdin()
1060                .read_line(&mut file_path)
1061                .map_err(|e| e.to_string())?;
1062            let file_path = file_path.trim();
1063
1064            if !std::path::Path::new(file_path).exists() {
1065                return Err(texts.file_not_exist.replace("{}", file_path));
1066            }
1067
1068            println!();
1069            let password = prompt_password(texts.enter_password, texts)?;
1070
1071            let keystore_json = std::fs::read_to_string(file_path)
1072                .map_err(|e| texts.write_failed.replace("{}", &e.to_string()))?;
1073
1074            let keypair = KeyManager::keypair_from_encrypted_json(&keystore_json, &password)?;
1075            let pubkey = keypair.pubkey();
1076            let private_key = keypair.to_base58_string();
1077
1078            (private_key, pubkey)
1079        }
1080        "2" => {
1081            // 输入加密字符串
1082            print!("{}", texts.encrypted_key);
1083            io::stdout().flush().map_err(|e| e.to_string())?;
1084
1085            let mut encrypted = String::new();
1086            io::stdin()
1087                .read_line(&mut encrypted)
1088                .map_err(|e| e.to_string())?;
1089            let encrypted = encrypted.trim();
1090
1091            println!();
1092            let password = prompt_password(texts.enter_password, texts)?;
1093
1094            let private_key = KeyManager::decrypt_with_password(encrypted, &password)?;
1095            let keypair = Keypair::from_base58_string(&private_key);
1096            let pubkey = keypair.pubkey();
1097
1098            (private_key, pubkey)
1099        }
1100        _ => {
1101            return Err(texts.invalid_choice.to_string());
1102        }
1103    };
1104
1105    println!();
1106    println!("{}", "=".repeat(50).green());
1107    println!("{}", texts.decrypt_success.green().bold());
1108    println!("{}", "=".repeat(50).green());
1109    println!();
1110    println!("{} {}", texts.public_key.cyan(), pubkey);
1111    println!("{} {}", texts.private_key.red().bold(), private_key);
1112    println!();
1113
1114    // 询问输出方式
1115    println!("{}", texts.output_method);
1116    println!("{}", texts.display_only);
1117    println!("{}", texts.save_to_file);
1118    println!();
1119    print!("{}", texts.select);
1120    io::stdout().flush().map_err(|e| e.to_string())?;
1121
1122    let mut output_choice = String::new();
1123    io::stdin()
1124        .read_line(&mut output_choice)
1125        .map_err(|e| e.to_string())?;
1126    let output_choice = output_choice.trim();
1127
1128    if output_choice == "2" {
1129        let default_filename = "decrypted_key.txt";
1130
1131        print!(
1132            "{}",
1133            texts.file_path.replace("keypair.json", default_filename)
1134        );
1135        io::stdout().flush().map_err(|e| e.to_string())?;
1136
1137        let mut file_path = String::new();
1138        io::stdin()
1139            .read_line(&mut file_path)
1140            .map_err(|e| e.to_string())?;
1141        let file_path = file_path.trim();
1142        let file_path = if file_path.is_empty() {
1143            default_filename
1144        } else {
1145            file_path
1146        };
1147
1148        let content = format!(
1149            "{} {}\n{} {}\n",
1150            texts.public_key,
1151            pubkey,
1152            texts.private_key.trim_end_matches(':'),
1153            private_key
1154        );
1155        std::fs::write(file_path, content)
1156            .map_err(|e| texts.write_failed.replace("{}", &e.to_string()))?;
1157
1158        println!();
1159        println!("{}", texts.file_saved.green());
1160        println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
1161    }
1162
1163    println!();
1164    println!("{}", texts.security_warning.yellow().bold());
1165    println!("{}", texts.dont_share_warning);
1166    println!("{}", texts.delete_plaintext);
1167    println!("{}", texts.use_encryption);
1168
1169    Ok(())
1170}
1171
1172/// 在 **stdout** 打印提示,在 TTY 上无回显读取密码(避免 `prompt_password` 仅写 TTY 导致 IDE 终端里看不到提示)。
1173fn prompt_password(prompt: &str, texts: &Texts) -> Result<String, String> {
1174    print!("{}", prompt);
1175    io::stdout().flush().map_err(|e| e.to_string())?;
1176
1177    let password =
1178        rpassword::read_password().map_err(|e| texts.write_failed.replace("{}", &e.to_string()))?;
1179
1180    Ok(password.trim().to_string())
1181}
1182
1183/// Unlock wallet interactively and store in session
1184fn unlock_wallet_interactive(session: &mut SessionState, language: Language) -> Result<(), String> {
1185    println!();
1186    if language == Language::Chinese {
1187        println!("{}", "  解锁钱包".cyan().bold());
1188        print!("Keystore 文件路径 [keystore.json]: ");
1189    } else {
1190        println!("{}", "  Unlock Wallet".cyan().bold());
1191        print!("Keystore file path [keystore.json]: ");
1192    }
1193    io::stdout().flush().map_err(|e| e.to_string())?;
1194
1195    let mut keystore_path = String::new();
1196    io::stdin()
1197        .read_line(&mut keystore_path)
1198        .map_err(|e| e.to_string())?;
1199    let keystore_path = keystore_path.trim();
1200
1201    // Only search subdirectories when user pressed Enter without typing a path
1202    let keystore_path = if keystore_path.is_empty() {
1203        if let Some(found) = search_keystore_in_subdirs("keystore.json") {
1204            if language == Language::Chinese {
1205                println!("🔍 在子目录中找到: {}", found);
1206            } else {
1207                println!("🔍 Found in subdirectory: {}", found);
1208            }
1209            found
1210        } else {
1211            "keystore.json".to_string()
1212        }
1213    } else {
1214        keystore_path.to_string()
1215    };
1216
1217    // Read encrypted file
1218    let file_content = std::fs::read_to_string(&keystore_path).map_err(|e| {
1219        if language == Language::Chinese {
1220            format!(
1221                "无法读取 Keystore 文件「{}」: {}(请确认路径正确、当前工作目录是否为文件所在目录)",
1222                keystore_path, e
1223            )
1224        } else {
1225            format!("Failed to read keystore '{}': {}", keystore_path, e)
1226        }
1227    })?;
1228
1229    // Parse JSON to get encryption type
1230    let json: serde_json::Value = serde_json::from_str(&file_content)
1231        .map_err(|e| format!("Failed to parse keystore: {}", e))?;
1232
1233    let encryption_type = json["encryption_type"].as_str().unwrap_or("password_only");
1234
1235    // Decrypt keypair: use bot_helper::unlock_wallet for password_only to guarantee
1236    // the exact same code path as bot startup (config/mod.rs → ensure_wallet_ready).
1237    let keypair = match encryption_type {
1238        "password_only" => crate::bot_helper::unlock_wallet(&keystore_path)
1239            .map_err(|e| format!("Failed to decrypt keystore: {}", e))?,
1240        "triple_factor_v1" => {
1241            return Err(
1242                "Triple-factor wallets not yet supported in interactive mode. Please use the CLI."
1243                    .to_string(),
1244            );
1245        }
1246        _ => {
1247            return Err(format!("Unknown encryption type: {}", encryption_type));
1248        }
1249    };
1250
1251    // Store in session
1252    session.unlock(keypair, keystore_path.to_string());
1253
1254    if language == Language::Chinese {
1255        println!("✅ 钱包解锁成功!");
1256        println!("📍 钱包地址: {}", session.get_keypair().unwrap().pubkey());
1257        println!("💡 提示: 在本次会话中,Solana操作将使用此钱包,无需重复输入密码");
1258    } else {
1259        println!("✅ Wallet unlocked successfully!");
1260        println!(
1261            "📍 Wallet address: {}",
1262            session.get_keypair().unwrap().pubkey()
1263        );
1264        println!("💡 Tip: Solana operations in this session will use this wallet without re-entering password");
1265    }
1266
1267    Ok(())
1268}
1269
1270/// Handle Solana operation using session keypair
1271#[cfg(feature = "solana-ops")]
1272fn handle_solana_operation(
1273    choice: &str,
1274    language: Language,
1275    session: &mut SessionState,
1276) -> Result<(), String> {
1277    // Convert Language to operations::Language
1278    let ops_language = match language {
1279        Language::English => crate::operations::Language::English,
1280        Language::Chinese => crate::operations::Language::Chinese,
1281    };
1282
1283    // Check if wallet is unlocked
1284    let keypair = if let Some(kp) = session.get_keypair() {
1285        kp
1286    } else {
1287        // Wallet not unlocked, prompt user to unlock first
1288        if language == Language::Chinese {
1289            println!("\n⚠️  请先使用 'U' 选项解锁钱包");
1290        } else {
1291            println!("\n⚠️  Please unlock wallet first using 'U' option");
1292        }
1293        return Ok(());
1294    };
1295
1296    if language == Language::Chinese {
1297        println!("\n📍 使用钱包: {}", keypair.pubkey());
1298    } else {
1299        println!("\n📍 Using wallet: {}", keypair.pubkey());
1300    }
1301
1302    // Call the appropriate operation
1303    #[cfg(feature = "2fa")]
1304    let result = match choice {
1305        "7" => crate::operations::check_balance(keypair, ops_language),
1306        "8" => crate::operations::transfer_sol(keypair, ops_language),
1307        "9" => crate::operations::create_wsol_ata(keypair, ops_language),
1308        "10" => crate::operations::wrap_sol(keypair, ops_language),
1309        "11" => crate::operations::unwrap_sol(keypair, ops_language),
1310        "12" => crate::operations::close_wsol_ata(keypair, ops_language),
1311        "13" => crate::operations::transfer_token(keypair, ops_language),
1312        "14" => crate::operations::create_nonce_account(keypair, ops_language),
1313        #[cfg(feature = "sol-trade-sdk")]
1314        "15" => crate::operations::pumpfun_sell_interactive(keypair, ops_language),
1315        #[cfg(feature = "sol-trade-sdk")]
1316        "16" => crate::operations::pumpswap_sell_interactive(keypair, ops_language),
1317        #[cfg(feature = "sol-trade-sdk")]
1318        "17" => crate::operations::pumpfun_cashback_interactive(keypair, ops_language),
1319        #[cfg(feature = "sol-trade-sdk")]
1320        "18" => crate::operations::pumpswap_cashback_interactive(keypair, ops_language),
1321        _ => Err("Invalid operation".to_string()),
1322    };
1323
1324    #[cfg(not(feature = "2fa"))]
1325    let result = match choice {
1326        "4" => crate::operations::check_balance(keypair, ops_language),
1327        "5" => crate::operations::transfer_sol(keypair, ops_language),
1328        "6" => crate::operations::create_wsol_ata(keypair, ops_language),
1329        "7" => crate::operations::wrap_sol(keypair, ops_language),
1330        "8" => crate::operations::unwrap_sol(keypair, ops_language),
1331        "9" => crate::operations::close_wsol_ata(keypair, ops_language),
1332        "10" => crate::operations::transfer_token(keypair, ops_language),
1333        "11" => crate::operations::create_nonce_account(keypair, ops_language),
1334        #[cfg(feature = "sol-trade-sdk")]
1335        "12" => crate::operations::pumpfun_sell_interactive(keypair, ops_language),
1336        #[cfg(feature = "sol-trade-sdk")]
1337        "13" => crate::operations::pumpswap_sell_interactive(keypair, ops_language),
1338        #[cfg(feature = "sol-trade-sdk")]
1339        "14" => crate::operations::pumpfun_cashback_interactive(keypair, ops_language),
1340        #[cfg(feature = "sol-trade-sdk")]
1341        "15" => crate::operations::pumpswap_cashback_interactive(keypair, ops_language),
1342        _ => Err("Invalid operation".to_string()),
1343    };
1344
1345    result
1346}
1347
1348/// Setup 2FA authentication interactively
1349#[cfg(feature = "2fa")]
1350fn setup_2fa_interactive(language: Language) -> Result<(), String> {
1351    use crate::{
1352        derive_totp_secret_from_hardware_and_password, hardware_fingerprint::HardwareFingerprint,
1353        security_question::SecurityQuestion, totp::*,
1354    };
1355    use rpassword;
1356
1357    let account = "wallet";
1358    let issuer = "FnzSafe";
1359
1360    println!("\n{}", "=".repeat(50).bright_magenta());
1361    if language == Language::Chinese {
1362        println!("{}", "  🔐 三因子 2FA 安全设置".bright_magenta().bold());
1363    } else {
1364        println!(
1365            "{}",
1366            "  🔐 Triple-Factor 2FA Security Setup"
1367                .bright_magenta()
1368                .bold()
1369        );
1370    }
1371    println!("{}", "=".repeat(50).bright_magenta());
1372    println!();
1373
1374    if language == Language::Chinese {
1375        println!("{}", "⚠️  安全架构说明:".yellow().bold());
1376        println!("  • 因子1: 硬件指纹(自动收集,绑定设备)");
1377        println!("  • 因子2: 主密码(您设置的强密码)");
1378        println!("  • 因子3: 安全问题答案(防止密码泄露)");
1379        println!("  • 2FA密钥: 从硬件指纹+主密码派生(确定性)");
1380        println!("  • 解锁需要: 主密码 + 安全问题答案 + 2FA动态验证码");
1381    } else {
1382        println!("{}", "⚠️  Security Architecture:".yellow().bold());
1383        println!("  • Factor 1: Hardware Fingerprint (auto-collected, device-bound)");
1384        println!("  • Factor 2: Master Password (your strong password)");
1385        println!("  • Factor 3: Security Question Answer (prevents password leak)");
1386        println!("  • 2FA Key: Derived from hardware fingerprint + master password");
1387        println!("  • Unlock requires: Master password + Security answer + 2FA code");
1388    }
1389    println!();
1390
1391    // Step 1: Collect hardware fingerprint
1392    if language == Language::Chinese {
1393        println!("{}", "步骤 1/4: 收集硬件指纹...".bright_blue());
1394    } else {
1395        println!(
1396            "{}",
1397            "Step 1/4: Collecting hardware fingerprint...".bright_blue()
1398        );
1399    }
1400
1401    let hardware_fp = HardwareFingerprint::collect()
1402        .map_err(|e| format!("Failed to collect hardware fingerprint: {}", e))?;
1403
1404    if language == Language::Chinese {
1405        println!("{} 硬件指纹已收集(SHA256哈希)", "✅".green());
1406        println!("   指纹预览: {}...", &hardware_fp.as_str()[..16]);
1407    } else {
1408        println!(
1409            "{} Hardware fingerprint collected (SHA256 hash)",
1410            "✅".green()
1411        );
1412        println!("   Preview: {}...", &hardware_fp.as_str()[..16]);
1413    }
1414    println!();
1415
1416    // Step 2: Set master password
1417    if language == Language::Chinese {
1418        println!("{}", "步骤 2/4: 设置主密码".bright_blue());
1419    } else {
1420        println!("{}", "Step 2/4: Set master password".bright_blue());
1421    }
1422
1423    let master_password = loop {
1424        let password = rpassword::prompt_password(if language == Language::Chinese {
1425            "请输入主密码: "
1426        } else {
1427            "Enter master password: "
1428        })
1429        .map_err(|e| format!("Failed to read password: {}", e))?;
1430
1431        if password.is_empty() {
1432            println!(
1433                "{} {}",
1434                "❌".red(),
1435                if language == Language::Chinese {
1436                    "主密码不能为空"
1437                } else {
1438                    "Master password cannot be empty"
1439                }
1440            );
1441            continue;
1442        }
1443
1444        // Check password strength
1445        if password.len() < 10 {
1446            println!(
1447                "{} {}",
1448                "❌".red(),
1449                if language == Language::Chinese {
1450                    "密码长度至少10个字符"
1451                } else {
1452                    "Password must be at least 10 characters"
1453                }
1454            );
1455            continue;
1456        }
1457
1458        let password_confirm = rpassword::prompt_password(if language == Language::Chinese {
1459            "请再次输入主密码确认: "
1460        } else {
1461            "Confirm master password: "
1462        })
1463        .map_err(|e| format!("Failed to read password: {}", e))?;
1464
1465        if password != password_confirm {
1466            println!(
1467                "{} {}",
1468                "❌".red(),
1469                if language == Language::Chinese {
1470                    "两次输入的密码不一致"
1471                } else {
1472                    "Passwords do not match"
1473                }
1474            );
1475            continue;
1476        }
1477
1478        break password;
1479    };
1480
1481    if language == Language::Chinese {
1482        println!("{} 主密码设置成功", "✅".green());
1483    } else {
1484        println!("{} Master password set successfully", "✅".green());
1485    }
1486    println!();
1487
1488    // Step 3: Set security question
1489    if language == Language::Chinese {
1490        println!("{}", "步骤 3/4: 设置安全问题".bright_blue());
1491    } else {
1492        println!("{}", "Step 3/4: Set security question".bright_blue());
1493    }
1494
1495    let (question_index, _security_answer) = SecurityQuestion::setup_interactive()
1496        .map_err(|e| format!("Failed to setup security question: {}", e))?;
1497    println!();
1498
1499    // Step 4: Setup 2FA
1500    if language == Language::Chinese {
1501        println!("{}", "步骤 4/4: 设置 2FA 动态验证码".bright_blue());
1502    } else {
1503        println!("{}", "Step 4/4: Setup 2FA TOTP".bright_blue());
1504    }
1505
1506    let twofa_secret = derive_totp_secret_from_hardware_and_password(
1507        hardware_fp.as_str(),
1508        &master_password,
1509        account,
1510        issuer,
1511    )
1512    .map_err(|e| format!("Failed to derive 2FA secret: {}", e))?;
1513
1514    let config = TOTPConfig {
1515        secret: twofa_secret.clone(),
1516        account: account.to_string(),
1517        issuer: issuer.to_string(),
1518        algorithm: "SHA1".to_string(),
1519        digits: 6,
1520        step: 30,
1521    };
1522
1523    let totp_manager = TOTPManager::new(config);
1524
1525    if language == Language::Chinese {
1526        println!(
1527            "{}",
1528            "📱 请使用 Google Authenticator 或 Authy 扫描以下 QR 码:".yellow()
1529        );
1530    } else {
1531        println!(
1532            "{}",
1533            "📱 Scan this QR code with Google Authenticator or Authy:".yellow()
1534        );
1535    }
1536    println!();
1537
1538    match totp_manager.generate_qr_code() {
1539        Ok(qr_code) => {
1540            println!("{}", qr_code);
1541        }
1542        Err(e) => {
1543            if language == Language::Chinese {
1544                eprintln!("{} QR 码生成失败: {}", "⚠️".yellow(), e);
1545                println!("{}", "📝 请手动输入以下信息:".yellow());
1546            } else {
1547                eprintln!("{} QR code generation failed: {}", "⚠️".yellow(), e);
1548                println!("{}", "📝 Please enter this info manually:".yellow());
1549            }
1550            println!("{}", totp_manager.get_manual_setup_info());
1551        }
1552    }
1553
1554    println!();
1555    if language == Language::Chinese {
1556        println!(
1557            "{} 或者手动输入密钥: {}",
1558            "🔑".bright_cyan(),
1559            twofa_secret.bright_white()
1560        );
1561    } else {
1562        println!(
1563            "{} Or enter manually: {}",
1564            "🔑".bright_cyan(),
1565            twofa_secret.bright_white()
1566        );
1567    }
1568    println!();
1569
1570    // Verify 2FA setup
1571    loop {
1572        print!(
1573            "{}",
1574            if language == Language::Chinese {
1575                "请输入认证器显示的 6 位验证码以确认设置: "
1576            } else {
1577                "Enter the 6-digit code from your authenticator to verify: "
1578            }
1579        );
1580        io::stdout().flush().map_err(|e| e.to_string())?;
1581
1582        let mut input = String::new();
1583        io::stdin()
1584            .read_line(&mut input)
1585            .map_err(|e| e.to_string())?;
1586        let code = input.trim();
1587
1588        match totp_manager.verify_code(code) {
1589            Ok(true) => {
1590                println!(
1591                    "{}",
1592                    if language == Language::Chinese {
1593                        "✅ 2FA 验证成功!".green()
1594                    } else {
1595                        "✅ 2FA verification successful!".green()
1596                    }
1597                );
1598                break;
1599            }
1600            Ok(false) => {
1601                println!(
1602                    "{}",
1603                    if language == Language::Chinese {
1604                        "❌ 验证码不正确,请重试".red()
1605                    } else {
1606                        "❌ Code incorrect, please try again".red()
1607                    }
1608                );
1609                continue;
1610            }
1611            Err(e) => {
1612                eprintln!(
1613                    "{} {}: {}",
1614                    "❌".red(),
1615                    if language == Language::Chinese {
1616                        "验证失败"
1617                    } else {
1618                        "Verification failed"
1619                    },
1620                    e
1621                );
1622                continue;
1623            }
1624        }
1625    }
1626
1627    println!();
1628    if language == Language::Chinese {
1629        println!("{}", "🎉 三因子 2FA 设置完成!".green().bold());
1630        println!();
1631        println!("{}", "📝 重要信息(请妥善保管):".yellow().bold());
1632        println!("  • 硬件指纹: 已绑定到当前设备");
1633        println!(
1634            "  • 安全问题: 问题 {} - {}",
1635            question_index + 1,
1636            crate::security_question::SECURITY_QUESTIONS[question_index]
1637        );
1638        println!("  • 2FA密钥: 已添加到认证器");
1639        println!();
1640        println!("{}", "💡 下一步: 使用选项5生成三因子钱包".bright_blue());
1641    } else {
1642        println!("{}", "🎉 Triple-factor 2FA setup complete!".green().bold());
1643        println!();
1644        println!("{}", "📝 Important info (keep safe):".yellow().bold());
1645        println!("  • Hardware fingerprint: Bound to current device");
1646        println!(
1647            "  • Security question: Question {} - {}",
1648            question_index + 1,
1649            crate::security_question::SECURITY_QUESTIONS[question_index]
1650        );
1651        println!("  • 2FA key: Added to authenticator");
1652        println!();
1653        println!(
1654            "{}",
1655            "💡 Next step: Use option 5 to generate triple-factor wallet".bright_blue()
1656        );
1657    }
1658
1659    Ok(())
1660}
1661
1662/// Generate triple-factor wallet interactively
1663#[cfg(feature = "2fa")]
1664fn generate_triple_factor_wallet_interactive(_language: Language) -> Result<(), String> {
1665    Err(
1666        "This feature will be implemented soon. Please use CLI command: fnzero-safe gen-2fa-wallet"
1667            .to_string(),
1668    )
1669}
1670
1671/// Unlock triple-factor wallet interactively
1672#[cfg(feature = "2fa")]
1673fn unlock_triple_factor_wallet_interactive(_language: Language) -> Result<(), String> {
1674    Err("This feature will be implemented soon. Please use CLI command: fnzero-safe unlock-2fa-wallet -f <file>".to_string())
1675}
1676
1677/// Read password with confirmation and validation
1678fn read_password_confirmed(texts: &Texts) -> Result<String, String> {
1679    println!("{}", texts.set_password);
1680
1681    let password = prompt_password(texts.new_password, texts)?;
1682
1683    if password.is_empty() {
1684        return Err(texts.password_empty.to_string());
1685    }
1686
1687    if password.len() < 10 {
1688        return Err(texts.password_min_length.to_string());
1689    }
1690
1691    let password_confirm = prompt_password(texts.confirm_password, texts)?;
1692
1693    if password != password_confirm {
1694        return Err(texts.password_mismatch.to_string());
1695    }
1696
1697    println!("{}", texts.password_set.green());
1698    Ok(password)
1699}