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 { "Pump.fun 卖出代币" } else { "Pump.fun Sell Tokens" });
384 #[cfg(feature = "sol-trade-sdk")]
385 println!(" {} {}", "16.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 卖出代币" } else { "PumpSwap Sell Tokens" });
386 #[cfg(feature = "sol-trade-sdk")]
387 println!(" {} {}", "17.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 返现(查看与领取)" } else { "Pump.fun Cashback (View & Claim)" });
388 #[cfg(feature = "sol-trade-sdk")]
389 println!(" {} {}", "18.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 返现(查看与领取)" } else { "PumpSwap Cashback (View & Claim)" });
390 }
391 #[cfg(not(feature = "2fa"))]
392 {
393 println!(" {} {}", "4.".bright_cyan().bold(), if lang == Language::Chinese { "查询 SOL 余额" } else { "Check SOL Balance" });
394 println!(" {} {}", "5.".bright_cyan().bold(), if lang == Language::Chinese { "转账 SOL" } else { "Transfer SOL" });
395 println!(" {} {}", "6.".bright_cyan().bold(), if lang == Language::Chinese { "创建 WSOL ATA" } else { "Create WSOL ATA" });
396 println!(" {} {}", "7.".bright_cyan().bold(), if lang == Language::Chinese { "包装 SOL → WSOL" } else { "Wrap SOL → WSOL" });
397 println!(" {} {}", "8.".bright_cyan().bold(), if lang == Language::Chinese { "解包 WSOL → SOL" } else { "Unwrap WSOL → SOL" });
398 println!(" {} {}", "9.".bright_cyan().bold(), if lang == Language::Chinese { "关闭 WSOL ATA" } else { "Close WSOL ATA" });
399 println!(" {} {}", "10.".bright_cyan().bold(), if lang == Language::Chinese { "转账 SPL 代币" } else { "Transfer SPL Token" });
400 println!(" {} {}", "11.".bright_cyan().bold(), if lang == Language::Chinese { "创建 Nonce 账户" } else { "Create Nonce Account" });
401
402 #[cfg(feature = "sol-trade-sdk")]
403 println!(" {} {}", "12.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 卖出代币" } else { "Pump.fun Sell Tokens" });
404 #[cfg(feature = "sol-trade-sdk")]
405 println!(" {} {}", "13.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 卖出代币" } else { "PumpSwap Sell Tokens" });
406 #[cfg(feature = "sol-trade-sdk")]
407 println!(" {} {}", "14.".bright_magenta().bold(), if lang == Language::Chinese { "Pump.fun 返现(查看与领取)" } else { "Pump.fun Cashback (View & Claim)" });
408 #[cfg(feature = "sol-trade-sdk")]
409 println!(" {} {}", "15.".bright_magenta().bold(), if lang == Language::Chinese { "PumpSwap 返现(查看与领取)" } else { "PumpSwap Cashback (View & Claim)" });
410 }
411 }
412
413 println!();
414 println!(" {} {}", "0.".red().bold(), &texts.exit[6..]);
415 println!();
416 print!("{}", texts.select_option);
417 io::stdout().flush().map_err(|e| e.to_string())?;
418
419 let mut choice = String::new();
420 io::stdin().read_line(&mut choice).map_err(|e| e.to_string())?;
421 let choice = choice.trim();
422
423 match choice.to_lowercase().as_str() {
424 "1" => create_plain_key_interactive(&texts)?,
425 "2" => create_encrypted_key_interactive(&texts)?,
426 "3" => decrypt_key_interactive(&texts)?,
427
428 "u" => {
430 if session.is_unlocked() {
431 if lang == Language::Chinese {
432 println!("\n✅ 钱包已经解锁!");
433 } else {
434 println!("\n✅ Wallet already unlocked!");
435 }
436 } else {
437 if let Err(e) = unlock_wallet_interactive(&mut session, lang) {
438 eprintln!("❌ {}", e);
439 }
440 }
441 continue;
443 }
444 "l" => {
445 if session.is_unlocked() {
446 session.lock();
447 if lang == Language::Chinese {
448 println!("\n🔒 钱包已锁定");
449 } else {
450 println!("\n🔒 Wallet locked");
451 }
452 } else {
453 if lang == Language::Chinese {
454 println!("\n⚠️ 钱包未解锁");
455 } else {
456 println!("\n⚠️ Wallet not unlocked");
457 }
458 }
459 continue;
460 }
461
462 #[cfg(feature = "2fa")]
464 "4" => {
465 if let Err(e) = setup_2fa_interactive(lang) {
466 eprintln!("❌ {}", e);
467 }
468 }
469 #[cfg(feature = "2fa")]
470 "5" => {
471 if let Err(e) = generate_triple_factor_wallet_interactive(lang) {
472 eprintln!("❌ {}", e);
473 }
474 }
475 #[cfg(feature = "2fa")]
476 "6" => {
477 if let Err(e) = unlock_triple_factor_wallet_interactive(lang) {
478 eprintln!("❌ {}", e);
479 }
480 }
481
482 #[cfg(all(feature = "solana-ops", feature = "2fa"))]
484 "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" => {
485 if let Err(e) = handle_solana_operation(choice, lang, &mut session) {
486 eprintln!("❌ {}", e);
487 }
488 }
489 #[cfg(all(feature = "solana-ops", not(feature = "2fa")))]
490 "4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" => {
491 if let Err(e) = handle_solana_operation(choice, lang, &mut session) {
492 eprintln!("❌ {}", e);
493 }
494 }
495
496 "0" => {
497 println!("\n{}", texts.goodbye.cyan());
498 break;
499 }
500 _ => {
501 println!("\n{}", texts.invalid_option.red());
502 continue;
503 }
504 }
505
506 println!();
508 print!("{}", texts.continue_use);
509 io::stdout().flush().map_err(|e| e.to_string())?;
510
511 let mut continue_choice = String::new();
512 io::stdin().read_line(&mut continue_choice).map_err(|e| e.to_string())?;
513 let continue_choice = continue_choice.trim().to_lowercase();
514
515 if continue_choice == "n" || continue_choice == "no" {
516 println!("\n{}", texts.goodbye.cyan());
517 break;
518 }
519 }
520
521 Ok(())
522}
523
524fn create_plain_key_interactive(texts: &Texts) -> Result<(), String> {
526 println!("\n{}", "=".repeat(50).yellow());
527 println!("{}", texts.create_plain_title.yellow().bold());
528 println!("{}", "=".repeat(50).yellow());
529 println!();
530
531 let keypair = KeyManager::generate_keypair();
533 let pubkey = keypair.pubkey();
534 let private_key = keypair.to_base58_string();
535
536 println!("{}", texts.keypair_generated.green().bold());
537 println!();
538 println!("{} {}", texts.public_key.cyan(), pubkey.to_string().white().bold());
539 println!("{} {}", texts.private_key.red().bold(), private_key);
540 println!();
541
542 println!("{}",texts.output_method);
544 println!("{}", texts.display_only);
545 println!("{}", texts.save_to_file);
546 println!();
547 print!("{}", texts.select);
548 io::stdout().flush().map_err(|e| e.to_string())?;
549
550 let mut output_choice = String::new();
551 io::stdin().read_line(&mut output_choice).map_err(|e| e.to_string())?;
552 let output_choice = output_choice.trim();
553
554 if output_choice == "2" {
555 print!("{}", texts.file_path);
556 io::stdout().flush().map_err(|e| e.to_string())?;
557
558 let mut file_path = String::new();
559 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
560 let file_path = file_path.trim();
561 let file_path = if file_path.is_empty() {
562 "keypair.json"
563 } else {
564 file_path
565 };
566
567 let bytes = keypair.to_bytes();
569 let json = serde_json::to_string(&bytes.to_vec())
570 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
571
572 std::fs::write(file_path, json)
573 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
574
575 println!();
576 println!("{}", texts.file_saved.green());
577 println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
578 }
579
580 println!();
581 println!("{}", texts.security_warning.yellow().bold());
582 println!("{}", texts.plaintext_warning);
583 println!("{}", texts.save_securely);
584 println!("{}", texts.dont_share);
585 println!("{}", texts.recommend_encrypted);
586
587 Ok(())
588}
589
590fn create_encrypted_key_interactive(texts: &Texts) -> Result<(), String> {
592 println!("\n{}", "=".repeat(50).yellow());
593 println!("{}", texts.create_encrypted_title.yellow().bold());
594 println!("{}", "=".repeat(50).yellow());
595 println!();
596
597 println!("{}", texts.choose_method);
599 println!("{}", texts.generate_new);
600 println!("{}", texts.import_existing);
601 println!();
602 print!("{}", texts.select);
603 io::stdout().flush().map_err(|e| e.to_string())?;
604
605 let mut source_choice = String::new();
606 io::stdin().read_line(&mut source_choice).map_err(|e| e.to_string())?;
607 let source_choice = source_choice.trim();
608
609 let keypair = match source_choice {
610 "1" => {
611 println!();
613 println!("{}", texts.generating.cyan());
614 KeyManager::generate_keypair()
615 }
616 "2" => {
617 println!();
619 print!("{}", texts.enter_private_key);
620 io::stdout().flush().map_err(|e| e.to_string())?;
621
622 let mut private_key = String::new();
623 io::stdin().read_line(&mut private_key).map_err(|e| e.to_string())?;
624 let private_key = private_key.trim();
625
626 if private_key.is_empty() {
627 return Err(texts.private_key_empty.to_string());
628 }
629
630 Keypair::from_base58_string(private_key)
631 }
632 _ => {
633 return Err(texts.invalid_choice.to_string());
634 }
635 };
636
637 let pubkey = keypair.pubkey();
638
639 println!();
640 println!("{}", texts.keypair_ready.green());
641 println!("{} {}", texts.public_key.cyan(), pubkey);
642 println!();
643
644 let password = read_password_confirmed(texts)?;
646
647 println!();
649 println!("{}", texts.output_method);
650 println!("{}", texts.keystore_recommended);
651 println!("{}", texts.show_encrypted_string);
652 println!();
653 print!("{}", texts.select);
654 io::stdout().flush().map_err(|e| e.to_string())?;
655
656 let mut output_choice = String::new();
657 io::stdin().read_line(&mut output_choice).map_err(|e| e.to_string())?;
658 let output_choice = output_choice.trim();
659
660 match output_choice {
661 "1" => {
662 print!("{}", texts.file_path.replace("keypair", "wallet"));
664 io::stdout().flush().map_err(|e| e.to_string())?;
665
666 let mut file_path = String::new();
667 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
668 let file_path = file_path.trim();
669 let file_path = if file_path.is_empty() {
670 "wallet.json"
671 } else {
672 file_path
673 };
674
675 let keystore_json = KeyManager::keypair_to_encrypted_json(&keypair, &password)?;
676 std::fs::write(file_path, keystore_json)
677 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
678
679 println!();
680 println!("{}", "=".repeat(50).green());
681 println!("{}", texts.keystore_created.green().bold());
682 println!("{}", "=".repeat(50).green());
683 println!();
684 println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
685 println!("{} {}", texts.public_key.cyan(), pubkey);
686 println!("{}", texts.private_key_encrypted.green());
687 println!();
688 println!("{}", texts.important_note.yellow().bold());
689 println!("{}", texts.keep_safe);
690 println!("{}", texts.lost_password_warning);
691 println!("{}", texts.backup_recommended);
692 }
693 "2" => {
694 let private_key = keypair.to_base58_string();
696 let encrypted = KeyManager::encrypt_with_password(&private_key, &password)?;
697
698 println!();
699 println!("{}", texts.keypair_ready.green().bold());
700 println!();
701 println!("{} {}", texts.public_key.cyan(), pubkey);
702 println!("{}", texts.encrypted_private_key.cyan());
703 println!("{}", encrypted);
704 println!();
705 println!("{}", texts.keep_safe_both.yellow());
706 }
707 _ => {
708 return Err(texts.invalid_choice.to_string());
709 }
710 }
711
712 Ok(())
713}
714
715fn decrypt_key_interactive(texts: &Texts) -> Result<(), String> {
717 println!("\n{}", "=".repeat(50).yellow());
718 println!("{}", texts.decrypt_title.yellow().bold());
719 println!("{}", "=".repeat(50).yellow());
720 println!();
721
722 println!("{}", texts.input_method);
724 println!("{}", texts.from_keystore);
725 println!("{}", texts.from_encrypted_string);
726 println!();
727 print!("{}", texts.select);
728 io::stdout().flush().map_err(|e| e.to_string())?;
729
730 let mut input_choice = String::new();
731 io::stdin().read_line(&mut input_choice).map_err(|e| e.to_string())?;
732 let input_choice = input_choice.trim();
733
734 let (private_key, pubkey) = match input_choice {
735 "1" => {
736 print!("{}", texts.file_path.trim_end_matches("(默认: keypair.json): ").trim_end_matches("(default: keypair.json): "));
738 io::stdout().flush().map_err(|e| e.to_string())?;
739
740 let mut file_path = String::new();
741 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
742 let file_path = file_path.trim();
743
744 if !std::path::Path::new(file_path).exists() {
745 return Err(format!("{}", texts.file_not_exist.replace("{}", file_path)));
746 }
747
748 println!();
749 let password = prompt_password(texts.enter_password, texts)?;
750
751 let keystore_json = std::fs::read_to_string(file_path)
752 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
753
754 let keypair = KeyManager::keypair_from_encrypted_json(&keystore_json, &password)?;
755 let pubkey = keypair.pubkey();
756 let private_key = keypair.to_base58_string();
757
758 (private_key, pubkey)
759 }
760 "2" => {
761 print!("{}", texts.encrypted_key);
763 io::stdout().flush().map_err(|e| e.to_string())?;
764
765 let mut encrypted = String::new();
766 io::stdin().read_line(&mut encrypted).map_err(|e| e.to_string())?;
767 let encrypted = encrypted.trim();
768
769 println!();
770 let password = prompt_password(texts.enter_password, texts)?;
771
772 let private_key = KeyManager::decrypt_with_password(encrypted, &password)?;
773 let keypair = Keypair::from_base58_string(&private_key);
774 let pubkey = keypair.pubkey();
775
776 (private_key, pubkey)
777 }
778 _ => {
779 return Err(texts.invalid_choice.to_string());
780 }
781 };
782
783 println!();
784 println!("{}", "=".repeat(50).green());
785 println!("{}", texts.decrypt_success.green().bold());
786 println!("{}", "=".repeat(50).green());
787 println!();
788 println!("{} {}", texts.public_key.cyan(), pubkey);
789 println!("{} {}", texts.private_key.red().bold(), private_key);
790 println!();
791
792 println!("{}", texts.output_method);
794 println!("{}", texts.display_only);
795 println!("{}", texts.save_to_file);
796 println!();
797 print!("{}", texts.select);
798 io::stdout().flush().map_err(|e| e.to_string())?;
799
800 let mut output_choice = String::new();
801 io::stdin().read_line(&mut output_choice).map_err(|e| e.to_string())?;
802 let output_choice = output_choice.trim();
803
804 if output_choice == "2" {
805 let default_filename = if texts.file_path.contains("默认") {
806 "decrypted_key.txt"
807 } else {
808 "decrypted_key.txt"
809 };
810
811 print!("{}", texts.file_path.replace("keypair.json", default_filename));
812 io::stdout().flush().map_err(|e| e.to_string())?;
813
814 let mut file_path = String::new();
815 io::stdin().read_line(&mut file_path).map_err(|e| e.to_string())?;
816 let file_path = file_path.trim();
817 let file_path = if file_path.is_empty() {
818 default_filename
819 } else {
820 file_path
821 };
822
823 let content = format!("{} {}\n{} {}\n", texts.public_key, pubkey, texts.private_key.trim_end_matches(':'), private_key);
824 std::fs::write(file_path, content)
825 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
826
827 println!();
828 println!("{}", texts.file_saved.green());
829 println!("{} {}", texts.file_path.trim_end_matches(':'), file_path);
830 }
831
832 println!();
833 println!("{}", texts.security_warning.yellow().bold());
834 println!("{}", texts.dont_share_warning);
835 println!("{}", texts.delete_plaintext);
836 println!("{}", texts.use_encryption);
837
838 Ok(())
839}
840
841fn prompt_password(prompt: &str, texts: &Texts) -> Result<String, String> {
844 print!("{}", prompt);
845 io::stdout().flush().map_err(|e| e.to_string())?;
846
847 let mut password = String::new();
849 io::stdin().read_line(&mut password)
850 .map_err(|e| format!("{}", texts.write_failed.replace("{}", &e.to_string())))?;
851
852 let password = password.trim().to_string();
853 println!("DEBUG: 读取到的密码: '{}' (长度: {})", password, password.len());
854
855 Ok(password)
856
857 }
862
863fn unlock_wallet_interactive(session: &mut SessionState, language: Language) -> Result<(), String> {
865 use rpassword;
866
867 println!();
868 if language == Language::Chinese {
869 println!("{}", " 解锁钱包".cyan().bold());
870 print!("Keystore 文件路径 [keystore.json]: ");
871 } else {
872 println!("{}", " Unlock Wallet".cyan().bold());
873 print!("Keystore file path [keystore.json]: ");
874 }
875 io::stdout().flush().map_err(|e| e.to_string())?;
876
877 let mut keystore_path = String::new();
878 io::stdin().read_line(&mut keystore_path).map_err(|e| e.to_string())?;
879 let keystore_path = keystore_path.trim();
880 let keystore_path = if keystore_path.is_empty() {
881 "keystore.json"
882 } else {
883 keystore_path
884 };
885
886 let file_content = std::fs::read_to_string(keystore_path)
888 .map_err(|e| format!("Failed to read keystore: {}", e))?;
889
890 let json: serde_json::Value = serde_json::from_str(&file_content)
892 .map_err(|e| format!("Failed to parse keystore: {}", e))?;
893
894 let encryption_type = json["encryption_type"].as_str().unwrap_or("password_only");
895
896 let keypair = match encryption_type {
898 "password_only" => {
899 let password = rpassword::prompt_password(
901 if language == Language::Chinese { "输入密码: " } else { "Enter password: " }
902 ).map_err(|e| format!("Failed to read password: {}", e))?;
903
904 KeyManager::keypair_from_encrypted_json(&file_content, &password)
905 .map_err(|e| format!("Failed to decrypt keystore: {}", e))?
906 }
907 "triple_factor_v1" => {
908 return Err("Triple-factor wallets not yet supported in interactive mode. Please use the CLI.".to_string());
909 }
910 _ => {
911 return Err(format!("Unknown encryption type: {}", encryption_type));
912 }
913 };
914
915 session.unlock(keypair, keystore_path.to_string());
917
918 if language == Language::Chinese {
919 println!("✅ 钱包解锁成功!");
920 println!("📍 钱包地址: {}", session.get_keypair().unwrap().pubkey());
921 println!("💡 提示: 在本次会话中,Solana操作将使用此钱包,无需重复输入密码");
922 } else {
923 println!("✅ Wallet unlocked successfully!");
924 println!("📍 Wallet address: {}", session.get_keypair().unwrap().pubkey());
925 println!("💡 Tip: Solana operations in this session will use this wallet without re-entering password");
926 }
927
928 Ok(())
929}
930
931#[cfg(feature = "solana-ops")]
933fn handle_solana_operation(choice: &str, language: Language, session: &mut SessionState) -> Result<(), String> {
934 let ops_language = match language {
936 Language::English => crate::operations::Language::English,
937 Language::Chinese => crate::operations::Language::Chinese,
938 };
939
940 let keypair = if let Some(kp) = session.get_keypair() {
942 kp
943 } else {
944 if language == Language::Chinese {
946 println!("\n⚠️ 请先使用 'U' 选项解锁钱包");
947 } else {
948 println!("\n⚠️ Please unlock wallet first using 'U' option");
949 }
950 return Ok(());
951 };
952
953 if language == Language::Chinese {
954 println!("\n📍 使用钱包: {}", keypair.pubkey());
955 } else {
956 println!("\n📍 Using wallet: {}", keypair.pubkey());
957 }
958
959 #[cfg(feature = "2fa")]
961 let result = match choice {
962 "7" => crate::operations::check_balance(&keypair, ops_language),
963 "8" => crate::operations::transfer_sol(&keypair, ops_language),
964 "9" => crate::operations::create_wsol_ata(&keypair, ops_language),
965 "10" => crate::operations::wrap_sol(&keypair, ops_language),
966 "11" => crate::operations::unwrap_sol(&keypair, ops_language),
967 "12" => crate::operations::close_wsol_ata(&keypair, ops_language),
968 "13" => crate::operations::transfer_token(&keypair, ops_language),
969 "14" => crate::operations::create_nonce_account(&keypair, ops_language),
970 #[cfg(feature = "sol-trade-sdk")]
971 "15" => crate::operations::pumpfun_sell_interactive(&keypair, ops_language),
972 #[cfg(feature = "sol-trade-sdk")]
973 "16" => crate::operations::pumpswap_sell_interactive(&keypair, ops_language),
974 #[cfg(feature = "sol-trade-sdk")]
975 "17" => crate::operations::pumpfun_cashback_interactive(&keypair, ops_language),
976 #[cfg(feature = "sol-trade-sdk")]
977 "18" => crate::operations::pumpswap_cashback_interactive(&keypair, ops_language),
978 _ => Err("Invalid operation".to_string()),
979 };
980
981 #[cfg(not(feature = "2fa"))]
982 let result = match choice {
983 "4" => crate::operations::check_balance(&keypair, ops_language),
984 "5" => crate::operations::transfer_sol(&keypair, ops_language),
985 "6" => crate::operations::create_wsol_ata(&keypair, ops_language),
986 "7" => crate::operations::wrap_sol(&keypair, ops_language),
987 "8" => crate::operations::unwrap_sol(&keypair, ops_language),
988 "9" => crate::operations::close_wsol_ata(&keypair, ops_language),
989 "10" => crate::operations::transfer_token(&keypair, ops_language),
990 "11" => crate::operations::create_nonce_account(&keypair, ops_language),
991 #[cfg(feature = "sol-trade-sdk")]
992 "12" => crate::operations::pumpfun_sell_interactive(&keypair, ops_language),
993 #[cfg(feature = "sol-trade-sdk")]
994 "13" => crate::operations::pumpswap_sell_interactive(&keypair, ops_language),
995 #[cfg(feature = "sol-trade-sdk")]
996 "14" => crate::operations::pumpfun_cashback_interactive(&keypair, ops_language),
997 #[cfg(feature = "sol-trade-sdk")]
998 "15" => crate::operations::pumpswap_cashback_interactive(&keypair, ops_language),
999 _ => Err("Invalid operation".to_string()),
1000 };
1001
1002 result
1003}
1004
1005#[cfg(feature = "2fa")]
1007fn setup_2fa_interactive(language: Language) -> Result<(), String> {
1008 use crate::{derive_totp_secret_from_hardware_and_password, hardware_fingerprint::HardwareFingerprint, security_question::SecurityQuestion, totp::*};
1009 use rpassword;
1010
1011 let account = "wallet";
1012 let issuer = "Sol-SafeKey";
1013
1014 println!("\n{}", "=".repeat(50).bright_magenta());
1015 if language == Language::Chinese {
1016 println!("{}", " 🔐 三因子 2FA 安全设置".bright_magenta().bold());
1017 } else {
1018 println!("{}", " 🔐 Triple-Factor 2FA Security Setup".bright_magenta().bold());
1019 }
1020 println!("{}", "=".repeat(50).bright_magenta());
1021 println!();
1022
1023 if language == Language::Chinese {
1024 println!("{}", "⚠️ 安全架构说明:".yellow().bold());
1025 println!(" • 因子1: 硬件指纹(自动收集,绑定设备)");
1026 println!(" • 因子2: 主密码(您设置的强密码)");
1027 println!(" • 因子3: 安全问题答案(防止密码泄露)");
1028 println!(" • 2FA密钥: 从硬件指纹+主密码派生(确定性)");
1029 println!(" • 解锁需要: 主密码 + 安全问题答案 + 2FA动态验证码");
1030 } else {
1031 println!("{}", "⚠️ Security Architecture:".yellow().bold());
1032 println!(" • Factor 1: Hardware Fingerprint (auto-collected, device-bound)");
1033 println!(" • Factor 2: Master Password (your strong password)");
1034 println!(" • Factor 3: Security Question Answer (prevents password leak)");
1035 println!(" • 2FA Key: Derived from hardware fingerprint + master password");
1036 println!(" • Unlock requires: Master password + Security answer + 2FA code");
1037 }
1038 println!();
1039
1040 if language == Language::Chinese {
1042 println!("{}", "步骤 1/4: 收集硬件指纹...".bright_blue());
1043 } else {
1044 println!("{}", "Step 1/4: Collecting hardware fingerprint...".bright_blue());
1045 }
1046
1047 let hardware_fp = HardwareFingerprint::collect()
1048 .map_err(|e| format!("Failed to collect hardware fingerprint: {}", e))?;
1049
1050 if language == Language::Chinese {
1051 println!("{} 硬件指纹已收集(SHA256哈希)", "✅".green());
1052 println!(" 指纹预览: {}...", &hardware_fp.as_str()[..16]);
1053 } else {
1054 println!("{} Hardware fingerprint collected (SHA256 hash)", "✅".green());
1055 println!(" Preview: {}...", &hardware_fp.as_str()[..16]);
1056 }
1057 println!();
1058
1059 if language == Language::Chinese {
1061 println!("{}", "步骤 2/4: 设置主密码".bright_blue());
1062 } else {
1063 println!("{}", "Step 2/4: Set master password".bright_blue());
1064 }
1065
1066 let master_password = loop {
1067 let password = rpassword::prompt_password(
1068 if language == Language::Chinese { "请输入主密码: " } else { "Enter master password: " }
1069 ).map_err(|e| format!("Failed to read password: {}", e))?;
1070
1071 if password.is_empty() {
1072 println!("{} {}", "❌".red(), if language == Language::Chinese { "主密码不能为空" } else { "Master password cannot be empty" });
1073 continue;
1074 }
1075
1076 if password.len() < 10 {
1078 println!("{} {}", "❌".red(), if language == Language::Chinese { "密码长度至少10个字符" } else { "Password must be at least 10 characters" });
1079 continue;
1080 }
1081
1082 let password_confirm = rpassword::prompt_password(
1083 if language == Language::Chinese { "请再次输入主密码确认: " } else { "Confirm master password: " }
1084 ).map_err(|e| format!("Failed to read password: {}", e))?;
1085
1086 if password != password_confirm {
1087 println!("{} {}", "❌".red(), if language == Language::Chinese { "两次输入的密码不一致" } else { "Passwords do not match" });
1088 continue;
1089 }
1090
1091 break password;
1092 };
1093
1094 if language == Language::Chinese {
1095 println!("{} 主密码设置成功", "✅".green());
1096 } else {
1097 println!("{} Master password set successfully", "✅".green());
1098 }
1099 println!();
1100
1101 if language == Language::Chinese {
1103 println!("{}", "步骤 3/4: 设置安全问题".bright_blue());
1104 } else {
1105 println!("{}", "Step 3/4: Set security question".bright_blue());
1106 }
1107
1108 let (question_index, _security_answer) = SecurityQuestion::setup_interactive()
1109 .map_err(|e| format!("Failed to setup security question: {}", e))?;
1110 println!();
1111
1112 if language == Language::Chinese {
1114 println!("{}", "步骤 4/4: 设置 2FA 动态验证码".bright_blue());
1115 } else {
1116 println!("{}", "Step 4/4: Setup 2FA TOTP".bright_blue());
1117 }
1118
1119 let twofa_secret = derive_totp_secret_from_hardware_and_password(
1120 hardware_fp.as_str(),
1121 &master_password,
1122 account,
1123 issuer,
1124 ).map_err(|e| format!("Failed to derive 2FA secret: {}", e))?;
1125
1126 let config = TOTPConfig {
1127 secret: twofa_secret.clone(),
1128 account: account.to_string(),
1129 issuer: issuer.to_string(),
1130 algorithm: "SHA1".to_string(),
1131 digits: 6,
1132 step: 30,
1133 };
1134
1135 let totp_manager = TOTPManager::new(config);
1136
1137 if language == Language::Chinese {
1138 println!("{}", "📱 请使用 Google Authenticator 或 Authy 扫描以下 QR 码:".yellow());
1139 } else {
1140 println!("{}", "📱 Scan this QR code with Google Authenticator or Authy:".yellow());
1141 }
1142 println!();
1143
1144 match totp_manager.generate_qr_code() {
1145 Ok(qr_code) => {
1146 println!("{}", qr_code);
1147 }
1148 Err(e) => {
1149 if language == Language::Chinese {
1150 eprintln!("{} QR 码生成失败: {}", "⚠️".yellow(), e);
1151 println!("{}", "📝 请手动输入以下信息:".yellow());
1152 } else {
1153 eprintln!("{} QR code generation failed: {}", "⚠️".yellow(), e);
1154 println!("{}", "📝 Please enter this info manually:".yellow());
1155 }
1156 println!("{}", totp_manager.get_manual_setup_info());
1157 }
1158 }
1159
1160 println!();
1161 if language == Language::Chinese {
1162 println!("{} 或者手动输入密钥: {}", "🔑".bright_cyan(), twofa_secret.bright_white());
1163 } else {
1164 println!("{} Or enter manually: {}", "🔑".bright_cyan(), twofa_secret.bright_white());
1165 }
1166 println!();
1167
1168 loop {
1170 print!("{}", if language == Language::Chinese {
1171 "请输入认证器显示的 6 位验证码以确认设置: "
1172 } else {
1173 "Enter the 6-digit code from your authenticator to verify: "
1174 });
1175 io::stdout().flush().map_err(|e| e.to_string())?;
1176
1177 let mut input = String::new();
1178 io::stdin().read_line(&mut input).map_err(|e| e.to_string())?;
1179 let code = input.trim();
1180
1181 match totp_manager.verify_code(code) {
1182 Ok(true) => {
1183 println!("{}", if language == Language::Chinese {
1184 "✅ 2FA 验证成功!".green()
1185 } else {
1186 "✅ 2FA verification successful!".green()
1187 });
1188 break;
1189 }
1190 Ok(false) => {
1191 println!("{}", if language == Language::Chinese {
1192 "❌ 验证码不正确,请重试".red()
1193 } else {
1194 "❌ Code incorrect, please try again".red()
1195 });
1196 continue;
1197 }
1198 Err(e) => {
1199 eprintln!("{} {}: {}", "❌".red(), if language == Language::Chinese { "验证失败" } else { "Verification failed" }, e);
1200 continue;
1201 }
1202 }
1203 }
1204
1205 println!();
1206 if language == Language::Chinese {
1207 println!("{}", "🎉 三因子 2FA 设置完成!".green().bold());
1208 println!();
1209 println!("{}", "📝 重要信息(请妥善保管):".yellow().bold());
1210 println!(" • 硬件指纹: 已绑定到当前设备");
1211 println!(" • 安全问题: 问题 {} - {}", question_index + 1, crate::security_question::SECURITY_QUESTIONS[question_index]);
1212 println!(" • 2FA密钥: 已添加到认证器");
1213 println!();
1214 println!("{}", "💡 下一步: 使用选项5生成三因子钱包".bright_blue());
1215 } else {
1216 println!("{}", "🎉 Triple-factor 2FA setup complete!".green().bold());
1217 println!();
1218 println!("{}", "📝 Important info (keep safe):".yellow().bold());
1219 println!(" • Hardware fingerprint: Bound to current device");
1220 println!(" • Security question: Question {} - {}", question_index + 1, crate::security_question::SECURITY_QUESTIONS[question_index]);
1221 println!(" • 2FA key: Added to authenticator");
1222 println!();
1223 println!("{}", "💡 Next step: Use option 5 to generate triple-factor wallet".bright_blue());
1224 }
1225
1226 Ok(())
1227}
1228
1229#[cfg(feature = "2fa")]
1231fn generate_triple_factor_wallet_interactive(_language: Language) -> Result<(), String> {
1232 Err("This feature will be implemented soon. Please use CLI command: sol-safekey gen-2fa-wallet".to_string())
1233}
1234
1235#[cfg(feature = "2fa")]
1237fn unlock_triple_factor_wallet_interactive(_language: Language) -> Result<(), String> {
1238 Err("This feature will be implemented soon. Please use CLI command: sol-safekey unlock-2fa-wallet -f <file>".to_string())
1239}
1240
1241fn read_password_confirmed(texts: &Texts) -> Result<String, String> {
1243 println!("{}", texts.set_password);
1244
1245 let password = prompt_password(texts.new_password, texts)?;
1246
1247 if password.is_empty() {
1248 return Err(texts.password_empty.to_string());
1249 }
1250
1251 if password.len() < 10 {
1252 return Err(texts.password_min_length.to_string());
1253 }
1254
1255 let password_confirm = prompt_password(texts.confirm_password, texts)?;
1256
1257 if password != password_confirm {
1258 return Err(texts.password_mismatch.to_string());
1259 }
1260
1261 println!("{}", texts.password_set.green());
1262 Ok(password)
1263}