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