1use colored::*;
7use solana_sdk::signature::Keypair;
8use solana_sdk::signer::Signer;
9use std::io::{self, Write};
10
11use crate::KeyManager;
12
13fn 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 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 results
51 .into_iter()
52 .find(|p| p.contains("prod"))
53 .or_else(|| {
54 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#[derive(Clone, Copy, PartialEq)]
63pub enum Language {
64 English,
65 Chinese,
66}
67
68struct Texts {
70 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 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 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 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 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 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
294fn 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
322struct 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
355pub fn show_main_menu() -> Result<(), String> {
357 let lang = select_language()?;
359 let texts = match lang {
360 Language::Chinese => Texts::chinese(),
361 Language::English => Texts::english(),
362 };
363
364 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 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 #[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 #[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 "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 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 #[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 #[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 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
818fn 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 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 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 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
892fn 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 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 println!();
917 println!("{}", texts.generating.cyan());
918 KeyManager::generate_keypair()
919 }
920 "2" => {
921 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 let password = read_password_confirmed(texts)?;
952
953 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 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 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
1025fn 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 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 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 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 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
1172fn 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
1183fn 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 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 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 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 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 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#[cfg(feature = "solana-ops")]
1272fn handle_solana_operation(
1273 choice: &str,
1274 language: Language,
1275 session: &mut SessionState,
1276) -> Result<(), String> {
1277 let ops_language = match language {
1279 Language::English => crate::operations::Language::English,
1280 Language::Chinese => crate::operations::Language::Chinese,
1281 };
1282
1283 let keypair = if let Some(kp) = session.get_keypair() {
1285 kp
1286 } else {
1287 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 #[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#[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 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 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 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 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 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 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#[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#[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
1677fn 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}