1use std::io::{self, Write};
7use colored::*;
8use solana_sdk::signature::Keypair;
9use solana_sdk::signer::Signer;
10
11use crate::KeyManager;
12
13fn search_keystore_in_subdirs(filename: &str) -> Option<String> {
17 fn search(dir: &std::path::Path, filename: &str, depth: usize, max_depth: usize, results: &mut Vec<String>) {
18 if depth > max_depth {
19 return;
20 }
21 if let Ok(entries) = std::fs::read_dir(dir) {
22 for entry in entries.flatten() {
23 let path = entry.path();
24 if path.is_dir() {
25 let name = path.file_name().unwrap_or_default().to_string_lossy();
26 if name.starts_with('.') || name == "target" || name == "node_modules" || name == "dev" {
28 continue;
29 }
30 search(&path, filename, depth + 1, max_depth, results);
31 } else if path.file_name().unwrap_or_default() == filename {
32 results.push(path.to_string_lossy().to_string());
33 }
34 }
35 }
36 }
37 let mut results = Vec::new();
38 search(std::path::Path::new("."), filename, 0, 3, &mut results);
39 results.into_iter().find(|p| p.contains("prod")).or_else(|| {
41 let mut results = Vec::new();
43 search(std::path::Path::new("."), filename, 0, 3, &mut results);
44 results.into_iter().next()
45 })
46}
47
48#[derive(Clone, Copy, PartialEq)]
50pub enum Language {
51 English,
52 Chinese,
53}
54
55struct Texts {
57 title: &'static str,
59 core_functions: &'static str,
60 create_plain: &'static str,
61 create_encrypted: &'static str,
62 decrypt: &'static str,
63 exit: &'static str,
64 select_option: &'static str,
65 goodbye: &'static str,
66 invalid_option: &'static str,
67 continue_use: &'static str,
68
69 create_plain_title: &'static str,
71 keypair_generated: &'static str,
72 public_key: &'static str,
73 private_key: &'static str,
74 output_method: &'static str,
75 display_only: &'static str,
76 save_to_file: &'static str,
77 select: &'static str,
78 file_path: &'static str,
79 file_saved: &'static str,
80 security_warning: &'static str,
81 plaintext_warning: &'static str,
82 save_securely: &'static str,
83 dont_share: &'static str,
84 recommend_encrypted: &'static str,
85
86 create_encrypted_title: &'static str,
88 choose_method: &'static str,
89 generate_new: &'static str,
90 import_existing: &'static str,
91 generating: &'static str,
92 enter_private_key: &'static str,
93 private_key_empty: &'static str,
94 keypair_ready: &'static str,
95 keystore_recommended: &'static str,
96 show_encrypted_string: &'static str,
97 keystore_created: &'static str,
98 private_key_encrypted: &'static str,
99 important_note: &'static str,
100 keep_safe: &'static str,
101 lost_password_warning: &'static str,
102 backup_recommended: &'static str,
103 encrypted_private_key: &'static str,
104 keep_safe_both: &'static str,
105
106 decrypt_title: &'static str,
108 input_method: &'static str,
109 from_keystore: &'static str,
110 from_encrypted_string: &'static str,
111 encrypted_key: &'static str,
112 enter_password: &'static str,
113 decrypt_success: &'static str,
114 file_not_exist: &'static str,
115 dont_share_warning: &'static str,
116 delete_plaintext: &'static str,
117 use_encryption: &'static str,
118
119 set_password: &'static str,
121 new_password: &'static str,
122 confirm_password: &'static str,
123 password_empty: &'static str,
124 password_min_length: &'static str,
125 password_mismatch: &'static str,
126 password_set: &'static str,
127
128 invalid_choice: &'static str,
130 write_failed: &'static str,
131}
132
133impl Texts {
134 fn chinese() -> Self {
135 Self {
136 title: " Sol-SafeKey - Solana 密钥管理工具",
137 core_functions: "核心功能 (只需3个操作):",
138 create_plain: " {} 创建明文私钥",
139 create_encrypted: " {} 创建加密私钥(bot)",
140 decrypt: " {} 解密私钥",
141 exit: " {} 退出",
142 select_option: "请输入选项 [0-14]: ",
143 goodbye: "👋 再见!",
144 invalid_option: "❌ 无效选项,请重新选择",
145 continue_use: "是否继续使用? [Y/n]: ",
146
147 create_plain_title: " 创建明文私钥",
148 keypair_generated: "✅ 密钥对生成成功!",
149 public_key: "公钥地址:",
150 private_key: "私钥:",
151 output_method: "输出方式:",
152 display_only: " 1. 仅显示 (当前已显示)",
153 save_to_file: " 2. 保存到文件",
154 select: "请选择 [1/2]: ",
155 file_path: "文件路径 (默认: keypair.json): ",
156 file_saved: "✅ 已保存到文件",
157 security_warning: "⚠️ 安全警告:",
158 plaintext_warning: " • 明文私钥非常不安全",
159 save_securely: " • 请立即保存到安全位置",
160 dont_share: " • 不要分享给任何人",
161 recommend_encrypted: " • 建议使用 '创建加密私钥' 功能",
162
163 create_encrypted_title: " 创建加密私钥",
164 choose_method: "选择方式:",
165 generate_new: " 1. 生成新的密钥对并加密",
166 import_existing: " 2. 导入现有私钥并加密",
167 generating: "🎲 生成新的密钥对...",
168 enter_private_key: "请输入私钥 (base58 格式): ",
169 private_key_empty: "私钥不能为空",
170 keypair_ready: "✅ 密钥对准备完成",
171 keystore_recommended: " 1. 保存为 Keystore 文件 (推荐)",
172 show_encrypted_string: " 2. 显示加密字符串",
173 keystore_created: " ✅ Keystore 创建成功!",
174 private_key_encrypted: "🔒 私钥已加密保存",
175 important_note: "⚠️ 重要提示:",
176 keep_safe: " • 请妥善保管 Keystore 文件和密码",
177 lost_password_warning: " • 丢失密码将无法恢复钱包",
178 backup_recommended: " • 建议备份到安全位置",
179 encrypted_private_key: "加密后的私钥:",
180 keep_safe_both: "⚠️ 提示: 请妥善保管加密私钥和密码",
181
182 decrypt_title: " 解密私钥",
183 input_method: "输入方式:",
184 from_keystore: " 1. 从 Keystore 文件读取",
185 from_encrypted_string: " 2. 输入加密字符串",
186 encrypted_key: "加密的私钥: ",
187 enter_password: "请输入密码: ",
188 decrypt_success: " ✅ 解密成功!",
189 file_not_exist: "文件不存在: {}",
190 dont_share_warning: " • 请勿分享私钥给任何人",
191 delete_plaintext: " • 使用完毕后请立即删除明文私钥文件",
192 use_encryption: " • 建议使用加密方式保存",
193
194 set_password: "设置加密密码 (至少 10 个字符):",
195 new_password: "新密码: ",
196 confirm_password: "确认密码: ",
197 password_empty: "密码不能为空",
198 password_min_length: "密码长度至少 10 个字符",
199 password_mismatch: "两次密码不一致",
200 password_set: "✅ 密码设置成功",
201
202 invalid_choice: "无效选项",
203 write_failed: "写入文件失败: {}",
204 }
205 }
206
207 fn english() -> Self {
208 Self {
209 title: " Sol-SafeKey - Solana Key Management Tool",
210 core_functions: "Core Functions (3 operations):",
211 create_plain: " {} Create Plain Private Key",
212 create_encrypted: " {} Create Encrypted Private Key (Bot)",
213 decrypt: " {} Decrypt Private Key",
214 exit: " {} Exit",
215 select_option: "Select option [0-14]: ",
216 goodbye: "👋 Goodbye!",
217 invalid_option: "❌ Invalid option, please try again",
218 continue_use: "Continue? [Y/n]: ",
219
220 create_plain_title: " Create Plain Private Key",
221 keypair_generated: "✅ Keypair generated successfully!",
222 public_key: "Public Key:",
223 private_key: "Private Key:",
224 output_method: "Output Method:",
225 display_only: " 1. Display Only (already shown)",
226 save_to_file: " 2. Save to File",
227 select: "Select [1/2]: ",
228 file_path: "File path (default: keypair.json): ",
229 file_saved: "✅ Saved to file",
230 security_warning: "⚠️ Security Warning:",
231 plaintext_warning: " • Plaintext private key is very insecure",
232 save_securely: " • Save to a secure location immediately",
233 dont_share: " • Never share with anyone",
234 recommend_encrypted: " • Consider using 'Create Encrypted Private Key'",
235
236 create_encrypted_title: " Create Encrypted Private Key",
237 choose_method: "Choose Method:",
238 generate_new: " 1. Generate new keypair and encrypt",
239 import_existing: " 2. Import existing private key and encrypt",
240 generating: "🎲 Generating new keypair...",
241 enter_private_key: "Enter private key (base58 format): ",
242 private_key_empty: "Private key cannot be empty",
243 keypair_ready: "✅ Keypair ready",
244 keystore_recommended: " 1. Save as Keystore file (Recommended)",
245 show_encrypted_string: " 2. Show encrypted string",
246 keystore_created: " ✅ Keystore created successfully!",
247 private_key_encrypted: "🔒 Private key encrypted and saved",
248 important_note: "⚠️ Important:",
249 keep_safe: " • Keep Keystore file and password safe",
250 lost_password_warning: " • Lost password = lost wallet",
251 backup_recommended: " • Backup to a secure location",
252 encrypted_private_key: "Encrypted Private Key:",
253 keep_safe_both: "⚠️ Note: Keep encrypted key and password safe",
254
255 decrypt_title: " Decrypt Private Key",
256 input_method: "Input Method:",
257 from_keystore: " 1. From Keystore file",
258 from_encrypted_string: " 2. Enter encrypted string",
259 encrypted_key: "Encrypted key: ",
260 enter_password: "Enter password: ",
261 decrypt_success: " ✅ Decryption successful!",
262 file_not_exist: "File not found: {}",
263 dont_share_warning: " • Never share private key with anyone",
264 delete_plaintext: " • Delete plaintext key file after use",
265 use_encryption: " • Consider using encryption for storage",
266
267 set_password: "Set encryption password (minimum 10 characters):",
268 new_password: "New password: ",
269 confirm_password: "Confirm password: ",
270 password_empty: "Password cannot be empty",
271 password_min_length: "Password must be at least 10 characters",
272 password_mismatch: "Passwords do not match",
273 password_set: "✅ Password set successfully",
274
275 invalid_choice: "Invalid choice",
276 write_failed: "Write failed: {}",
277 }
278 }
279}
280
281fn select_language() -> Result<Language, String> {
283 println!("\n{}", "=".repeat(50).cyan());
284 println!("{}", " Language / 语言选择".cyan().bold());
285 println!("{}", "=".repeat(50).cyan());
286 println!();
287 println!(" {} English", "1.".green().bold());
288 println!(" {} 中文", "2.".green().bold());
289 println!();
290 print!("Select / 选择 [1/2]: ");
291 io::stdout().flush().map_err(|e| e.to_string())?;
292
293 let mut choice = String::new();
294 io::stdin().read_line(&mut choice).map_err(|e| e.to_string())?;
295 let choice = choice.trim();
296
297 match choice {
298 "1" => Ok(Language::English),
299 "2" => Ok(Language::Chinese),
300 _ => {
301 println!("\n{}", "❌ Invalid option / 无效选项".red());
302 select_language()
303 }
304 }
305}
306
307struct SessionState {
309 keypair: Option<Keypair>,
310 keystore_path: Option<String>,
311}
312
313impl SessionState {
314 fn new() -> Self {
315 Self {
316 keypair: None,
317 keystore_path: None,
318 }
319 }
320
321 fn is_unlocked(&self) -> bool {
322 self.keypair.is_some()
323 }
324
325 fn unlock(&mut self, keypair: Keypair, path: String) {
326 self.keypair = Some(keypair);
327 self.keystore_path = Some(path);
328 }
329
330 fn get_keypair(&self) -> Option<&Keypair> {
331 self.keypair.as_ref()
332 }
333
334 fn lock(&mut self) {
335 self.keypair = None;
336 self.keystore_path = None;
337 }
338}
339
340pub fn show_main_menu() -> Result<(), String> {
342 let lang = select_language()?;
344 let texts = match lang {
345 Language::Chinese => Texts::chinese(),
346 Language::English => Texts::english(),
347 };
348
349 let mut session = SessionState::new();
351
352 loop {
353 println!("\n{}", "=".repeat(50).cyan());
354 println!("{}", texts.title.cyan().bold());
355 println!("{}", "=".repeat(50).cyan());
356 println!();
357 println!("{}", texts.core_functions);
358 println!();
359 println!(" {} {}", "1.".green().bold(), &texts.create_plain[6..]);
360 println!(" {} {}", "2.".green().bold(), &texts.create_encrypted[6..]);
361 println!(" {} {}", "3.".green().bold(), &texts.decrypt[6..]);
362
363 println!();
365 if session.is_unlocked() {
366 if lang == Language::Chinese {
367 println!(" 🔓 {} {}", "钱包已解锁:".green().bold(), session.get_keypair().unwrap().pubkey().to_string().bright_white());
368 println!(" {} {}", "L.".yellow().bold(), "锁定钱包".yellow());
369 } else {
370 println!(" 🔓 {} {}", "Wallet Unlocked:".green().bold(), session.get_keypair().unwrap().pubkey().to_string().bright_white());
371 println!(" {} {}", "L.".yellow().bold(), "Lock Wallet".yellow());
372 }
373 } else {
374 if lang == Language::Chinese {
375 println!(" 🔒 {} {}", "钱包状态:".red(), "未解锁".red());
376 println!(" {} {}", "U.".green().bold(), "解锁钱包(用于Solana操作)".green());
377 } else {
378 println!(" 🔒 {} {}", "Wallet Status:".red(), "Locked".red());
379 println!(" {} {}", "U.".green().bold(), "Unlock Wallet (for Solana Operations)".green());
380 }
381 }
382
383 #[cfg(feature = "2fa")]
385 {
386 println!();
387 if lang == Language::Chinese {
388 println!("{}", " 高级安全功能:".bright_magenta().bold());
389 } else {
390 println!("{}", " Advanced Security:".bright_magenta().bold());
391 }
392 println!(" {} {}", "4.".bright_magenta().bold(), if lang == Language::Chinese { "设置 2FA 认证" } else { "Setup 2FA Authentication" });
393 println!(" {} {}", "5.".bright_magenta().bold(), if lang == Language::Chinese { "生成三因子钱包" } else { "Generate Triple-Factor Wallet" });
394 println!(" {} {}", "6.".bright_magenta().bold(), if lang == Language::Chinese { "解锁三因子钱包" } else { "Unlock Triple-Factor Wallet" });
395 }
396
397 #[cfg(feature = "solana-ops")]
399 {
400 println!();
401 if lang == Language::Chinese {
402 println!("{}", " Solana 链上操作:".bright_blue().bold());
403 } else {
404 println!("{}", " Solana Operations:".bright_blue().bold());
405 }
406 #[cfg(feature = "2fa")]
407 {
408 println!(" {} {}", "7.".bright_cyan().bold(), if lang == Language::Chinese { "查询 SOL 余额" } else { "Check SOL Balance" });
409 println!(" {} {}", "8.".bright_cyan().bold(), if lang == Language::Chinese { "转账 SOL" } else { "Transfer SOL" });
410 println!(" {} {}", "9.".bright_cyan().bold(), if lang == Language::Chinese { "创建 WSOL ATA" } else { "Create WSOL ATA" });
411 println!(" {} {}", "10.".bright_cyan().bold(), if lang == Language::Chinese { "包装 SOL → WSOL" } else { "Wrap SOL → WSOL" });
412 println!(" {} {}", "11.".bright_cyan().bold(), if lang == Language::Chinese { "解包 WSOL → SOL" } else { "Unwrap WSOL → SOL" });
413 println!(" {} {}", "12.".bright_cyan().bold(), if lang == Language::Chinese { "关闭 WSOL ATA" } else { "Close WSOL ATA" });
414 println!(" {} {}", "13.".bright_cyan().bold(), if lang == Language::Chinese { "转账 SPL 代币" } else { "Transfer SPL Token" });
415 println!(" {} {}", "14.".bright_cyan().bold(), if lang == Language::Chinese { "创建 Nonce 账户" } else { "Create Nonce Account" });
416
417 #[cfg(feature = "sol-trade-sdk")]
418 println!(" {} {}", "15.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 卖出代币" } else { "Pump.fun Sell Tokens" });
419 #[cfg(feature = "sol-trade-sdk")]
420 println!(" {} {}", "16.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 卖出代币" } else { "PumpSwap Sell Tokens" });
421 #[cfg(feature = "sol-trade-sdk")]
422 println!(" {} {}", "17.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 返现(查看与领取)" } else { "Pump.fun Cashback (View & Claim)" });
423 #[cfg(feature = "sol-trade-sdk")]
424 println!(" {} {}", "18.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 返现(查看与领取)" } else { "PumpSwap Cashback (View & Claim)" });
425 }
426 #[cfg(not(feature = "2fa"))]
427 {
428 println!(" {} {}", "4.".bright_cyan().bold(), if lang == Language::Chinese { "查询 SOL 余额" } else { "Check SOL Balance" });
429 println!(" {} {}", "5.".bright_cyan().bold(), if lang == Language::Chinese { "转账 SOL" } else { "Transfer SOL" });
430 println!(" {} {}", "6.".bright_cyan().bold(), if lang == Language::Chinese { "创建 WSOL ATA" } else { "Create WSOL ATA" });
431 println!(" {} {}", "7.".bright_cyan().bold(), if lang == Language::Chinese { "包装 SOL → WSOL" } else { "Wrap SOL → WSOL" });
432 println!(" {} {}", "8.".bright_cyan().bold(), if lang == Language::Chinese { "解包 WSOL → SOL" } else { "Unwrap WSOL → SOL" });
433 println!(" {} {}", "9.".bright_cyan().bold(), if lang == Language::Chinese { "关闭 WSOL ATA" } else { "Close WSOL ATA" });
434 println!(" {} {}", "10.".bright_cyan().bold(), if lang == Language::Chinese { "转账 SPL 代币" } else { "Transfer SPL Token" });
435 println!(" {} {}", "11.".bright_cyan().bold(), if lang == Language::Chinese { "创建 Nonce 账户" } else { "Create Nonce Account" });
436
437 #[cfg(feature = "sol-trade-sdk")]
438 println!(" {} {}", "12.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 卖出代币" } else { "Pump.fun Sell Tokens" });
439 #[cfg(feature = "sol-trade-sdk")]
440 println!(" {} {}", "13.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 卖出代币" } else { "PumpSwap Sell Tokens" });
441 #[cfg(feature = "sol-trade-sdk")]
442 println!(" {} {}", "14.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 返现(查看与领取)" } else { "Pump.fun Cashback (View & Claim)" });
443 #[cfg(feature = "sol-trade-sdk")]
444 println!(" {} {}", "15.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 返现(查看与领取)" } else { "PumpSwap Cashback (View & Claim)" });
445 }
446 }
447
448 println!();
449 println!(" {} {}", "0.".red().bold(), &texts.exit[6..]);
450 println!();
451 print!("{}", texts.select_option);
452 io::stdout().flush().map_err(|e| e.to_string())?;
453
454 let mut choice = String::new();
455 io::stdin().read_line(&mut choice).map_err(|e| e.to_string())?;
456 let choice = choice.trim();
457
458 match choice.to_lowercase().as_str() {
459 "1" => create_plain_key_interactive(&texts)?,
460 "2" => create_encrypted_key_interactive(&texts)?,
461 "3" => decrypt_key_interactive(&texts)?,
462
463 "u" => {
465 if session.is_unlocked() {
466 if lang == Language::Chinese {
467 println!("\n✅ 钱包已经解锁!");
468 } else {
469 println!("\n✅ Wallet already unlocked!");
470 }
471 } else {
472 if let Err(e) = unlock_wallet_interactive(&mut session, lang) {
473 eprintln!("❌ {}", e);
474 }
475 }
476 continue;
478 }
479 "l" => {
480 if session.is_unlocked() {
481 session.lock();
482 if lang == Language::Chinese {
483 println!("\n🔒 钱包已锁定");
484 } else {
485 println!("\n🔒 Wallet locked");
486 }
487 } else {
488 if lang == Language::Chinese {
489 println!("\n⚠️ 钱包未解锁");
490 } else {
491 println!("\n⚠️ Wallet not unlocked");
492 }
493 }
494 continue;
495 }
496
497 #[cfg(feature = "2fa")]
499 "4" => {
500 if let Err(e) = setup_2fa_interactive(lang) {
501 eprintln!("❌ {}", e);
502 }
503 }
504 #[cfg(feature = "2fa")]
505 "5" => {
506 if let Err(e) = generate_triple_factor_wallet_interactive(lang) {
507 eprintln!("❌ {}", e);
508 }
509 }
510 #[cfg(feature = "2fa")]
511 "6" => {
512 if let Err(e) = unlock_triple_factor_wallet_interactive(lang) {
513 eprintln!("❌ {}", e);
514 }
515 }
516
517 #[cfg(all(feature = "solana-ops", feature = "2fa"))]
519 "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" => {
520 if let Err(e) = handle_solana_operation(choice, lang, &mut session) {
521 eprintln!("❌ {}", e);
522 }
523 }
524 #[cfg(all(feature = "solana-ops", not(feature = "2fa")))]
525 "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" => {
526 if let Err(e) = handle_solana_operation(choice, lang, &mut session) {
527 eprintln!("❌ {}", e);
528 }
529 }
530
531 "0" => {
532 println!("\n{}", texts.goodbye.cyan());
533 break;
534 }
535 _ => {
536 println!("\n{}", texts.invalid_option.red());
537 continue;
538 }
539 }
540
541 println!();
543 print!("{}", texts.continue_use);
544 io::stdout().flush().map_err(|e| e.to_string())?;
545
546 let mut continue_choice = String::new();
547 io::stdin().read_line(&mut continue_choice).map_err(|e| e.to_string())?;
548 let continue_choice = continue_choice.trim().to_lowercase();
549
550 if continue_choice == "n" || continue_choice == "no" {
551 println!("\n{}", texts.goodbye.cyan());
552 break;
553 }
554 }
555
556 Ok(())
557}
558
559fn create_plain_key_interactive(texts: &Texts) -> Result<(), String> {
561 println!("\n{}", "=".repeat(50).yellow());
562 println!("{}", texts.create_plain_title.yellow().bold());
563 println!("{}", "=".repeat(50).yellow());
564 println!();
565
566 let keypair = KeyManager::generate_keypair();
568 let pubkey = keypair.pubkey();
569 let private_key = keypair.to_base58_string();
570
571 println!("{}", texts.keypair_generated.green().bold());
572 println!();
573 println!("{} {}", texts.public_key.cyan(), pubkey.to_string().white().bold());
574 println!("{} {}", texts.private_key.red().bold(), private_key);
575 println!();
576
577 println!("{}",texts.output_method);
579 println!("{}", texts.display_only);
580 println!("{}", texts.save_to_file);
581 println!();
582 print!("{}", texts.select);
583 io::stdout().flush().map_err(|e| e.to_string())?;
584
585 let mut output_choice = String::new();
586 io::stdin().read_line(&mut output_choice).map_err(|e| e.to_string())?;
587 let output_choice = output_choice.trim();
588
589 if output_choice == "2" {
590 print!("{}", texts.file_path);
591 io::stdout().flush().map_err(|e| e.to_string())?;
592
593 let mut file_path = String::new();
594 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
595 let file_path = file_path.trim();
596 let file_path = if file_path.is_empty() {
597 "keypair.json"
598 } else {
599 file_path
600 };
601
602 let bytes = keypair.to_bytes();
604 let json = serde_json::to_string(&bytes.to_vec())
605 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
606
607 std::fs::write(file_path, json)
608 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
609
610 println!();
611 println!("{}", texts.file_saved.green());
612 println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
613 }
614
615 println!();
616 println!("{}", texts.security_warning.yellow().bold());
617 println!("{}", texts.plaintext_warning);
618 println!("{}", texts.save_securely);
619 println!("{}", texts.dont_share);
620 println!("{}", texts.recommend_encrypted);
621
622 Ok(())
623}
624
625fn create_encrypted_key_interactive(texts: &Texts) -> Result<(), String> {
627 println!("\n{}", "=".repeat(50).yellow());
628 println!("{}", texts.create_encrypted_title.yellow().bold());
629 println!("{}", "=".repeat(50).yellow());
630 println!();
631
632 println!("{}", texts.choose_method);
634 println!("{}", texts.generate_new);
635 println!("{}", texts.import_existing);
636 println!();
637 print!("{}", texts.select);
638 io::stdout().flush().map_err(|e| e.to_string())?;
639
640 let mut source_choice = String::new();
641 io::stdin().read_line(&mut source_choice).map_err(|e| e.to_string())?;
642 let source_choice = source_choice.trim();
643
644 let keypair = match source_choice {
645 "1" => {
646 println!();
648 println!("{}", texts.generating.cyan());
649 KeyManager::generate_keypair()
650 }
651 "2" => {
652 println!();
654 print!("{}", texts.enter_private_key);
655 io::stdout().flush().map_err(|e| e.to_string())?;
656
657 let mut private_key = String::new();
658 io::stdin().read_line(&mut private_key).map_err(|e| e.to_string())?;
659 let private_key = private_key.trim();
660
661 if private_key.is_empty() {
662 return Err(texts.private_key_empty.to_string());
663 }
664
665 Keypair::from_base58_string(private_key)
666 }
667 _ => {
668 return Err(texts.invalid_choice.to_string());
669 }
670 };
671
672 let pubkey = keypair.pubkey();
673
674 println!();
675 println!("{}", texts.keypair_ready.green());
676 println!("{} {}", texts.public_key.cyan(), pubkey);
677 println!();
678
679 let password = read_password_confirmed(texts)?;
681
682 println!();
684 println!("{}", texts.output_method);
685 println!("{}", texts.keystore_recommended);
686 println!("{}", texts.show_encrypted_string);
687 println!();
688 print!("{}", texts.select);
689 io::stdout().flush().map_err(|e| e.to_string())?;
690
691 let mut output_choice = String::new();
692 io::stdin().read_line(&mut output_choice).map_err(|e| e.to_string())?;
693 let output_choice = output_choice.trim();
694
695 match output_choice {
696 "1" => {
697 print!("{}", texts.file_path.replace("keypair", "wallet"));
699 io::stdout().flush().map_err(|e| e.to_string())?;
700
701 let mut file_path = String::new();
702 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
703 let file_path = file_path.trim();
704 let file_path = if file_path.is_empty() {
705 "keystore.json"
706 } else {
707 file_path
708 };
709
710 let keystore_json = KeyManager::keypair_to_encrypted_json(&keypair, &password)?;
711 std::fs::write(file_path, keystore_json)
712 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
713
714 println!();
715 println!("{}", "=".repeat(50).green());
716 println!("{}", texts.keystore_created.green().bold());
717 println!("{}", "=".repeat(50).green());
718 println!();
719 println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
720 println!("{} {}", texts.public_key.cyan(), pubkey);
721 println!("{}", texts.private_key_encrypted.green());
722 println!();
723 println!("{}", texts.important_note.yellow().bold());
724 println!("{}", texts.keep_safe);
725 println!("{}", texts.lost_password_warning);
726 println!("{}", texts.backup_recommended);
727 }
728 "2" => {
729 let private_key = keypair.to_base58_string();
731 let encrypted = KeyManager::encrypt_with_password(&private_key, &password)?;
732
733 println!();
734 println!("{}", texts.keypair_ready.green().bold());
735 println!();
736 println!("{} {}", texts.public_key.cyan(), pubkey);
737 println!("{}", texts.encrypted_private_key.cyan());
738 println!("{}", encrypted);
739 println!();
740 println!("{}", texts.keep_safe_both.yellow());
741 }
742 _ => {
743 return Err(texts.invalid_choice.to_string());
744 }
745 }
746
747 Ok(())
748}
749
750fn decrypt_key_interactive(texts: &Texts) -> Result<(), String> {
752 println!("\n{}", "=".repeat(50).yellow());
753 println!("{}", texts.decrypt_title.yellow().bold());
754 println!("{}", "=".repeat(50).yellow());
755 println!();
756
757 println!("{}", texts.input_method);
759 println!("{}", texts.from_keystore);
760 println!("{}", texts.from_encrypted_string);
761 println!();
762 print!("{}", texts.select);
763 io::stdout().flush().map_err(|e| e.to_string())?;
764
765 let mut input_choice = String::new();
766 io::stdin().read_line(&mut input_choice).map_err(|e| e.to_string())?;
767 let input_choice = input_choice.trim();
768
769 let (private_key, pubkey) = match input_choice {
770 "1" => {
771 print!("{}", texts.file_path.trim_end_matches("(默认: keypair.json): ").trim_end_matches("(default: keypair.json): "));
773 io::stdout().flush().map_err(|e| e.to_string())?;
774
775 let mut file_path = String::new();
776 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
777 let file_path = file_path.trim();
778
779 if !std::path::Path::new(file_path).exists() {
780 return Err(format!("{}", texts.file_not_exist.replace("{}", file_path)));
781 }
782
783 println!();
784 let password = prompt_password(texts.enter_password, texts)?;
785
786 let keystore_json = std::fs::read_to_string(file_path)
787 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
788
789 let keypair = KeyManager::keypair_from_encrypted_json(&keystore_json, &password)?;
790 let pubkey = keypair.pubkey();
791 let private_key = keypair.to_base58_string();
792
793 (private_key, pubkey)
794 }
795 "2" => {
796 print!("{}", texts.encrypted_key);
798 io::stdout().flush().map_err(|e| e.to_string())?;
799
800 let mut encrypted = String::new();
801 io::stdin().read_line(&mut encrypted).map_err(|e| e.to_string())?;
802 let encrypted = encrypted.trim();
803
804 println!();
805 let password = prompt_password(texts.enter_password, texts)?;
806
807 let private_key = KeyManager::decrypt_with_password(encrypted, &password)?;
808 let keypair = Keypair::from_base58_string(&private_key);
809 let pubkey = keypair.pubkey();
810
811 (private_key, pubkey)
812 }
813 _ => {
814 return Err(texts.invalid_choice.to_string());
815 }
816 };
817
818 println!();
819 println!("{}", "=".repeat(50).green());
820 println!("{}", texts.decrypt_success.green().bold());
821 println!("{}", "=".repeat(50).green());
822 println!();
823 println!("{} {}", texts.public_key.cyan(), pubkey);
824 println!("{} {}", texts.private_key.red().bold(), private_key);
825 println!();
826
827 println!("{}", texts.output_method);
829 println!("{}", texts.display_only);
830 println!("{}", texts.save_to_file);
831 println!();
832 print!("{}", texts.select);
833 io::stdout().flush().map_err(|e| e.to_string())?;
834
835 let mut output_choice = String::new();
836 io::stdin().read_line(&mut output_choice).map_err(|e| e.to_string())?;
837 let output_choice = output_choice.trim();
838
839 if output_choice == "2" {
840 let default_filename = if texts.file_path.contains("默认") {
841 "decrypted_key.txt"
842 } else {
843 "decrypted_key.txt"
844 };
845
846 print!("{}", texts.file_path.replace("keypair.json", default_filename));
847 io::stdout().flush().map_err(|e| e.to_string())?;
848
849 let mut file_path = String::new();
850 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
851 let file_path = file_path.trim();
852 let file_path = if file_path.is_empty() {
853 default_filename
854 } else {
855 file_path
856 };
857
858 let content = format!("{} {}\n{} {}\n", texts.public_key, pubkey, texts.private_key.trim_end_matches(':'), private_key);
859 std::fs::write(file_path, content)
860 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
861
862 println!();
863 println!("{}", texts.file_saved.green());
864 println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
865 }
866
867 println!();
868 println!("{}", texts.security_warning.yellow().bold());
869 println!("{}", texts.dont_share_warning);
870 println!("{}", texts.delete_plaintext);
871 println!("{}", texts.use_encryption);
872
873 Ok(())
874}
875
876fn prompt_password(prompt: &str, texts: &Texts) -> Result<String, String> {
878 print!("{}", prompt);
879 io::stdout().flush().map_err(|e| e.to_string())?;
880
881 let password = rpassword::read_password()
882 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
883
884 Ok(password.trim().to_string())
885}
886
887fn unlock_wallet_interactive(session: &mut SessionState, language: Language) -> Result<(), String> {
889 println!();
890 if language == Language::Chinese {
891 println!("{}", " 解锁钱包".cyan().bold());
892 print!("Keystore 文件路径 [keystore.json]: ");
893 } else {
894 println!("{}", " Unlock Wallet".cyan().bold());
895 print!("Keystore file path [keystore.json]: ");
896 }
897 io::stdout().flush().map_err(|e| e.to_string())?;
898
899 let mut keystore_path = String::new();
900 io::stdin().read_line(&mut keystore_path).map_err(|e| e.to_string())?;
901 let keystore_path = keystore_path.trim();
902
903 let keystore_path = if keystore_path.is_empty() {
905 if let Some(found) = search_keystore_in_subdirs("keystore.json") {
906 if language == Language::Chinese {
907 println!("🔍 在子目录中找到: {}", found);
908 } else {
909 println!("🔍 Found in subdirectory: {}", found);
910 }
911 found
912 } else {
913 "keystore.json".to_string()
914 }
915 } else {
916 keystore_path.to_string()
917 };
918
919 let file_content = std::fs::read_to_string(&keystore_path).map_err(|e| {
921 if language == Language::Chinese {
922 format!(
923 "无法读取 Keystore 文件「{}」: {}(请确认路径正确、当前工作目录是否为文件所在目录)",
924 keystore_path, e
925 )
926 } else {
927 format!("Failed to read keystore '{}': {}", keystore_path, e)
928 }
929 })?;
930
931 let json: serde_json::Value = serde_json::from_str(&file_content)
933 .map_err(|e| format!("Failed to parse keystore: {}", e))?;
934
935 let encryption_type = json["encryption_type"].as_str().unwrap_or("password_only");
936
937 let keypair = match encryption_type {
940 "password_only" => {
941 crate::bot_helper::unlock_wallet(&keystore_path)
942 .map_err(|e| format!("Failed to decrypt keystore: {}", e))?
943 }
944 "triple_factor_v1" => {
945 return Err("Triple-factor wallets not yet supported in interactive mode. Please use the CLI.".to_string());
946 }
947 _ => {
948 return Err(format!("Unknown encryption type: {}", encryption_type));
949 }
950 };
951
952 session.unlock(keypair, keystore_path.to_string());
954
955 if language == Language::Chinese {
956 println!("✅ 钱包解锁成功!");
957 println!("📍 钱包地址: {}", session.get_keypair().unwrap().pubkey());
958 println!("💡 提示: 在本次会话中,Solana操作将使用此钱包,无需重复输入密码");
959 } else {
960 println!("✅ Wallet unlocked successfully!");
961 println!("📍 Wallet address: {}", session.get_keypair().unwrap().pubkey());
962 println!("💡 Tip: Solana operations in this session will use this wallet without re-entering password");
963 }
964
965 Ok(())
966}
967
968#[cfg(feature = "solana-ops")]
970fn handle_solana_operation(choice: &str, language: Language, session: &mut SessionState) -> Result<(), String> {
971 let ops_language = match language {
973 Language::English => crate::operations::Language::English,
974 Language::Chinese => crate::operations::Language::Chinese,
975 };
976
977 let keypair = if let Some(kp) = session.get_keypair() {
979 kp
980 } else {
981 if language == Language::Chinese {
983 println!("\n⚠️ 请先使用 'U' 选项解锁钱包");
984 } else {
985 println!("\n⚠️ Please unlock wallet first using 'U' option");
986 }
987 return Ok(());
988 };
989
990 if language == Language::Chinese {
991 println!("\n📍 使用钱包: {}", keypair.pubkey());
992 } else {
993 println!("\n📍 Using wallet: {}", keypair.pubkey());
994 }
995
996 #[cfg(feature = "2fa")]
998 let result = match choice {
999 "7" => crate::operations::check_balance(&keypair, ops_language),
1000 "8" => crate::operations::transfer_sol(&keypair, ops_language),
1001 "9" => crate::operations::create_wsol_ata(&keypair, ops_language),
1002 "10" => crate::operations::wrap_sol(&keypair, ops_language),
1003 "11" => crate::operations::unwrap_sol(&keypair, ops_language),
1004 "12" => crate::operations::close_wsol_ata(&keypair, ops_language),
1005 "13" => crate::operations::transfer_token(&keypair, ops_language),
1006 "14" => crate::operations::create_nonce_account(&keypair, ops_language),
1007 #[cfg(feature = "sol-trade-sdk")]
1008 "15" => crate::operations::pumpfun_sell_interactive(&keypair, ops_language),
1009 #[cfg(feature = "sol-trade-sdk")]
1010 "16" => crate::operations::pumpswap_sell_interactive(&keypair, ops_language),
1011 #[cfg(feature = "sol-trade-sdk")]
1012 "17" => crate::operations::pumpfun_cashback_interactive(&keypair, ops_language),
1013 #[cfg(feature = "sol-trade-sdk")]
1014 "18" => crate::operations::pumpswap_cashback_interactive(&keypair, ops_language),
1015 _ => Err("Invalid operation".to_string()),
1016 };
1017
1018 #[cfg(not(feature = "2fa"))]
1019 let result = match choice {
1020 "4" => crate::operations::check_balance(&keypair, ops_language),
1021 "5" => crate::operations::transfer_sol(&keypair, ops_language),
1022 "6" => crate::operations::create_wsol_ata(&keypair, ops_language),
1023 "7" => crate::operations::wrap_sol(&keypair, ops_language),
1024 "8" => crate::operations::unwrap_sol(&keypair, ops_language),
1025 "9" => crate::operations::close_wsol_ata(&keypair, ops_language),
1026 "10" => crate::operations::transfer_token(&keypair, ops_language),
1027 "11" => crate::operations::create_nonce_account(&keypair, ops_language),
1028 #[cfg(feature = "sol-trade-sdk")]
1029 "12" => crate::operations::pumpfun_sell_interactive(&keypair, ops_language),
1030 #[cfg(feature = "sol-trade-sdk")]
1031 "13" => crate::operations::pumpswap_sell_interactive(&keypair, ops_language),
1032 #[cfg(feature = "sol-trade-sdk")]
1033 "14" => crate::operations::pumpfun_cashback_interactive(&keypair, ops_language),
1034 #[cfg(feature = "sol-trade-sdk")]
1035 "15" => crate::operations::pumpswap_cashback_interactive(&keypair, ops_language),
1036 _ => Err("Invalid operation".to_string()),
1037 };
1038
1039 result
1040}
1041
1042#[cfg(feature = "2fa")]
1044fn setup_2fa_interactive(language: Language) -> Result<(), String> {
1045 use crate::{derive_totp_secret_from_hardware_and_password, hardware_fingerprint::HardwareFingerprint, security_question::SecurityQuestion, totp::*};
1046 use rpassword;
1047
1048 let account = "wallet";
1049 let issuer = "Sol-SafeKey";
1050
1051 println!("\n{}", "=".repeat(50).bright_magenta());
1052 if language == Language::Chinese {
1053 println!("{}", " 🔐 三因子 2FA 安全设置".bright_magenta().bold());
1054 } else {
1055 println!("{}", " 🔐 Triple-Factor 2FA Security Setup".bright_magenta().bold());
1056 }
1057 println!("{}", "=".repeat(50).bright_magenta());
1058 println!();
1059
1060 if language == Language::Chinese {
1061 println!("{}", "⚠️ 安全架构说明:".yellow().bold());
1062 println!(" • 因子1: 硬件指纹(自动收集,绑定设备)");
1063 println!(" • 因子2: 主密码(您设置的强密码)");
1064 println!(" • 因子3: 安全问题答案(防止密码泄露)");
1065 println!(" • 2FA密钥: 从硬件指纹+主密码派生(确定性)");
1066 println!(" • 解锁需要: 主密码 + 安全问题答案 + 2FA动态验证码");
1067 } else {
1068 println!("{}", "⚠️ Security Architecture:".yellow().bold());
1069 println!(" • Factor 1: Hardware Fingerprint (auto-collected, device-bound)");
1070 println!(" • Factor 2: Master Password (your strong password)");
1071 println!(" • Factor 3: Security Question Answer (prevents password leak)");
1072 println!(" • 2FA Key: Derived from hardware fingerprint + master password");
1073 println!(" • Unlock requires: Master password + Security answer + 2FA code");
1074 }
1075 println!();
1076
1077 if language == Language::Chinese {
1079 println!("{}", "步骤 1/4: 收集硬件指纹...".bright_blue());
1080 } else {
1081 println!("{}", "Step 1/4: Collecting hardware fingerprint...".bright_blue());
1082 }
1083
1084 let hardware_fp = HardwareFingerprint::collect()
1085 .map_err(|e| format!("Failed to collect hardware fingerprint: {}", e))?;
1086
1087 if language == Language::Chinese {
1088 println!("{} 硬件指纹已收集(SHA256哈希)", "✅".green());
1089 println!(" 指纹预览: {}...", &hardware_fp.as_str()[..16]);
1090 } else {
1091 println!("{} Hardware fingerprint collected (SHA256 hash)", "✅".green());
1092 println!(" Preview: {}...", &hardware_fp.as_str()[..16]);
1093 }
1094 println!();
1095
1096 if language == Language::Chinese {
1098 println!("{}", "步骤 2/4: 设置主密码".bright_blue());
1099 } else {
1100 println!("{}", "Step 2/4: Set master password".bright_blue());
1101 }
1102
1103 let master_password = loop {
1104 let password = rpassword::prompt_password(
1105 if language == Language::Chinese { "请输入主密码: " } else { "Enter master password: " }
1106 ).map_err(|e| format!("Failed to read password: {}", e))?;
1107
1108 if password.is_empty() {
1109 println!("{} {}", "❌".red(), if language == Language::Chinese { "主密码不能为空" } else { "Master password cannot be empty" });
1110 continue;
1111 }
1112
1113 if password.len() < 10 {
1115 println!("{} {}", "❌".red(), if language == Language::Chinese { "密码长度至少10个字符" } else { "Password must be at least 10 characters" });
1116 continue;
1117 }
1118
1119 let password_confirm = rpassword::prompt_password(
1120 if language == Language::Chinese { "请再次输入主密码确认: " } else { "Confirm master password: " }
1121 ).map_err(|e| format!("Failed to read password: {}", e))?;
1122
1123 if password != password_confirm {
1124 println!("{} {}", "❌".red(), if language == Language::Chinese { "两次输入的密码不一致" } else { "Passwords do not match" });
1125 continue;
1126 }
1127
1128 break password;
1129 };
1130
1131 if language == Language::Chinese {
1132 println!("{} 主密码设置成功", "✅".green());
1133 } else {
1134 println!("{} Master password set successfully", "✅".green());
1135 }
1136 println!();
1137
1138 if language == Language::Chinese {
1140 println!("{}", "步骤 3/4: 设置安全问题".bright_blue());
1141 } else {
1142 println!("{}", "Step 3/4: Set security question".bright_blue());
1143 }
1144
1145 let (question_index, _security_answer) = SecurityQuestion::setup_interactive()
1146 .map_err(|e| format!("Failed to setup security question: {}", e))?;
1147 println!();
1148
1149 if language == Language::Chinese {
1151 println!("{}", "步骤 4/4: 设置 2FA 动态验证码".bright_blue());
1152 } else {
1153 println!("{}", "Step 4/4: Setup 2FA TOTP".bright_blue());
1154 }
1155
1156 let twofa_secret = derive_totp_secret_from_hardware_and_password(
1157 hardware_fp.as_str(),
1158 &master_password,
1159 account,
1160 issuer,
1161 ).map_err(|e| format!("Failed to derive 2FA secret: {}", e))?;
1162
1163 let config = TOTPConfig {
1164 secret: twofa_secret.clone(),
1165 account: account.to_string(),
1166 issuer: issuer.to_string(),
1167 algorithm: "SHA1".to_string(),
1168 digits: 6,
1169 step: 30,
1170 };
1171
1172 let totp_manager = TOTPManager::new(config);
1173
1174 if language == Language::Chinese {
1175 println!("{}", "📱 请使用 Google Authenticator 或 Authy 扫描以下 QR 码:".yellow());
1176 } else {
1177 println!("{}", "📱 Scan this QR code with Google Authenticator or Authy:".yellow());
1178 }
1179 println!();
1180
1181 match totp_manager.generate_qr_code() {
1182 Ok(qr_code) => {
1183 println!("{}", qr_code);
1184 }
1185 Err(e) => {
1186 if language == Language::Chinese {
1187 eprintln!("{} QR 码生成失败: {}", "⚠️".yellow(), e);
1188 println!("{}", "📝 请手动输入以下信息:".yellow());
1189 } else {
1190 eprintln!("{} QR code generation failed: {}", "⚠️".yellow(), e);
1191 println!("{}", "📝 Please enter this info manually:".yellow());
1192 }
1193 println!("{}", totp_manager.get_manual_setup_info());
1194 }
1195 }
1196
1197 println!();
1198 if language == Language::Chinese {
1199 println!("{} 或者手动输入密钥: {}", "🔑".bright_cyan(), twofa_secret.bright_white());
1200 } else {
1201 println!("{} Or enter manually: {}", "🔑".bright_cyan(), twofa_secret.bright_white());
1202 }
1203 println!();
1204
1205 loop {
1207 print!("{}", if language == Language::Chinese {
1208 "请输入认证器显示的 6 位验证码以确认设置: "
1209 } else {
1210 "Enter the 6-digit code from your authenticator to verify: "
1211 });
1212 io::stdout().flush().map_err(|e| e.to_string())?;
1213
1214 let mut input = String::new();
1215 io::stdin().read_line(&mut input).map_err(|e| e.to_string())?;
1216 let code = input.trim();
1217
1218 match totp_manager.verify_code(code) {
1219 Ok(true) => {
1220 println!("{}", if language == Language::Chinese {
1221 "✅ 2FA 验证成功!".green()
1222 } else {
1223 "✅ 2FA verification successful!".green()
1224 });
1225 break;
1226 }
1227 Ok(false) => {
1228 println!("{}", if language == Language::Chinese {
1229 "❌ 验证码不正确,请重试".red()
1230 } else {
1231 "❌ Code incorrect, please try again".red()
1232 });
1233 continue;
1234 }
1235 Err(e) => {
1236 eprintln!("{} {}: {}", "❌".red(), if language == Language::Chinese { "验证失败" } else { "Verification failed" }, e);
1237 continue;
1238 }
1239 }
1240 }
1241
1242 println!();
1243 if language == Language::Chinese {
1244 println!("{}", "🎉 三因子 2FA 设置完成!".green().bold());
1245 println!();
1246 println!("{}", "📝 重要信息(请妥善保管):".yellow().bold());
1247 println!(" • 硬件指纹: 已绑定到当前设备");
1248 println!(" • 安全问题: 问题 {} - {}", question_index + 1, crate::security_question::SECURITY_QUESTIONS[question_index]);
1249 println!(" • 2FA密钥: 已添加到认证器");
1250 println!();
1251 println!("{}", "💡 下一步: 使用选项5生成三因子钱包".bright_blue());
1252 } else {
1253 println!("{}", "🎉 Triple-factor 2FA setup complete!".green().bold());
1254 println!();
1255 println!("{}", "📝 Important info (keep safe):".yellow().bold());
1256 println!(" • Hardware fingerprint: Bound to current device");
1257 println!(" • Security question: Question {} - {}", question_index + 1, crate::security_question::SECURITY_QUESTIONS[question_index]);
1258 println!(" • 2FA key: Added to authenticator");
1259 println!();
1260 println!("{}", "💡 Next step: Use option 5 to generate triple-factor wallet".bright_blue());
1261 }
1262
1263 Ok(())
1264}
1265
1266#[cfg(feature = "2fa")]
1268fn generate_triple_factor_wallet_interactive(_language: Language) -> Result<(), String> {
1269 Err("This feature will be implemented soon. Please use CLI command: sol-safekey gen-2fa-wallet".to_string())
1270}
1271
1272#[cfg(feature = "2fa")]
1274fn unlock_triple_factor_wallet_interactive(_language: Language) -> Result<(), String> {
1275 Err("This feature will be implemented soon. Please use CLI command: sol-safekey unlock-2fa-wallet -f <file>".to_string())
1276}
1277
1278fn read_password_confirmed(texts: &Texts) -> Result<String, String> {
1280 println!("{}", texts.set_password);
1281
1282 let password = prompt_password(texts.new_password, texts)?;
1283
1284 if password.is_empty() {
1285 return Err(texts.password_empty.to_string());
1286 }
1287
1288 if password.len() < 10 {
1289 return Err(texts.password_min_length.to_string());
1290 }
1291
1292 let password_confirm = prompt_password(texts.confirm_password, texts)?;
1293
1294 if password != password_confirm {
1295 return Err(texts.password_mismatch.to_string());
1296 }
1297
1298 println!("{}", texts.password_set.green());
1299 Ok(password)
1300}