1use solana_sdk::signature::Keypair;
7#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
8use std::io::{self, Write};
9
10#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
11use colored::*;
12#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
13use solana_sdk::bs58;
14#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
15use solana_sdk::pubkey::Pubkey;
16#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
17use solana_sdk::signer::Signer;
18#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
19use std::str::FromStr;
20
21#[cfg(feature = "solana-ops")]
22use crate::solana_utils::{lamports_to_sol, SolanaClient, SolanaClientSdk};
23
24#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
25use solana_client::rpc_client::RpcClient;
26
27#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
28const DEFAULT_RPC_URL: &str = "https://api.mainnet-beta.solana.com";
29#[cfg(feature = "solana-ops")]
30const DEVNET_RPC_URL: &str = "https://api.devnet.solana.com";
31
32#[derive(Clone, Copy, PartialEq)]
34pub enum Language {
35 English,
36 Chinese,
37}
38
39#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
41fn read_input(prompt: &str, default: &str) -> String {
42 print!("{}", prompt);
43 io::stdout().flush().unwrap();
44
45 let mut input = String::new();
46 io::stdin().read_line(&mut input).unwrap();
47 let input = input.trim();
48
49 if input.is_empty() && !default.is_empty() {
50 default.to_string()
51 } else {
52 input.to_string()
53 }
54}
55
56#[cfg(feature = "solana-ops")]
58pub fn show_operations_menu(keypair: &Keypair, language: Language) -> Result<(), String> {
59 loop {
60 println!(
61 "\n{}",
62 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
63 );
64 if language == Language::English {
65 println!(
66 " {} - Solana Operations",
67 "🔧 FnzSafe".bright_yellow().bold()
68 );
69 } else {
70 println!(" {} - Solana 操作", "🔧 FnzSafe".bright_yellow().bold());
71 }
72 println!(
73 "{}",
74 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
75 );
76
77 println!(
78 "\n{}",
79 if language == Language::English {
80 "Current Wallet:"
81 } else {
82 "当前钱包:"
83 }
84 .bright_green()
85 );
86 println!(" 📍 {}", keypair.pubkey().to_string().bright_white());
87
88 println!(
89 "\n{}",
90 if language == Language::English {
91 "Available Operations:"
92 } else {
93 "可用操作:"
94 }
95 .bright_green()
96 );
97
98 if language == Language::English {
99 println!(" {} Check SOL Balance", "1.".bright_cyan());
100 println!(" {} Transfer SOL", "2.".bright_cyan());
101 println!(" {} Create WSOL ATA", "3.".bright_cyan());
102 println!(" {} Wrap SOL → WSOL", "4.".bright_cyan());
103 println!(" {} Unwrap WSOL → SOL", "5.".bright_cyan());
104 println!(" {} Transfer SPL Token", "6.".bright_cyan());
105 println!(" {} Create Nonce Account", "7.".bright_cyan());
106 println!(" {} Back to Main Menu", "0.".bright_cyan());
107 } else {
108 println!(" {} 查询 SOL 余额", "1.".bright_cyan());
109 println!(" {} 转账 SOL", "2.".bright_cyan());
110 println!(" {} 创建 WSOL ATA 账户", "3.".bright_cyan());
111 println!(" {} 包装 SOL → WSOL", "4.".bright_cyan());
112 println!(" {} 解包 WSOL → SOL", "5.".bright_cyan());
113 println!(" {} 转账 SPL 代币", "6.".bright_cyan());
114 println!(" {} 创建 Nonce 账户", "7.".bright_cyan());
115 println!(" {} 返回主菜单", "0.".bright_cyan());
116 }
117
118 let prompt = if language == Language::English {
119 "\nSelect option [0-7]: "
120 } else {
121 "\n请输入选项 [0-7]: "
122 };
123
124 let choice = read_input(prompt, "");
125
126 match choice.as_str() {
127 "1" => check_balance(keypair, language)?,
128 "2" => transfer_sol(keypair, language)?,
129 "3" => create_wsol_ata(keypair, language)?,
130 "4" => wrap_sol(keypair, language)?,
131 "5" => unwrap_sol(keypair, language)?,
132 "6" => transfer_token(keypair, language)?,
133 "7" => create_nonce_account(keypair, language)?,
134 "0" => {
135 if language == Language::English {
136 println!("\n{}", "Returning to main menu...".bright_green());
137 } else {
138 println!("\n{}", "返回主菜单...".bright_green());
139 }
140 return Ok(());
141 }
142 _ => {
143 let msg = if language == Language::English {
144 "❌ Invalid option, please try again"
145 } else {
146 "❌ 无效选项,请重试"
147 };
148 println!("\n{}", msg.red());
149 }
150 }
151 }
152}
153
154#[cfg(not(feature = "solana-ops"))]
156pub fn show_operations_menu(_keypair: &Keypair, language: Language) -> Result<(), String> {
157 let msg = if language == Language::English {
158 "❌ Solana operations require the 'solana-ops' feature. Please rebuild with: cargo build --features solana-ops"
159 } else {
160 "❌ Solana 操作需要 'solana-ops' 功能。请使用以下命令重新编译: cargo build --features solana-ops"
161 };
162 Err(msg.to_string())
163}
164
165#[cfg(feature = "solana-ops")]
166pub fn check_balance(keypair: &Keypair, language: Language) -> Result<(), String> {
167 println!(
168 "\n{}",
169 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
170 );
171 if language == Language::English {
172 println!(" {}", "📊 Check SOL Balance".bright_yellow().bold());
173 } else {
174 println!(" {}", "📊 查询 SOL 余额".bright_yellow().bold());
175 }
176 println!(
177 "{}",
178 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
179 );
180
181 let network_prompt = if language == Language::English {
182 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
183 } else {
184 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
185 };
186
187 let network = read_input(network_prompt, "1");
188 let rpc_url = if network == "2" {
189 DEVNET_RPC_URL
190 } else {
191 DEFAULT_RPC_URL
192 };
193
194 if language == Language::English {
195 println!(
196 "\n🔍 Checking balance on {}...",
197 if network == "2" { "Devnet" } else { "Mainnet" }
198 );
199 } else {
200 println!(
201 "\n🔍 正在查询{}余额...",
202 if network == "2" {
203 "测试网"
204 } else {
205 "主网"
206 }
207 );
208 }
209
210 let client = SolanaClient::new(rpc_url.to_string());
211 match client.get_sol_balance(&keypair.pubkey()) {
212 Ok(balance) => {
213 let sol = lamports_to_sol(balance);
214 println!("\n{}", "✅ Balance:".bright_green());
215 println!(" 💰 {} SOL", sol.to_string().bright_white().bold());
216 println!(" 📊 {} lamports", balance.to_string().bright_white());
217 Ok(())
218 }
219 Err(e) => {
220 let msg = if language == Language::English {
221 format!("❌ Failed to fetch balance: {}", e)
222 } else {
223 format!("❌ 查询余额失败: {}", e)
224 };
225 Err(msg)
226 }
227 }
228}
229
230#[cfg(feature = "solana-ops")]
231pub fn transfer_sol(keypair: &Keypair, language: Language) -> Result<(), String> {
232 println!(
233 "\n{}",
234 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
235 );
236 if language == Language::English {
237 println!(" {}", "💸 Transfer SOL".bright_yellow().bold());
238 } else {
239 println!(" {}", "💸 转账 SOL".bright_yellow().bold());
240 }
241 println!(
242 "{}",
243 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
244 );
245
246 let network_prompt = if language == Language::English {
247 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
248 } else {
249 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
250 };
251
252 let network = read_input(network_prompt, "1");
253 let rpc_url = if network == "2" {
254 DEVNET_RPC_URL
255 } else {
256 DEFAULT_RPC_URL
257 };
258
259 let recipient_prompt = if language == Language::English {
260 "\nRecipient address: "
261 } else {
262 "\n接收地址: "
263 };
264 let recipient_str = read_input(recipient_prompt, "");
265
266 let recipient = Pubkey::from_str(&recipient_str).map_err(|_| {
267 if language == Language::English {
268 "❌ Invalid recipient address".to_string()
269 } else {
270 "❌ 无效的接收地址".to_string()
271 }
272 })?;
273
274 let amount_prompt = if language == Language::English {
275 "Amount (SOL): "
276 } else {
277 "金额 (SOL): "
278 };
279 let amount_str = read_input(amount_prompt, "");
280 let amount_sol: f64 = amount_str.parse().map_err(|_| {
281 if language == Language::English {
282 "❌ Invalid amount".to_string()
283 } else {
284 "❌ 无效的金额".to_string()
285 }
286 })?;
287
288 let amount_lamports = (amount_sol * 1_000_000_000.0) as u64;
289
290 println!("\n{}", "📋 Transaction Summary:".bright_yellow());
291 println!(" From: {}", keypair.pubkey().to_string().bright_white());
292 println!(" To: {}", recipient.to_string().bright_white());
293 println!(
294 " Amount: {} SOL",
295 amount_sol.to_string().bright_white().bold()
296 );
297
298 let confirm_prompt = if language == Language::English {
299 "\nConfirm transaction? (yes/no) [no]: "
300 } else {
301 "\n确认交易? (yes/no) [no]: "
302 };
303 let confirm = read_input(confirm_prompt, "no");
304
305 if confirm.to_lowercase() != "yes" {
306 let msg = if language == Language::English {
307 "❌ Transaction cancelled"
308 } else {
309 "❌ 交易已取消"
310 };
311 println!("\n{}", msg.red());
312 return Ok(());
313 }
314
315 if language == Language::English {
316 println!("\n🚀 Sending transaction...");
317 } else {
318 println!("\n🚀 正在发送交易...");
319 }
320
321 let client = SolanaClient::new(rpc_url.to_string());
322 match client.transfer_sol(keypair, &recipient, amount_lamports) {
323 Ok(signature) => {
324 println!("\n{}", "✅ Transfer successful!".bright_green().bold());
325 println!(" 📝 Signature: {}", signature.to_string().bright_white());
326 let explorer_url = if network == "2" {
327 format!(
328 "https://explorer.solana.com/tx/{}?cluster=devnet",
329 signature
330 )
331 } else {
332 format!("https://explorer.solana.com/tx/{}", signature)
333 };
334 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
335 Ok(())
336 }
337 Err(e) => {
338 let msg = if language == Language::English {
339 format!("❌ Transfer failed: {}", e)
340 } else {
341 format!("❌ 转账失败: {}", e)
342 };
343 Err(msg)
344 }
345 }
346}
347
348#[cfg(feature = "solana-ops")]
350fn run_async<F, T>(future: F) -> T
351where
352 F: std::future::Future<Output = T>,
353{
354 match tokio::runtime::Handle::try_current() {
356 Ok(handle) => {
357 tokio::task::block_in_place(|| handle.block_on(future))
359 }
360 Err(_) => {
361 let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
363 rt.block_on(future)
364 }
365 }
366}
367
368#[cfg(feature = "solana-ops")]
370fn print_wsol_ata_address(owner: &Pubkey, language: Language, _use_seed: bool) {
371 use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast;
372
373 let wsol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
374 let token_program = Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap();
375
376 let ata = get_associated_token_address_with_program_id_fast(owner, &wsol_mint, &token_program);
378
379 println!(
380 "\n{}",
381 if language == Language::English {
382 "📍 WSOL ATA Address:"
383 } else {
384 "📍 WSOL ATA 地址:"
385 }
386 .bright_yellow()
387 );
388 println!(" {}", ata.to_string().bright_white().bold());
389}
390
391#[cfg(feature = "solana-ops")]
392pub fn create_wsol_ata(keypair: &Keypair, language: Language) -> Result<(), String> {
393 println!(
394 "\n{}",
395 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
396 );
397 if language == Language::English {
398 println!(" {}", "🏦 Create WSOL ATA Account".bright_yellow().bold());
399 } else {
400 println!(" {}", "🏦 创建 WSOL ATA 账户".bright_yellow().bold());
401 }
402 println!(
403 "{}",
404 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
405 );
406
407 let network_prompt = if language == Language::English {
408 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
409 } else {
410 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
411 };
412
413 let network = read_input(network_prompt, "1");
414 let rpc_url = if network == "2" {
415 DEVNET_RPC_URL
416 } else {
417 DEFAULT_RPC_URL
418 };
419
420 let use_seed_optimize = false;
422
423 print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
425
426 if language == Language::English {
428 println!("\n🔍 Checking if account already exists...");
429 } else {
430 println!("\n🔍 检查账号是否已存在...");
431 }
432
433 let client_sdk = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
434 let wsol_balance = client_sdk.get_wsol_balance(&keypair.pubkey()).unwrap_or(0);
435
436 let rpc_client = RpcClient::new(rpc_url.to_string());
438 let wsol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
439 let token_program = Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap();
440
441 let ata = {
442 use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast;
443 get_associated_token_address_with_program_id_fast(
444 &keypair.pubkey(),
445 &wsol_mint,
446 &token_program,
447 )
448 };
449
450 let account_exists = rpc_client.get_account(&ata).is_ok();
451
452 if account_exists {
453 println!(
454 "\n{}",
455 "ℹ️ WSOL ATA account already exists!"
456 .bright_yellow()
457 .bold()
458 );
459 println!(
460 " 💰 Balance: {} lamports ({} SOL)",
461 wsol_balance.to_string().bright_white(),
462 lamports_to_sol(wsol_balance).to_string().bright_white()
463 );
464
465 if language == Language::English {
466 println!("\n✅ Account is ready to use. No need to create.");
467 } else {
468 println!("\n✅ 账号已准备就绪,无需创建。");
469 }
470 return Ok(());
471 }
472
473 println!(
474 "\n{}",
475 if language == Language::English {
476 "ℹ️ Account does not exist. Creating new WSOL ATA..."
477 } else {
478 "ℹ️ 账号不存在,将创建新的 WSOL ATA..."
479 }
480 .bright_yellow()
481 );
482
483 let confirm_prompt = if language == Language::English {
484 "\nConfirm operation? (yes/no) [no]: "
485 } else {
486 "\n确认操作? (yes/no) [no]: "
487 };
488 let confirm = read_input(confirm_prompt, "no");
489
490 if confirm.to_lowercase() != "yes" {
491 let msg = if language == Language::English {
492 "❌ Operation cancelled"
493 } else {
494 "❌ 操作已取消"
495 };
496 println!("\n{}", msg.red());
497 return Ok(());
498 }
499
500 if language == Language::English {
501 println!("\n🚀 Creating WSOL ATA...");
502 } else {
503 println!("\n🚀 正在创建 WSOL ATA...");
504 }
505
506 match run_async(client_sdk.create_wsol_ata(keypair)) {
508 Ok(signature) => {
509 println!(
510 "\n{}",
511 "✅ WSOL ATA created successfully!".bright_green().bold()
512 );
513 println!(" 📝 Signature: {}", signature.to_string().bright_white());
514 let explorer_url = if network == "2" {
515 format!(
516 "https://explorer.solana.com/tx/{}?cluster=devnet",
517 signature
518 )
519 } else {
520 format!("https://explorer.solana.com/tx/{}", signature)
521 };
522 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
523 Ok(())
524 }
525 Err(e) => {
526 let msg = if language == Language::English {
527 format!("❌ Creation failed: {}", e)
528 } else {
529 format!("❌ 创建失败: {}", e)
530 };
531 Err(msg)
532 }
533 }
534}
535
536#[cfg(feature = "solana-ops")]
537pub fn wrap_sol(keypair: &Keypair, language: Language) -> Result<(), String> {
538 println!(
539 "\n{}",
540 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
541 );
542 if language == Language::English {
543 println!(" {}", "🔄 Wrap SOL → WSOL".bright_yellow().bold());
544 } else {
545 println!(" {}", "🔄 包装 SOL → WSOL".bright_yellow().bold());
546 }
547 println!(
548 "{}",
549 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
550 );
551
552 let network_prompt = if language == Language::English {
553 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
554 } else {
555 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
556 };
557
558 let network = read_input(network_prompt, "1");
559 let rpc_url = if network == "2" {
560 DEVNET_RPC_URL
561 } else {
562 DEFAULT_RPC_URL
563 };
564
565 let use_seed_optimize = false;
567
568 print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
570
571 let amount_prompt = if language == Language::English {
572 "\nAmount to wrap (SOL): "
573 } else {
574 "\n包装金额 (SOL): "
575 };
576 let amount_str = read_input(amount_prompt, "");
577 let amount_sol: f64 = amount_str.parse().map_err(|_| {
578 if language == Language::English {
579 "❌ Invalid amount".to_string()
580 } else {
581 "❌ 无效的金额".to_string()
582 }
583 })?;
584
585 let amount_lamports = (amount_sol * 1_000_000_000.0) as u64;
586
587 println!("\n{}", "📋 Operation Summary:".bright_yellow());
588 println!(
589 " Wrap: {} SOL → WSOL",
590 amount_sol.to_string().bright_white().bold()
591 );
592
593 let confirm_prompt = if language == Language::English {
594 "\nConfirm operation? (yes/no) [no]: "
595 } else {
596 "\n确认操作? (yes/no) [no]: "
597 };
598 let confirm = read_input(confirm_prompt, "no");
599
600 if confirm.to_lowercase() != "yes" {
601 let msg = if language == Language::English {
602 "❌ Operation cancelled"
603 } else {
604 "❌ 操作已取消"
605 };
606 println!("\n{}", msg.red());
607 return Ok(());
608 }
609
610 if language == Language::English {
611 println!("\n🚀 Wrapping SOL...");
612 } else {
613 println!("\n🚀 正在包装 SOL...");
614 }
615
616 let client = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
618
619 match run_async(client.wrap_sol(keypair, amount_lamports)) {
621 Ok(signature) => {
622 println!("\n{}", "✅ Wrap successful!".bright_green().bold());
623 println!(" 📝 Signature: {}", signature.to_string().bright_white());
624 let explorer_url = if network == "2" {
625 format!(
626 "https://explorer.solana.com/tx/{}?cluster=devnet",
627 signature
628 )
629 } else {
630 format!("https://explorer.solana.com/tx/{}", signature)
631 };
632 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
633 Ok(())
634 }
635 Err(e) => {
636 let msg = if language == Language::English {
637 format!("❌ Wrap failed: {}", e)
638 } else {
639 format!("❌ 包装失败: {}", e)
640 };
641 Err(msg)
642 }
643 }
644}
645
646#[cfg(feature = "solana-ops")]
647pub fn unwrap_sol(keypair: &Keypair, language: Language) -> Result<(), String> {
648 println!(
649 "\n{}",
650 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
651 );
652 if language == Language::English {
653 println!(" {}", "🔄 Unwrap WSOL → SOL".bright_yellow().bold());
654 } else {
655 println!(" {}", "🔄 解包 WSOL → SOL".bright_yellow().bold());
656 }
657 println!(
658 "{}",
659 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
660 );
661
662 let network_prompt = if language == Language::English {
663 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
664 } else {
665 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
666 };
667
668 let network = read_input(network_prompt, "1");
669 let rpc_url = if network == "2" {
670 DEVNET_RPC_URL
671 } else {
672 DEFAULT_RPC_URL
673 };
674
675 let use_seed_optimize = false;
677
678 print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
680
681 let amount_prompt = if language == Language::English {
683 "\nUnwrap amount (in SOL, leave empty for ALL): "
684 } else {
685 "\n解包金额(单位: SOL,留空则解包全部): "
686 };
687 let amount_input = read_input(amount_prompt, "");
688 let amount_input = amount_input.trim();
689
690 let (is_partial, amount_lamports) = if amount_input.is_empty() {
691 println!(
693 "\n{}",
694 if language == Language::English {
695 "ℹ️ Will unwrap ALL WSOL back to SOL"
696 } else {
697 "ℹ️ 将解包所有 WSOL 回 SOL"
698 }
699 .bright_yellow()
700 );
701 (false, 0)
702 } else {
703 let amount_sol: f64 = amount_input.parse().map_err(|_| {
705 if language == Language::English {
706 "❌ Invalid amount".to_string()
707 } else {
708 "❌ 无效的金额".to_string()
709 }
710 })?;
711
712 if amount_sol <= 0.0 {
713 return Err(if language == Language::English {
714 "❌ Amount must be greater than 0".to_string()
715 } else {
716 "❌ 金额必须大于 0".to_string()
717 });
718 }
719
720 let lamports = (amount_sol * 1_000_000_000.0) as u64;
721 println!(
722 "\n{}",
723 if language == Language::English {
724 format!("ℹ️ Will unwrap {} SOL from WSOL", amount_sol)
725 } else {
726 format!("ℹ️ 将从 WSOL 解包 {} SOL", amount_sol)
727 }
728 .bright_yellow()
729 );
730 (true, lamports)
731 };
732
733 let confirm_prompt = if language == Language::English {
734 "\nConfirm operation? (yes/no) [no]: "
735 } else {
736 "\n确认操作? (yes/no) [no]: "
737 };
738 let confirm = read_input(confirm_prompt, "no");
739
740 if confirm.to_lowercase() != "yes" {
741 let msg = if language == Language::English {
742 "❌ Operation cancelled"
743 } else {
744 "❌ 操作已取消"
745 };
746 println!("\n{}", msg.red());
747 return Ok(());
748 }
749
750 if language == Language::English {
751 println!("\n🚀 Unwrapping WSOL...");
752 } else {
753 println!("\n🚀 正在解包 WSOL...");
754 }
755
756 let client = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
758
759 let result = if is_partial {
761 run_async(client.unwrap_sol_partial(keypair, amount_lamports))
762 } else {
763 run_async(client.unwrap_sol(keypair))
764 };
765
766 match result {
767 Ok(signature) => {
768 println!("\n{}", "✅ Unwrap successful!".bright_green().bold());
769 println!(" 📝 Signature: {}", signature.to_string().bright_white());
770 let explorer_url = if network == "2" {
771 format!(
772 "https://explorer.solana.com/tx/{}?cluster=devnet",
773 signature
774 )
775 } else {
776 format!("https://explorer.solana.com/tx/{}", signature)
777 };
778 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
779 Ok(())
780 }
781 Err(e) => {
782 let msg = if language == Language::English {
783 format!("❌ Unwrap failed: {}", e)
784 } else {
785 format!("❌ 解包失败: {}", e)
786 };
787 Err(msg)
788 }
789 }
790}
791
792#[cfg(feature = "solana-ops")]
793pub fn close_wsol_ata(keypair: &Keypair, language: Language) -> Result<(), String> {
795 println!(
796 "\n{}",
797 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
798 );
799 if language == Language::English {
800 println!(" {}", "🗑️ Close WSOL ATA Account".bright_yellow().bold());
801 } else {
802 println!(" {}", "🗑️ 关闭 WSOL ATA 账号".bright_yellow().bold());
803 }
804 println!(
805 "{}",
806 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
807 );
808
809 let network_prompt = if language == Language::English {
810 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
811 } else {
812 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
813 };
814
815 let network = read_input(network_prompt, "1");
816 let rpc_url = if network == "2" {
817 DEVNET_RPC_URL
818 } else {
819 DEFAULT_RPC_URL
820 };
821
822 let use_seed_optimize = false;
824
825 print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
827
828 println!(
829 "\n{}",
830 if language == Language::English {
831 "ℹ️ This operation will:"
832 } else {
833 "ℹ️ 此操作将:"
834 }
835 .bright_yellow()
836 .bold()
837 );
838
839 if language == Language::English {
840 println!(" • Unwrap ALL WSOL back to SOL automatically");
841 println!(" • Close the WSOL ATA account");
842 println!(" • Reclaim rent (~0.00203928 SOL) to your wallet");
843 } else {
844 println!(" • 自动将所有 WSOL 解包回 SOL");
845 println!(" • 关闭 WSOL ATA 账号");
846 println!(" • 回收租金 (~0.00203928 SOL) 到您的钱包");
847 }
848
849 println!(
850 "\n{}",
851 if language == Language::English {
852 "💰 All SOL (unwrapped WSOL + rent) will be returned to your wallet!"
853 } else {
854 "�� 所有 SOL(解包的 WSOL + 租金)将返回到您的钱包!"
855 }
856 .green()
857 .bold()
858 );
859
860 let confirm_prompt = if language == Language::English {
861 "\nConfirm operation? (yes/no) [no]: "
862 } else {
863 "\n确认操作? (yes/no) [no]: "
864 };
865 let confirm = read_input(confirm_prompt, "no");
866
867 if confirm.to_lowercase() != "yes" {
868 let msg = if language == Language::English {
869 "❌ Operation cancelled"
870 } else {
871 "❌ 操作已取消"
872 };
873 println!("\n{}", msg.red());
874 return Ok(());
875 }
876
877 if language == Language::English {
878 println!("\n🚀 Closing WSOL ATA account...");
879 } else {
880 println!("\n🚀 正在关闭 WSOL ATA 账号...");
881 }
882
883 let client = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
885
886 match run_async(client.unwrap_sol(keypair)) {
888 Ok(signature) => {
889 println!(
890 "\n{}",
891 "✅ WSOL ATA closed successfully!".bright_green().bold()
892 );
893 println!(" 📝 Signature: {}", signature.to_string().bright_white());
894 let explorer_url = if network == "2" {
895 format!(
896 "https://explorer.solana.com/tx/{}?cluster=devnet",
897 signature
898 )
899 } else {
900 format!("https://explorer.solana.com/tx/{}", signature)
901 };
902 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
903 println!(
904 "\n{}",
905 if language == Language::English {
906 "💰 Rent reclaimed to your wallet!"
907 } else {
908 "💰 租金已返还到您的钱包!"
909 }
910 .green()
911 );
912 Ok(())
913 }
914 Err(e) => {
915 let msg = if language == Language::English {
916 format!("❌ Close failed: {}", e)
917 } else {
918 format!("❌ 关闭失败: {}", e)
919 };
920 Err(msg)
921 }
922 }
923}
924
925#[cfg(feature = "solana-ops")]
926pub fn transfer_token(keypair: &Keypair, language: Language) -> Result<(), String> {
927 println!(
928 "\n{}",
929 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
930 );
931 if language == Language::English {
932 println!(" {}", "💎 Transfer SPL Token".bright_yellow().bold());
933 } else {
934 println!(" {}", "💎 转账 SPL 代币".bright_yellow().bold());
935 }
936 println!(
937 "{}",
938 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
939 );
940
941 let network_prompt = if language == Language::English {
942 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
943 } else {
944 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
945 };
946
947 let network = read_input(network_prompt, "1");
948 let rpc_url = if network == "2" {
949 DEVNET_RPC_URL
950 } else {
951 DEFAULT_RPC_URL
952 };
953
954 let mint_prompt = if language == Language::English {
955 "\nToken Mint address: "
956 } else {
957 "\n代币 Mint 地址: "
958 };
959 let mint_str = read_input(mint_prompt, "");
960
961 let mint = Pubkey::from_str(&mint_str).map_err(|_| {
962 if language == Language::English {
963 "❌ Invalid mint address".to_string()
964 } else {
965 "❌ 无效的 Mint 地址".to_string()
966 }
967 })?;
968
969 let recipient_prompt = if language == Language::English {
970 "Recipient address: "
971 } else {
972 "接收地址: "
973 };
974 let recipient_str = read_input(recipient_prompt, "");
975
976 let recipient = Pubkey::from_str(&recipient_str).map_err(|_| {
977 if language == Language::English {
978 "❌ Invalid recipient address".to_string()
979 } else {
980 "❌ 无效的接收地址".to_string()
981 }
982 })?;
983
984 let amount_prompt = if language == Language::English {
985 "Amount (smallest units): "
986 } else {
987 "金额 (最小单位): "
988 };
989 let amount_str = read_input(amount_prompt, "");
990 let amount: u64 = amount_str.parse().map_err(|_| {
991 if language == Language::English {
992 "❌ Invalid amount".to_string()
993 } else {
994 "❌ 无效的金额".to_string()
995 }
996 })?;
997
998 println!("\n{}", "📋 Transaction Summary:".bright_yellow());
999 println!(" From: {}", keypair.pubkey().to_string().bright_white());
1000 println!(" To: {}", recipient.to_string().bright_white());
1001 println!(" Token: {}", mint.to_string().bright_white());
1002 println!(
1003 " Amount: {} (smallest units)",
1004 amount.to_string().bright_white().bold()
1005 );
1006
1007 let confirm_prompt = if language == Language::English {
1008 "\nConfirm transaction? (yes/no) [no]: "
1009 } else {
1010 "\n确认交易? (yes/no) [no]: "
1011 };
1012 let confirm = read_input(confirm_prompt, "no");
1013
1014 if confirm.to_lowercase() != "yes" {
1015 let msg = if language == Language::English {
1016 "❌ Transaction cancelled"
1017 } else {
1018 "❌ 交易已取消"
1019 };
1020 println!("\n{}", msg.red());
1021 return Ok(());
1022 }
1023
1024 if language == Language::English {
1025 println!("\n🚀 Sending transaction...");
1026 } else {
1027 println!("\n🚀 正在发送交易...");
1028 }
1029
1030 let client = SolanaClient::new(rpc_url.to_string());
1031 match client.transfer_token(keypair, &recipient, &mint, amount) {
1032 Ok(signature) => {
1033 println!("\n{}", "✅ Transfer successful!".bright_green().bold());
1034 println!(" 📝 Signature: {}", signature.to_string().bright_white());
1035 let explorer_url = if network == "2" {
1036 format!(
1037 "https://explorer.solana.com/tx/{}?cluster=devnet",
1038 signature
1039 )
1040 } else {
1041 format!("https://explorer.solana.com/tx/{}", signature)
1042 };
1043 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
1044 Ok(())
1045 }
1046 Err(e) => {
1047 let msg = if language == Language::English {
1048 format!("❌ Transfer failed: {}", e)
1049 } else {
1050 format!("❌ 转账失败: {}", e)
1051 };
1052 Err(msg)
1053 }
1054 }
1055}
1056
1057#[cfg(feature = "solana-ops")]
1058pub fn create_nonce_account(keypair: &Keypair, language: Language) -> Result<(), String> {
1059 println!(
1060 "\n{}",
1061 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
1062 );
1063 if language == Language::English {
1064 println!(" {}", "🔑 Create Nonce Account".bright_yellow().bold());
1065 } else {
1066 println!(" {}", "🔑 创建 Nonce 账户".bright_yellow().bold());
1067 }
1068 println!(
1069 "{}",
1070 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan()
1071 );
1072
1073 let network_prompt = if language == Language::English {
1074 "\nSelect network:\n 1. Mainnet\n 2. Devnet\nChoice [1]: "
1075 } else {
1076 "\n选择网络:\n 1. 主网 (Mainnet)\n 2. 测试网 (Devnet)\n选择 [1]: "
1077 };
1078
1079 let network = read_input(network_prompt, "1");
1080 let rpc_url = if network == "2" {
1081 DEVNET_RPC_URL
1082 } else {
1083 DEFAULT_RPC_URL
1084 };
1085
1086 println!(
1087 "\n{}",
1088 if language == Language::English {
1089 "ℹ️ A nonce account will be created for durable transactions"
1090 } else {
1091 "ℹ️ 将创建一个用于持久交易的 Nonce 账户"
1092 }
1093 .bright_yellow()
1094 );
1095
1096 println!(
1097 "{}",
1098 if language == Language::English {
1099 "ℹ️ This requires ~0.00144 SOL for rent exemption"
1100 } else {
1101 "ℹ️ 这需要约 0.00144 SOL 用于租金豁免"
1102 }
1103 .bright_yellow()
1104 );
1105
1106 let confirm_prompt = if language == Language::English {
1107 "\nConfirm creation? (yes/no) [no]: "
1108 } else {
1109 "\n确认创建? (yes/no) [no]: "
1110 };
1111 let confirm = read_input(confirm_prompt, "no");
1112
1113 if confirm.to_lowercase() != "yes" {
1114 let msg = if language == Language::English {
1115 "❌ Operation cancelled"
1116 } else {
1117 "❌ 操作已取消"
1118 };
1119 println!("\n{}", msg.red());
1120 return Ok(());
1121 }
1122
1123 if language == Language::English {
1124 println!("\n🚀 Creating nonce account...");
1125 } else {
1126 println!("\n🚀 正在创建 Nonce 账户...");
1127 }
1128
1129 let client = SolanaClient::new(rpc_url.to_string());
1130 match client.create_nonce_account(keypair) {
1131 Ok((nonce_pubkey, signature)) => {
1132 println!(
1133 "\n{}",
1134 "✅ Nonce account created successfully!"
1135 .bright_green()
1136 .bold()
1137 );
1138 println!(
1139 " 🔑 Nonce Account: {}",
1140 nonce_pubkey.to_string().bright_white().bold()
1141 );
1142 println!(" 📝 Signature: {}", signature.to_string().bright_white());
1143 let explorer_url = if network == "2" {
1144 format!(
1145 "https://explorer.solana.com/address/{}?cluster=devnet",
1146 nonce_pubkey
1147 )
1148 } else {
1149 format!("https://explorer.solana.com/address/{}", nonce_pubkey)
1150 };
1151 println!(" 🔗 Explorer: {}", explorer_url.bright_blue());
1152 println!(
1153 "\n{}",
1154 if language == Language::English {
1155 "💡 Save this nonce account address for future use!"
1156 } else {
1157 "💡 请保存此 Nonce 账户地址以供将来使用!"
1158 }
1159 .bright_yellow()
1160 );
1161 Ok(())
1162 }
1163 Err(e) => {
1164 let msg = if language == Language::English {
1165 format!("❌ Creation failed: {}", e)
1166 } else {
1167 format!("❌ 创建失败: {}", e)
1168 };
1169 Err(msg)
1170 }
1171 }
1172}
1173
1174pub fn show_solana_operations_menu(language: crate::interactive::Language) -> Result<(), String> {
1177 #[cfg(feature = "solana-ops")]
1178 {
1179 use crate::KeyManager;
1180
1181 let ops_language = match language {
1183 crate::interactive::Language::English => Language::English,
1184 crate::interactive::Language::Chinese => Language::Chinese,
1185 };
1186
1187 let file_prompt = if ops_language == Language::English {
1189 "Keystore file path (default: keystore.json): "
1190 } else {
1191 "Keystore 文件路径 (默认: keystore.json): "
1192 };
1193
1194 let file_path = read_input(file_prompt, "keystore.json");
1195
1196 if !std::path::Path::new(&file_path).exists() {
1198 let err_msg = if ops_language == Language::English {
1199 format!("❌ File not found: {}", file_path)
1200 } else {
1201 format!("❌ 文件不存在: {}", file_path)
1202 };
1203 return Err(err_msg);
1204 }
1205
1206 let password_prompt = if ops_language == Language::English {
1208 "Enter password: "
1209 } else {
1210 "请输入密码: "
1211 };
1212
1213 print!("{}", password_prompt);
1214 io::stdout().flush().map_err(|e| e.to_string())?;
1215 let password =
1216 rpassword::read_password().map_err(|e| format!("Failed to read password: {}", e))?;
1217
1218 let keystore_json = std::fs::read_to_string(&file_path).map_err(|e| {
1220 if ops_language == Language::English {
1221 format!("❌ Failed to read file: {}", e)
1222 } else {
1223 format!("❌ 读取文件失败: {}", e)
1224 }
1225 })?;
1226
1227 let keypair =
1228 KeyManager::keypair_from_encrypted_json(&keystore_json, &password).map_err(|e| {
1229 if ops_language == Language::English {
1230 format!("❌ Failed to decrypt keystore: {}", e)
1231 } else {
1232 format!("❌ 解密 keystore 失败: {}", e)
1233 }
1234 })?;
1235
1236 println!(
1237 "\n{}",
1238 if ops_language == Language::English {
1239 "✅ Wallet unlocked successfully!"
1240 } else {
1241 "✅ 钱包解锁成功!"
1242 }
1243 .bright_green()
1244 );
1245
1246 show_operations_menu(&keypair, ops_language)
1248 }
1249
1250 #[cfg(not(feature = "solana-ops"))]
1251 {
1252 let _ = language; Err("Solana operations require the 'solana-ops' feature".to_string())
1254 }
1255}
1256
1257#[cfg(feature = "sol-trade-sdk")]
1259pub fn pumpswap_sell_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1260 println!(
1261 "\n{}",
1262 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1263 );
1264 if language == Language::English {
1265 println!(" {}", "🔥 PumpSwap Sell Tokens".bright_magenta().bold());
1266 } else {
1267 println!(" {}", "🔥 PumpSwap 卖出代币".bright_magenta().bold());
1268 }
1269 println!(
1270 "{}",
1271 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1272 );
1273
1274 println!(
1275 "\n{}",
1276 if language == Language::English {
1277 "Current Wallet:"
1278 } else {
1279 "当前钱包:"
1280 }
1281 .bright_green()
1282 );
1283 println!(" 📍 {}", keypair.pubkey().to_string().bright_white());
1284
1285 println!();
1287 let rpc_prompt = if language == Language::English {
1288 format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
1289 } else {
1290 format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
1291 };
1292 let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
1293
1294 println!();
1296 if language == Language::English {
1297 println!("{}", "🔧 Seed Optimization Configuration".bright_cyan());
1298 println!(" Seed optimization is used to create optimized ATA addresses");
1299 println!(" If your token ATA was created using the standard method, choose 'no'");
1300 println!(" If unsure, it's recommended to choose 'no'");
1301 } else {
1302 println!("{}", "🔧 Seed 优化配置".bright_cyan());
1303 println!(" Seed 优化用于创建优化的 ATA 地址,可以节省交易费用");
1304 println!(" 如果你的代币 ATA 是通过标准方式创建的,请选择 'no'");
1305 println!(" 如果不确定,建议选择 'yes'(默认)");
1306 }
1307
1308 print!(
1309 "\n{} ",
1310 if language == Language::English {
1311 "❓ Enable Seed Optimization? (yes/no, default: yes):"
1312 } else {
1313 "❓ 启用 Seed 优化? (yes/no, 默认 yes):"
1314 }
1315 .yellow()
1316 );
1317 io::stdout().flush().map_err(|e| e.to_string())?;
1318
1319 let mut seed_input = String::new();
1320 io::stdin()
1321 .read_line(&mut seed_input)
1322 .map_err(|e| e.to_string())?;
1323 let seed_input_trimmed = seed_input.trim().to_lowercase();
1324 let use_seed =
1326 seed_input_trimmed.is_empty() || seed_input_trimmed == "yes" || seed_input_trimmed == "y";
1327
1328 if use_seed {
1329 println!(
1330 "{}",
1331 if language == Language::English {
1332 "✅ Seed optimization enabled"
1333 } else {
1334 "✅ 已启用 Seed 优化"
1335 }
1336 .green()
1337 );
1338 } else {
1339 println!(
1340 "{}",
1341 if language == Language::English {
1342 "✅ Using standard ATA"
1343 } else {
1344 "✅ 使用标准 ATA"
1345 }
1346 .green()
1347 );
1348 }
1349
1350 println!();
1352 if language == Language::English {
1353 println!(
1354 "{}",
1355 "💡 You can enter multiple mint addresses separated by commas or spaces".bright_cyan()
1356 );
1357 println!(" Tokens will be sold in the order entered");
1358 } else {
1359 println!(
1360 "{}",
1361 "💡 可以输入多个 Mint 地址,用逗号或空格分割".bright_cyan()
1362 );
1363 println!(" 将按输入顺序依次卖出");
1364 }
1365
1366 let mint_prompt = if language == Language::English {
1367 "\nToken Mint Address(es): "
1368 } else {
1369 "\n代币 Mint 地址: "
1370 };
1371 print!("{}", mint_prompt.yellow());
1372 io::stdout().flush().map_err(|e| e.to_string())?;
1373
1374 let mut mint_input = String::new();
1375 io::stdin()
1376 .read_line(&mut mint_input)
1377 .map_err(|e| e.to_string())?;
1378 let mint_input = mint_input.trim();
1379
1380 if mint_input.is_empty() {
1381 return Err(if language == Language::English {
1382 "Token mint address cannot be empty".to_string()
1383 } else {
1384 "代币 Mint 地址不能为空".to_string()
1385 });
1386 }
1387
1388 let mint_addresses: Vec<String> = mint_input
1390 .split(|c: char| c == ',' || c.is_whitespace())
1391 .map(|s| s.trim())
1392 .filter(|s| !s.is_empty())
1393 .map(|s| s.to_string())
1394 .collect();
1395
1396 if mint_addresses.is_empty() {
1397 return Err(if language == Language::English {
1398 "No valid mint addresses found".to_string()
1399 } else {
1400 "未找到有效的 Mint 地址".to_string()
1401 });
1402 }
1403
1404 for (idx, mint) in mint_addresses.iter().enumerate() {
1406 if let Err(e) = Pubkey::from_str(mint) {
1407 return Err(if language == Language::English {
1408 format!("Invalid mint address #{}: {} (error: {})", idx + 1, mint, e)
1409 } else {
1410 format!("无效的 Mint 地址 #{}: {} (错误: {})", idx + 1, mint, e)
1411 });
1412 }
1413 }
1414
1415 println!();
1417 if language == Language::English {
1418 println!(
1419 "{}",
1420 format!("📋 Found {} token(s) to sell:", mint_addresses.len()).bright_green()
1421 );
1422 } else {
1423 println!(
1424 "{}",
1425 format!("📋 找到 {} 个代币待卖出:", mint_addresses.len()).bright_green()
1426 );
1427 }
1428 for (idx, mint) in mint_addresses.iter().enumerate() {
1429 println!(" {}. {}", idx + 1, mint.bright_white());
1430 }
1431
1432 let slippage = 9900u64;
1434 println!();
1435 if language == Language::English {
1436 println!("📊 Slippage tolerance: {}%", slippage as f64 / 100.0);
1437 } else {
1438 println!("📊 滑点容忍度: {}%", slippage as f64 / 100.0);
1439 }
1440
1441 let total_mints = mint_addresses.len();
1443 if total_mints > 1 {
1444 println!();
1445 if language == Language::English {
1446 println!(
1447 "{}",
1448 format!("⚠️ You are about to sell {} tokens", total_mints)
1449 .yellow()
1450 .bold()
1451 );
1452 println!(" All tokens will be sold automatically without individual confirmation");
1453 } else {
1454 println!(
1455 "{}",
1456 format!("⚠️ 您即将卖出 {} 个代币", total_mints)
1457 .yellow()
1458 .bold()
1459 );
1460 println!(" 所有代币将自动卖出,不会逐个确认");
1461 }
1462
1463 print!(
1464 "\n{}",
1465 if language == Language::English {
1466 "❓ Confirm batch sell? (yes/no, default: yes): "
1467 } else {
1468 "❓ 确认批量卖出? (yes/no, 默认 yes): "
1469 }
1470 .yellow()
1471 );
1472 io::stdout().flush().map_err(|e| e.to_string())?;
1473
1474 let mut confirm = String::new();
1475 io::stdin()
1476 .read_line(&mut confirm)
1477 .map_err(|e| e.to_string())?;
1478 let confirm_trimmed = confirm.trim().to_lowercase();
1479
1480 if confirm_trimmed == "no" || confirm_trimmed == "n" {
1481 return Err(if language == Language::English {
1482 "❌ Batch sell cancelled".to_string()
1483 } else {
1484 "❌ 批量卖出已取消".to_string()
1485 });
1486 }
1487 }
1488
1489 let skip_confirmation = total_mints > 1;
1492 for (idx, mint) in mint_addresses.iter().enumerate() {
1493 println!();
1494 println!(
1495 "{}",
1496 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1497 );
1498 if language == Language::English {
1499 println!(
1500 "{}",
1501 format!("🚀 Processing token {}/{}", idx + 1, total_mints).bright_blue()
1502 );
1503 println!(" Mint: {}", mint.bright_white());
1504 } else {
1505 println!(
1506 "{}",
1507 format!("🚀 处理第 {}/{} 个代币", idx + 1, total_mints).bright_blue()
1508 );
1509 println!(" Mint: {}", mint.bright_white());
1510 }
1511 println!(
1512 "{}",
1513 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1514 );
1515
1516 let result = match tokio::runtime::Handle::try_current() {
1518 Ok(handle) => {
1519 let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
1521 let mint_clone = mint.to_string();
1522 let rpc_url_clone = rpc_url.clone();
1523
1524 std::thread::spawn(move || {
1525 let keypair_clone = Keypair::from_base58_string(&keypair_b58);
1526 handle.block_on(async move {
1527 crate::solana_utils::pumpswap_sell::handle_pumpswap_sell_no_prompt(
1528 &keypair_clone,
1529 &mint_clone,
1530 &rpc_url_clone,
1531 slippage,
1532 use_seed,
1533 language,
1534 skip_confirmation, )
1536 .await
1537 })
1538 })
1539 .join()
1540 .map_err(|_| "Thread panicked".to_string())?
1541 }
1542 Err(_) => {
1543 let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
1545 rt.block_on(async {
1546 crate::solana_utils::pumpswap_sell::handle_pumpswap_sell_no_prompt(
1547 keypair,
1548 mint,
1549 &rpc_url,
1550 slippage,
1551 use_seed,
1552 language,
1553 skip_confirmation, )
1555 .await
1556 })
1557 }
1558 };
1559
1560 match result {
1562 Ok(_) => {
1563 if language == Language::English {
1564 println!(
1565 "\n{}",
1566 format!("✅ Token {}/{} sold successfully", idx + 1, total_mints)
1567 .bright_green()
1568 );
1569 } else {
1570 println!(
1571 "\n{}",
1572 format!("✅ 第 {}/{} 个代币卖出成功", idx + 1, total_mints).bright_green()
1573 );
1574 }
1575 }
1576 Err(e) => {
1577 if language == Language::English {
1578 println!(
1579 "\n{}",
1580 format!("❌ Token {}/{} failed: {}", idx + 1, total_mints, e).bright_red()
1581 );
1582 println!(" Continuing with next token...");
1583 } else {
1584 println!(
1585 "\n{}",
1586 format!("❌ 第 {}/{} 个代币卖出失败: {}", idx + 1, total_mints, e)
1587 .bright_red()
1588 );
1589 println!(" 继续处理下一个代币...");
1590 }
1591 }
1592 }
1593
1594 if idx < total_mints - 1 {
1596 println!();
1597 if language == Language::English {
1598 println!("⏳ Waiting 2 seconds before next transaction...");
1599 } else {
1600 println!("⏳ 等待 2 秒后处理下一个交易...");
1601 }
1602 std::thread::sleep(std::time::Duration::from_secs(2));
1603 }
1604 }
1605
1606 println!();
1608 println!(
1609 "{}",
1610 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1611 );
1612 if language == Language::English {
1613 println!("{}", "🎉 All transactions completed!".bright_green().bold());
1614 } else {
1615 println!("{}", "🎉 所有交易已完成!".bright_green().bold());
1616 }
1617 println!(
1618 "{}",
1619 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1620 );
1621
1622 Ok(())
1623}
1624
1625#[cfg(not(feature = "sol-trade-sdk"))]
1626pub fn pumpswap_sell_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
1627 Err(if language == Language::English {
1628 "PumpSwap sell requires 'sol-trade-sdk' feature. Please rebuild with:\ncargo build --release --features sol-trade-sdk".to_string()
1629 } else {
1630 "PumpSwap 卖出需要 'sol-trade-sdk' 功能。请使用以下命令重新编译:\ncargo build --release --features sol-trade-sdk".to_string()
1631 })
1632}
1633
1634#[cfg(feature = "sol-trade-sdk")]
1636pub fn pumpfun_sell_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1637 println!(
1638 "\n{}",
1639 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1640 );
1641 if language == Language::English {
1642 println!(
1643 " {}",
1644 "🔥 Pump.fun Bonding Curve Sell".bright_magenta().bold()
1645 );
1646 } else {
1647 println!(" {}", "🔥 Pump.fun 内盘卖出代币".bright_magenta().bold());
1648 }
1649 println!(
1650 "{}",
1651 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1652 );
1653
1654 println!(
1655 "\n{}",
1656 if language == Language::English {
1657 "Current Wallet:"
1658 } else {
1659 "当前钱包:"
1660 }
1661 .bright_green()
1662 );
1663 println!(" 📍 {}", keypair.pubkey().to_string().bright_white());
1664
1665 let rpc_prompt = if language == Language::English {
1666 format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
1667 } else {
1668 format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
1669 };
1670 let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
1671
1672 println!();
1673 if language == Language::English {
1674 println!("{}", "🔧 Seed Optimization Configuration".bright_cyan());
1675 println!(" If your token ATA was created using the standard method, choose 'no'");
1676 println!(" If unsure, it's recommended to choose 'no'");
1677 } else {
1678 println!("{}", "🔧 Seed 优化配置".bright_cyan());
1679 println!(" 如果你的代币 ATA 是通过标准方式创建的,请选择 'no'");
1680 println!(" 如果不确定,建议选择 'yes'(默认)");
1681 }
1682
1683 print!(
1684 "\n{} ",
1685 if language == Language::English {
1686 "❓ Enable Seed Optimization? (yes/no, default: yes):"
1687 } else {
1688 "❓ 启用 Seed 优化? (yes/no, 默认 yes):"
1689 }
1690 .yellow()
1691 );
1692 io::stdout().flush().map_err(|e| e.to_string())?;
1693
1694 let mut seed_input = String::new();
1695 io::stdin()
1696 .read_line(&mut seed_input)
1697 .map_err(|e| e.to_string())?;
1698 let seed_input_trimmed = seed_input.trim().to_lowercase();
1699 let use_seed =
1700 seed_input_trimmed.is_empty() || seed_input_trimmed == "yes" || seed_input_trimmed == "y";
1701
1702 if language == Language::English {
1703 println!(
1704 "{}",
1705 "💡 You can enter multiple mint addresses separated by commas or spaces".bright_cyan()
1706 );
1707 } else {
1708 println!(
1709 "{}",
1710 "💡 可以输入多个 Mint 地址,用逗号或空格分割".bright_cyan()
1711 );
1712 }
1713
1714 let mint_prompt = if language == Language::English {
1715 "\nToken Mint Address(es): "
1716 } else {
1717 "\n代币 Mint 地址: "
1718 };
1719 print!("{}", mint_prompt.yellow());
1720 io::stdout().flush().map_err(|e| e.to_string())?;
1721
1722 let mut mint_input = String::new();
1723 io::stdin()
1724 .read_line(&mut mint_input)
1725 .map_err(|e| e.to_string())?;
1726 let mint_input = mint_input.trim();
1727
1728 if mint_input.is_empty() {
1729 return Err(if language == Language::English {
1730 "Token mint address cannot be empty".to_string()
1731 } else {
1732 "代币 Mint 地址不能为空".to_string()
1733 });
1734 }
1735
1736 let mint_addresses: Vec<String> = mint_input
1737 .split(|c: char| c == ',' || c.is_whitespace())
1738 .map(|s| s.trim())
1739 .filter(|s| !s.is_empty())
1740 .map(|s| s.to_string())
1741 .collect();
1742
1743 if mint_addresses.is_empty() {
1744 return Err(if language == Language::English {
1745 "No valid mint addresses found".to_string()
1746 } else {
1747 "未找到有效的 Mint 地址".to_string()
1748 });
1749 }
1750
1751 for (idx, mint) in mint_addresses.iter().enumerate() {
1752 if let Err(e) = Pubkey::from_str(mint) {
1753 return Err(if language == Language::English {
1754 format!("Invalid mint address #{}: {} (error: {})", idx + 1, mint, e)
1755 } else {
1756 format!("无效的 Mint 地址 #{}: {} (错误: {})", idx + 1, mint, e)
1757 });
1758 }
1759 }
1760
1761 println!();
1762 if language == Language::English {
1763 println!(
1764 "{}",
1765 format!("📋 Found {} token(s) to sell:", mint_addresses.len()).bright_green()
1766 );
1767 } else {
1768 println!(
1769 "{}",
1770 format!("📋 找到 {} 个代币待卖出:", mint_addresses.len()).bright_green()
1771 );
1772 }
1773 for (idx, mint) in mint_addresses.iter().enumerate() {
1774 println!(" {}. {}", idx + 1, mint.bright_white());
1775 }
1776
1777 let slippage = 9900u64;
1778 println!();
1779 if language == Language::English {
1780 println!("📊 Slippage tolerance: {}%", slippage as f64 / 100.0);
1781 } else {
1782 println!("📊 滑点容忍度: {}%", slippage as f64 / 100.0);
1783 }
1784
1785 let total_mints = mint_addresses.len();
1786 if total_mints > 1 {
1787 println!();
1788 if language == Language::English {
1789 println!(
1790 "{}",
1791 format!("⚠️ You are about to sell {} tokens", total_mints)
1792 .yellow()
1793 .bold()
1794 );
1795 } else {
1796 println!(
1797 "{}",
1798 format!("⚠️ 您即将卖出 {} 个代币", total_mints)
1799 .yellow()
1800 .bold()
1801 );
1802 }
1803
1804 print!(
1805 "\n{}",
1806 if language == Language::English {
1807 "❓ Confirm batch sell? (yes/no, default: yes): "
1808 } else {
1809 "❓ 确认批量卖出? (yes/no, 默认 yes): "
1810 }
1811 .yellow()
1812 );
1813 io::stdout().flush().map_err(|e| e.to_string())?;
1814
1815 let mut confirm = String::new();
1816 io::stdin()
1817 .read_line(&mut confirm)
1818 .map_err(|e| e.to_string())?;
1819 let confirm_trimmed = confirm.trim().to_lowercase();
1820
1821 if confirm_trimmed == "no" || confirm_trimmed == "n" {
1822 return Err(if language == Language::English {
1823 "❌ Batch sell cancelled".to_string()
1824 } else {
1825 "❌ 批量卖出已取消".to_string()
1826 });
1827 }
1828 }
1829
1830 let skip_confirmation = total_mints > 1;
1831 for (idx, mint) in mint_addresses.iter().enumerate() {
1832 println!();
1833 println!(
1834 "{}",
1835 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1836 );
1837 if language == Language::English {
1838 println!(
1839 "{}",
1840 format!("🚀 Processing token {}/{}", idx + 1, total_mints).bright_blue()
1841 );
1842 println!(" Mint: {}", mint.bright_white());
1843 } else {
1844 println!(
1845 "{}",
1846 format!("🚀 处理第 {}/{} 个代币", idx + 1, total_mints).bright_blue()
1847 );
1848 println!(" Mint: {}", mint.bright_white());
1849 }
1850 println!(
1851 "{}",
1852 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1853 );
1854
1855 let result = match tokio::runtime::Handle::try_current() {
1856 Ok(handle) => {
1857 let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
1858 let mint_clone = mint.to_string();
1859 let rpc_url_clone = rpc_url.clone();
1860
1861 std::thread::spawn(move || {
1862 let keypair_clone = Keypair::from_base58_string(&keypair_b58);
1863 handle.block_on(async move {
1864 crate::solana_utils::pumpfun_sell::handle_pumpfun_sell_no_prompt(
1865 &keypair_clone,
1866 &mint_clone,
1867 &rpc_url_clone,
1868 slippage,
1869 use_seed,
1870 language,
1871 skip_confirmation,
1872 )
1873 .await
1874 })
1875 })
1876 .join()
1877 .map_err(|_| "Thread panicked".to_string())?
1878 }
1879 Err(_) => {
1880 let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
1881 rt.block_on(async {
1882 crate::solana_utils::pumpfun_sell::handle_pumpfun_sell_no_prompt(
1883 keypair,
1884 mint,
1885 &rpc_url,
1886 slippage,
1887 use_seed,
1888 language,
1889 skip_confirmation,
1890 )
1891 .await
1892 })
1893 }
1894 };
1895
1896 match result {
1897 Ok(_) => {
1898 if language == Language::English {
1899 println!(
1900 "\n{}",
1901 format!("✅ Token {}/{} sold successfully", idx + 1, total_mints)
1902 .bright_green()
1903 );
1904 } else {
1905 println!(
1906 "\n{}",
1907 format!("✅ 第 {}/{} 个代币卖出成功", idx + 1, total_mints).bright_green()
1908 );
1909 }
1910 }
1911 Err(e) => {
1912 if language == Language::English {
1913 println!(
1914 "\n{}",
1915 format!("❌ Token {}/{} failed: {}", idx + 1, total_mints, e).bright_red()
1916 );
1917 } else {
1918 println!(
1919 "\n{}",
1920 format!("❌ 第 {}/{} 个代币卖出失败: {}", idx + 1, total_mints, e)
1921 .bright_red()
1922 );
1923 }
1924 }
1925 }
1926
1927 if idx < total_mints - 1 {
1928 println!();
1929 std::thread::sleep(std::time::Duration::from_secs(2));
1930 }
1931 }
1932
1933 println!();
1934 println!(
1935 "{}",
1936 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1937 );
1938 if language == Language::English {
1939 println!("{}", "🎉 All transactions completed!".bright_green().bold());
1940 } else {
1941 println!("{}", "🎉 所有交易已完成!".bright_green().bold());
1942 }
1943 println!(
1944 "{}",
1945 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1946 );
1947
1948 Ok(())
1949}
1950
1951#[cfg(feature = "sol-trade-sdk")]
1953pub fn pumpfun_cashback_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1954 use sol_trade_sdk::{common::TradeConfig, SolanaTrade};
1955 use solana_commitment_config::CommitmentConfig;
1956 use std::sync::Arc;
1957
1958 println!(
1959 "\n{}",
1960 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1961 );
1962 if language == Language::English {
1963 println!(
1964 " {}",
1965 "💰 Pump (Pump.fun) Cashback – View & Claim"
1966 .bright_magenta()
1967 .bold()
1968 );
1969 println!(
1970 " {}",
1971 " Cashback is native SOL from trading on Pump.fun.".bright_white()
1972 );
1973 } else {
1974 println!(
1975 " {}",
1976 "💰 Pump (Pump.fun) 返现 – 查看与领取"
1977 .bright_magenta()
1978 .bold()
1979 );
1980 println!(
1981 " {}",
1982 " 返现为在 Pump.fun 交易累积的原生 SOL。".bright_white()
1983 );
1984 }
1985 println!(
1986 "{}",
1987 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
1988 );
1989
1990 println!(
1991 "\n{}",
1992 if language == Language::English {
1993 "Current Wallet:"
1994 } else {
1995 "当前钱包:"
1996 }
1997 .bright_green()
1998 );
1999 println!(" 📍 {}", keypair.pubkey().to_string().bright_white());
2000
2001 let rpc_prompt = if language == Language::English {
2002 format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
2003 } else {
2004 format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
2005 };
2006 let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
2007
2008 if language == Language::English {
2010 println!("\n{}", "🔍 Querying cashback balance...".bright_cyan());
2011 } else {
2012 println!("\n{}", "🔍 正在查询返现余额...".bright_cyan());
2013 }
2014
2015 let pda = sol_trade_sdk::instruction::utils::pumpfun::get_user_volume_accumulator_pda(
2016 &keypair.pubkey(),
2017 )
2018 .ok_or_else(|| {
2019 if language == Language::English {
2020 "Failed to derive UserVolumeAccumulator PDA".to_string()
2021 } else {
2022 "无法派生 UserVolumeAccumulator PDA 地址".to_string()
2023 }
2024 })?;
2025
2026 let rpc_client = RpcClient::new(rpc_url.clone());
2027 let pda_balance_lamports = rpc_client.get_balance(&pda).unwrap_or(0);
2028 let rent_exempt_min: u64 = 890_880;
2029 let claimable_lamports = pda_balance_lamports.saturating_sub(rent_exempt_min);
2030 let claimable_sol = claimable_lamports as f64 / 1_000_000_000.0;
2031
2032 println!(
2034 "\n{}",
2035 if language == Language::English {
2036 "📊 Cashback Info:"
2037 } else {
2038 "📊 返现信息:"
2039 }
2040 .bright_yellow()
2041 );
2042 println!(" PDA: {}", pda.to_string().bright_white());
2043 if language == Language::English {
2044 println!(
2045 " PDA Balance: {} lamports ({:.9} SOL)",
2046 pda_balance_lamports,
2047 pda_balance_lamports as f64 / 1e9
2048 );
2049 println!(
2050 " Claimable: {} lamports ({:.9} SOL)",
2051 claimable_lamports, claimable_sol
2052 );
2053 } else {
2054 println!(
2055 " PDA 余额: {} lamports ({:.9} SOL)",
2056 pda_balance_lamports,
2057 pda_balance_lamports as f64 / 1e9
2058 );
2059 println!(
2060 " 可领取: {} lamports ({:.9} SOL)",
2061 claimable_lamports, claimable_sol
2062 );
2063 }
2064
2065 if claimable_lamports == 0 {
2066 if language == Language::English {
2067 println!(
2068 "\n{}",
2069 "ℹ️ No cashback available to claim.".bright_yellow()
2070 );
2071 } else {
2072 println!("\n{}", "ℹ️ 暂无可领取的返现。".bright_yellow());
2073 }
2074 return Ok(());
2075 }
2076
2077 let confirm_prompt = if language == Language::English {
2079 format!("\nClaim {:.9} SOL? (yes/no) [no]: ", claimable_sol)
2080 } else {
2081 format!("\n领取 {:.9} SOL? (yes/no) [no]: ", claimable_sol)
2082 };
2083 let confirm = read_input(&confirm_prompt, "no");
2084
2085 if confirm.to_lowercase() != "yes" {
2086 let msg = if language == Language::English {
2087 "❌ Claim cancelled"
2088 } else {
2089 "❌ 已取消领取"
2090 };
2091 println!("\n{}", msg.red());
2092 return Ok(());
2093 }
2094
2095 if language == Language::English {
2097 println!("\n{}", "🚀 Claiming...".bright_cyan());
2098 } else {
2099 println!("\n{}", "🚀 正在领取...".bright_cyan());
2100 }
2101
2102 let sig = match tokio::runtime::Handle::try_current() {
2103 Ok(handle) => {
2104 let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
2105 let rpc_url_clone = rpc_url.clone();
2106 std::thread::spawn(move || {
2107 let kp = Keypair::from_base58_string(&keypair_b58);
2108 let payer = Arc::new(kp);
2109 let config = TradeConfig::builder(
2110 rpc_url_clone.clone(),
2111 vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url_clone)],
2112 CommitmentConfig::confirmed(),
2113 )
2114 .create_wsol_ata_on_startup(false)
2115 .use_seed_optimize(false)
2116 .check_min_tip(false)
2117 .log_enabled(false)
2118 .swqos_cores_from_end(false)
2119 .mev_protection(false)
2120 .build();
2121 handle.block_on(async move {
2122 let client = SolanaTrade::new(payer, config).await;
2123 client
2124 .claim_cashback_pumpfun()
2125 .await
2126 .map_err(|e| e.to_string())
2127 })
2128 })
2129 .join()
2130 .map_err(|_| "Thread panicked".to_string())??
2131 }
2132 Err(_) => {
2133 let payer = Arc::new(keypair.insecure_clone());
2134 let config = TradeConfig::builder(
2135 rpc_url.clone(),
2136 vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url)],
2137 CommitmentConfig::confirmed(),
2138 )
2139 .create_wsol_ata_on_startup(false)
2140 .use_seed_optimize(false)
2141 .check_min_tip(false)
2142 .log_enabled(false)
2143 .swqos_cores_from_end(false)
2144 .mev_protection(false)
2145 .build();
2146 let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
2147 let client = rt.block_on(SolanaTrade::new(payer, config));
2148 rt.block_on(client.claim_cashback_pumpfun())
2149 .map_err(|e| e.to_string())?
2150 }
2151 };
2152
2153 if language == Language::English {
2154 println!("{}", "✅ Claim successful!".bright_green().bold());
2155 println!("Signature: {}", sig.yellow());
2156 println!("Explorer: https://solscan.io/tx/{}", sig);
2157 } else {
2158 println!("{}", "✅ 领取成功!".bright_green().bold());
2159 println!("签名: {}", sig.yellow());
2160 println!("浏览器: https://solscan.io/tx/{}", sig);
2161 }
2162 Ok(())
2163}
2164
2165#[cfg(feature = "sol-trade-sdk")]
2167pub fn pumpswap_cashback_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
2168 use sol_trade_sdk::{common::TradeConfig, SolanaTrade};
2169 use solana_commitment_config::CommitmentConfig;
2170 use std::sync::Arc;
2171
2172 println!(
2173 "\n{}",
2174 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
2175 );
2176 if language == Language::English {
2177 println!(
2178 " {}",
2179 "💰 PumpSwap Cashback – View & Claim"
2180 .bright_magenta()
2181 .bold()
2182 );
2183 println!(
2184 " {}",
2185 " Cashback is WSOL from trading on PumpSwap.".bright_white()
2186 );
2187 } else {
2188 println!(
2189 " {}",
2190 "💰 PumpSwap 返现 – 查看与领取".bright_magenta().bold()
2191 );
2192 println!(
2193 " {}",
2194 " 返现为在 PumpSwap 交易累积的 WSOL。".bright_white()
2195 );
2196 }
2197 println!(
2198 "{}",
2199 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta()
2200 );
2201
2202 println!(
2203 "\n{}",
2204 if language == Language::English {
2205 "Current Wallet:"
2206 } else {
2207 "当前钱包:"
2208 }
2209 .bright_green()
2210 );
2211 println!(" 📍 {}", keypair.pubkey().to_string().bright_white());
2212
2213 let rpc_prompt = if language == Language::English {
2214 format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
2215 } else {
2216 format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
2217 };
2218 let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
2219
2220 if language == Language::English {
2222 println!("\n{}", "🔍 Querying cashback balance...".bright_cyan());
2223 } else {
2224 println!("\n{}", "🔍 正在查询返现余额...".bright_cyan());
2225 }
2226
2227 let wsol_ata =
2228 sol_trade_sdk::instruction::utils::pumpswap::get_user_volume_accumulator_wsol_ata(
2229 &keypair.pubkey(),
2230 )
2231 .ok_or_else(|| {
2232 if language == Language::English {
2233 "Failed to derive PumpSwap UserVolumeAccumulator WSOL ATA".to_string()
2234 } else {
2235 "无法派生 PumpSwap UserVolumeAccumulator WSOL ATA 地址".to_string()
2236 }
2237 })?;
2238
2239 let rpc_client = RpcClient::new(rpc_url.clone());
2240 let claimable_lamports = match rpc_client.get_token_account_balance(&wsol_ata) {
2241 Ok(balance) => balance.amount.parse::<u64>().unwrap_or(0),
2242 Err(_) => 0,
2243 };
2244 let claimable_sol = claimable_lamports as f64 / 1_000_000_000.0;
2245
2246 println!(
2248 "\n{}",
2249 if language == Language::English {
2250 "📊 Cashback Info:"
2251 } else {
2252 "📊 返现信息:"
2253 }
2254 .bright_yellow()
2255 );
2256 println!(" WSOL ATA: {}", wsol_ata.to_string().bright_white());
2257 if language == Language::English {
2258 println!(
2259 " Claimable: {} lamports ({:.9} SOL)",
2260 claimable_lamports, claimable_sol
2261 );
2262 } else {
2263 println!(
2264 " 可领取: {} lamports ({:.9} SOL)",
2265 claimable_lamports, claimable_sol
2266 );
2267 }
2268
2269 if claimable_lamports == 0 {
2270 if language == Language::English {
2271 println!(
2272 "\n{}",
2273 "ℹ️ No cashback available to claim.".bright_yellow()
2274 );
2275 } else {
2276 println!("\n{}", "ℹ️ 暂无可领取的返现。".bright_yellow());
2277 }
2278 return Ok(());
2279 }
2280
2281 let confirm_prompt = if language == Language::English {
2283 format!("\nClaim {:.9} SOL (WSOL)? (yes/no) [no]: ", claimable_sol)
2284 } else {
2285 format!("\n领取 {:.9} SOL (WSOL)? (yes/no) [no]: ", claimable_sol)
2286 };
2287 let confirm = read_input(&confirm_prompt, "no");
2288
2289 if confirm.to_lowercase() != "yes" {
2290 let msg = if language == Language::English {
2291 "❌ Claim cancelled"
2292 } else {
2293 "❌ 已取消领取"
2294 };
2295 println!("\n{}", msg.red());
2296 return Ok(());
2297 }
2298
2299 if language == Language::English {
2301 println!("\n{}", "🚀 Claiming...".bright_cyan());
2302 } else {
2303 println!("\n{}", "🚀 正在领取...".bright_cyan());
2304 }
2305
2306 let sig = match tokio::runtime::Handle::try_current() {
2307 Ok(handle) => {
2308 let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
2309 let rpc_url_clone = rpc_url.clone();
2310 std::thread::spawn(move || {
2311 let kp = Keypair::from_base58_string(&keypair_b58);
2312 let payer = Arc::new(kp);
2313 let config = TradeConfig::builder(
2314 rpc_url_clone.clone(),
2315 vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url_clone)],
2316 CommitmentConfig::confirmed(),
2317 )
2318 .create_wsol_ata_on_startup(false)
2319 .use_seed_optimize(false)
2320 .check_min_tip(false)
2321 .log_enabled(false)
2322 .swqos_cores_from_end(false)
2323 .mev_protection(false)
2324 .build();
2325 handle.block_on(async move {
2326 let client = SolanaTrade::new(payer, config).await;
2327 client
2328 .claim_cashback_pumpswap()
2329 .await
2330 .map_err(|e| e.to_string())
2331 })
2332 })
2333 .join()
2334 .map_err(|_| "Thread panicked".to_string())??
2335 }
2336 Err(_) => {
2337 let payer = Arc::new(keypair.insecure_clone());
2338 let config = TradeConfig::builder(
2339 rpc_url.clone(),
2340 vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url)],
2341 CommitmentConfig::confirmed(),
2342 )
2343 .create_wsol_ata_on_startup(false)
2344 .use_seed_optimize(false)
2345 .check_min_tip(false)
2346 .log_enabled(false)
2347 .swqos_cores_from_end(false)
2348 .mev_protection(false)
2349 .build();
2350 let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
2351 let client = rt.block_on(SolanaTrade::new(payer, config));
2352 rt.block_on(client.claim_cashback_pumpswap())
2353 .map_err(|e| e.to_string())?
2354 }
2355 };
2356
2357 if language == Language::English {
2358 println!("{}", "✅ Claim successful!".bright_green().bold());
2359 println!("Signature: {}", sig.yellow());
2360 println!("Explorer: https://solscan.io/tx/{}", sig);
2361 } else {
2362 println!("{}", "✅ 领取成功!".bright_green().bold());
2363 println!("签名: {}", sig.yellow());
2364 println!("浏览器: https://solscan.io/tx/{}", sig);
2365 }
2366 Ok(())
2367}
2368
2369#[cfg(not(feature = "sol-trade-sdk"))]
2370pub fn pumpfun_sell_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
2371 Err(if language == Language::English {
2372 "Pump.fun sell requires 'sol-trade-sdk' feature. Please rebuild with:\ncargo build --release --features sol-trade-sdk".to_string()
2373 } else {
2374 "Pump.fun 内盘卖出需要 'sol-trade-sdk' 功能。请使用以下命令重新编译:\ncargo build --release --features sol-trade-sdk".to_string()
2375 })
2376}
2377
2378#[cfg(not(feature = "sol-trade-sdk"))]
2379pub fn pumpfun_cashback_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
2380 Err(if language == Language::English {
2381 "Pump cashback requires 'sol-trade-sdk' feature.".to_string()
2382 } else {
2383 "Pump 返现需要 'sol-trade-sdk' 功能。".to_string()
2384 })
2385}
2386
2387#[cfg(not(feature = "sol-trade-sdk"))]
2388pub fn pumpswap_cashback_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
2389 Err(if language == Language::English {
2390 "PumpSwap cashback requires 'sol-trade-sdk' feature.".to_string()
2391 } else {
2392 "PumpSwap 返现需要 'sol-trade-sdk' 功能。".to_string()
2393 })
2394}