Skip to main content

sol_safekey/
operations.rs

1//! Solana Operations Interactive Menu
2//!
3//! Provides interactive Solana operations using encrypted keystore
4//! 提供使用加密 keystore 的交互式 Solana 操作
5
6#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
7use std::io::{self, Write};
8use solana_sdk::signature::Keypair;
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::pubkey::Pubkey;
14#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
15use solana_sdk::signer::Signer;
16#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
17use solana_sdk::bs58;
18#[cfg(any(feature = "solana-ops", feature = "sol-trade-sdk"))]
19use std::str::FromStr;
20
21#[cfg(feature = "solana-ops")]
22use crate::solana_utils::{SolanaClient, SolanaClientSdk, lamports_to_sol};
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/// Language for UI
33#[derive(Clone, Copy, PartialEq)]
34pub enum Language {
35    English,
36    Chinese,
37}
38
39/// Read user input with default value
40#[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/// Show Solana operations menu
57#[cfg(feature = "solana-ops")]
58pub fn show_operations_menu(keypair: &Keypair, language: Language) -> Result<(), String> {
59    loop {
60        println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
61        if language == Language::English {
62            println!("  {} - Solana Operations", "🔧 Sol-SafeKey".bright_yellow().bold());
63        } else {
64            println!("  {} - Solana 操作", "🔧 Sol-SafeKey".bright_yellow().bold());
65        }
66        println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
67
68        println!("\n{}", if language == Language::English {
69            "Current Wallet:"
70        } else {
71            "当前钱包:"
72        }.bright_green());
73        println!("  📍 {}", keypair.pubkey().to_string().bright_white());
74
75        println!("\n{}", if language == Language::English {
76            "Available Operations:"
77        } else {
78            "可用操作:"
79        }.bright_green());
80
81        if language == Language::English {
82            println!("  {}  Check SOL Balance", "1.".bright_cyan());
83            println!("  {}  Transfer SOL", "2.".bright_cyan());
84            println!("  {}  Create WSOL ATA", "3.".bright_cyan());
85            println!("  {}  Wrap SOL → WSOL", "4.".bright_cyan());
86            println!("  {}  Unwrap WSOL → SOL", "5.".bright_cyan());
87            println!("  {}  Transfer SPL Token", "6.".bright_cyan());
88            println!("  {}  Create Nonce Account", "7.".bright_cyan());
89            println!("  {}  Back to Main Menu", "0.".bright_cyan());
90        } else {
91            println!("  {}  查询 SOL 余额", "1.".bright_cyan());
92            println!("  {}  转账 SOL", "2.".bright_cyan());
93            println!("  {}  创建 WSOL ATA 账户", "3.".bright_cyan());
94            println!("  {}  包装 SOL → WSOL", "4.".bright_cyan());
95            println!("  {}  解包 WSOL → SOL", "5.".bright_cyan());
96            println!("  {}  转账 SPL 代币", "6.".bright_cyan());
97            println!("  {}  创建 Nonce 账户", "7.".bright_cyan());
98            println!("  {}  返回主菜单", "0.".bright_cyan());
99        }
100
101        let prompt = if language == Language::English {
102            "\nSelect option [0-7]: "
103        } else {
104            "\n请输入选项 [0-7]: "
105        };
106
107        let choice = read_input(prompt, "");
108
109        match choice.as_str() {
110            "1" => check_balance(keypair, language)?,
111            "2" => transfer_sol(keypair, language)?,
112            "3" => create_wsol_ata(keypair, language)?,
113            "4" => wrap_sol(keypair, language)?,
114            "5" => unwrap_sol(keypair, language)?,
115            "6" => transfer_token(keypair, language)?,
116            "7" => create_nonce_account(keypair, language)?,
117            "0" => {
118                if language == Language::English {
119                    println!("\n{}", "Returning to main menu...".bright_green());
120                } else {
121                    println!("\n{}", "返回主菜单...".bright_green());
122                }
123                return Ok(());
124            }
125            _ => {
126                let msg = if language == Language::English {
127                    "❌ Invalid option, please try again"
128                } else {
129                    "❌ 无效选项,请重试"
130                };
131                println!("\n{}", msg.red());
132            }
133        }
134    }
135}
136
137/// Placeholder for non-solana-ops builds
138#[cfg(not(feature = "solana-ops"))]
139pub fn show_operations_menu(_keypair: &Keypair, language: Language) -> Result<(), String> {
140    let msg = if language == Language::English {
141        "❌ Solana operations require the 'solana-ops' feature. Please rebuild with: cargo build --features solana-ops"
142    } else {
143        "❌ Solana 操作需要 'solana-ops' 功能。请使用以下命令重新编译: cargo build --features solana-ops"
144    };
145    Err(msg.to_string())
146}
147
148#[cfg(feature = "solana-ops")]
149pub fn check_balance(keypair: &Keypair, language: Language) -> Result<(), String> {
150    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
151    if language == Language::English {
152        println!("  {}", "📊 Check SOL Balance".bright_yellow().bold());
153    } else {
154        println!("  {}", "📊 查询 SOL 余额".bright_yellow().bold());
155    }
156    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
157
158    let network_prompt = if language == Language::English {
159        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
160    } else {
161        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
162    };
163
164    let network = read_input(network_prompt, "1");
165    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
166
167    if language == Language::English {
168        println!("\n🔍 Checking balance on {}...", if network == "2" { "Devnet" } else { "Mainnet" });
169    } else {
170        println!("\n🔍 正在查询{}余额...", if network == "2" { "测试网" } else { "主网" });
171    }
172
173    let client = SolanaClient::new(rpc_url.to_string());
174    match client.get_sol_balance(&keypair.pubkey()) {
175        Ok(balance) => {
176            let sol = lamports_to_sol(balance);
177            println!("\n{}", "✅ Balance:".bright_green());
178            println!("  💰 {} SOL", sol.to_string().bright_white().bold());
179            println!("  📊 {} lamports", balance.to_string().bright_white());
180            Ok(())
181        }
182        Err(e) => {
183            let msg = if language == Language::English {
184                format!("❌ Failed to fetch balance: {}", e)
185            } else {
186                format!("❌ 查询余额失败: {}", e)
187            };
188            Err(msg)
189        }
190    }
191}
192
193#[cfg(feature = "solana-ops")]
194pub fn transfer_sol(keypair: &Keypair, language: Language) -> Result<(), String> {
195    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
196    if language == Language::English {
197        println!("  {}", "💸 Transfer SOL".bright_yellow().bold());
198    } else {
199        println!("  {}", "💸 转账 SOL".bright_yellow().bold());
200    }
201    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
202
203    let network_prompt = if language == Language::English {
204        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
205    } else {
206        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
207    };
208
209    let network = read_input(network_prompt, "1");
210    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
211
212    let recipient_prompt = if language == Language::English {
213        "\nRecipient address: "
214    } else {
215        "\n接收地址: "
216    };
217    let recipient_str = read_input(recipient_prompt, "");
218
219    let recipient = Pubkey::from_str(&recipient_str)
220        .map_err(|_| if language == Language::English {
221            "❌ Invalid recipient address".to_string()
222        } else {
223            "❌ 无效的接收地址".to_string()
224        })?;
225
226    let amount_prompt = if language == Language::English {
227        "Amount (SOL): "
228    } else {
229        "金额 (SOL): "
230    };
231    let amount_str = read_input(amount_prompt, "");
232    let amount_sol: f64 = amount_str.parse()
233        .map_err(|_| if language == Language::English {
234            "❌ Invalid amount".to_string()
235        } else {
236            "❌ 无效的金额".to_string()
237        })?;
238
239    let amount_lamports = (amount_sol * 1_000_000_000.0) as u64;
240
241    println!("\n{}", "📋 Transaction Summary:".bright_yellow());
242    println!("  From: {}", keypair.pubkey().to_string().bright_white());
243    println!("  To: {}", recipient.to_string().bright_white());
244    println!("  Amount: {} SOL", amount_sol.to_string().bright_white().bold());
245
246    let confirm_prompt = if language == Language::English {
247        "\nConfirm transaction? (yes/no) [no]: "
248    } else {
249        "\n确认交易? (yes/no) [no]: "
250    };
251    let confirm = read_input(confirm_prompt, "no");
252
253    if confirm.to_lowercase() != "yes" {
254        let msg = if language == Language::English {
255            "❌ Transaction cancelled"
256        } else {
257            "❌ 交易已取消"
258        };
259        println!("\n{}", msg.red());
260        return Ok(());
261    }
262
263    if language == Language::English {
264        println!("\n🚀 Sending transaction...");
265    } else {
266        println!("\n🚀 正在发送交易...");
267    }
268
269    let client = SolanaClient::new(rpc_url.to_string());
270    match client.transfer_sol(keypair, &recipient, amount_lamports) {
271        Ok(signature) => {
272            println!("\n{}", "✅ Transfer successful!".bright_green().bold());
273            println!("  📝 Signature: {}", signature.to_string().bright_white());
274            let explorer_url = if network == "2" {
275                format!("https://explorer.solana.com/tx/{}?cluster=devnet", signature)
276            } else {
277                format!("https://explorer.solana.com/tx/{}", signature)
278            };
279            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
280            Ok(())
281        }
282        Err(e) => {
283            let msg = if language == Language::English {
284                format!("❌ Transfer failed: {}", e)
285            } else {
286                format!("❌ 转账失败: {}", e)
287            };
288            Err(msg)
289        }
290    }
291}
292
293/// Helper: Run async code, handling both sync and async contexts
294#[cfg(feature = "solana-ops")]
295fn run_async<F, T>(future: F) -> T
296where
297    F: std::future::Future<Output = T>,
298{
299    // Try to get current runtime handle
300    match tokio::runtime::Handle::try_current() {
301        Ok(handle) => {
302            // We're already in a runtime, use block_in_place
303            tokio::task::block_in_place(|| handle.block_on(future))
304        }
305        Err(_) => {
306            // No runtime exists, create a new one
307            let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
308            rt.block_on(future)
309        }
310    }
311}
312
313/// Helper: Calculate and print WSOL ATA address
314#[cfg(feature = "solana-ops")]
315fn print_wsol_ata_address(owner: &Pubkey, language: Language, _use_seed: bool) {
316    use sol_trade_sdk::common::fast_fn::{
317        get_associated_token_address_with_program_id_fast,
318    };
319
320    let wsol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
321    let token_program = Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap();
322
323    // SDK 强制对 WSOL 使用标准 ATA(不支持 seed 优化)
324    let ata = get_associated_token_address_with_program_id_fast(owner, &wsol_mint, &token_program);
325
326    println!("\n{}", if language == Language::English {
327        "📍 WSOL ATA Address:"
328    } else {
329        "📍 WSOL ATA 地址:"
330    }.bright_yellow());
331    println!("  {}", ata.to_string().bright_white().bold());
332}
333
334#[cfg(feature = "solana-ops")]
335pub fn create_wsol_ata(keypair: &Keypair, language: Language) -> Result<(), String> {
336    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
337    if language == Language::English {
338        println!("  {}", "🏦 Create WSOL ATA Account".bright_yellow().bold());
339    } else {
340        println!("  {}", "🏦 创建 WSOL ATA 账户".bright_yellow().bold());
341    }
342    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
343
344    let network_prompt = if language == Language::English {
345        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
346    } else {
347        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
348    };
349
350    let network = read_input(network_prompt, "1");
351    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
352
353    // WSOL 强制使用标准 ATA(SDK 设计)
354    let use_seed_optimize = false;
355
356    // 打印WSOL ATA地址
357    print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
358
359    // 检查账号是否已存在
360    if language == Language::English {
361        println!("\n🔍 Checking if account already exists...");
362    } else {
363        println!("\n🔍 检查账号是否已存在...");
364    }
365
366    let client_sdk = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
367    let wsol_balance = client_sdk.get_wsol_balance(&keypair.pubkey())
368        .unwrap_or(0);
369
370    // 如果能查到余额(即使是0),说明账号已存在
371    let rpc_client = RpcClient::new(rpc_url.to_string());
372    let wsol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
373    let token_program = Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap();
374
375    let ata = {
376        use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast;
377        get_associated_token_address_with_program_id_fast(&keypair.pubkey(), &wsol_mint, &token_program)
378    };
379
380    let account_exists = rpc_client.get_account(&ata).is_ok();
381
382    if account_exists {
383        println!("\n{}", "ℹ️  WSOL ATA account already exists!".bright_yellow().bold());
384        println!("  💰 Balance: {} lamports ({} SOL)",
385            wsol_balance.to_string().bright_white(),
386            lamports_to_sol(wsol_balance).to_string().bright_white());
387
388        if language == Language::English {
389            println!("\n✅ Account is ready to use. No need to create.");
390        } else {
391            println!("\n✅ 账号已准备就绪,无需创建。");
392        }
393        return Ok(());
394    }
395
396    println!("\n{}", if language == Language::English {
397        "ℹ️  Account does not exist. Creating new WSOL ATA..."
398    } else {
399        "ℹ️  账号不存在,将创建新的 WSOL ATA..."
400    }.bright_yellow());
401
402    let confirm_prompt = if language == Language::English {
403        "\nConfirm operation? (yes/no) [no]: "
404    } else {
405        "\n确认操作? (yes/no) [no]: "
406    };
407    let confirm = read_input(confirm_prompt, "no");
408
409    if confirm.to_lowercase() != "yes" {
410        let msg = if language == Language::English {
411            "❌ Operation cancelled"
412        } else {
413            "❌ 操作已取消"
414        };
415        println!("\n{}", msg.red());
416        return Ok(());
417    }
418
419    if language == Language::English {
420        println!("\n🚀 Creating WSOL ATA...");
421    } else {
422        println!("\n🚀 正在创建 WSOL ATA...");
423    }
424
425    // 使用run_async执行异步操作
426    match run_async(client_sdk.create_wsol_ata(keypair)) {
427        Ok(signature) => {
428            println!("\n{}", "✅ WSOL ATA created successfully!".bright_green().bold());
429            println!("  📝 Signature: {}", signature.to_string().bright_white());
430            let explorer_url = if network == "2" {
431                format!("https://explorer.solana.com/tx/{}?cluster=devnet", signature)
432            } else {
433                format!("https://explorer.solana.com/tx/{}", signature)
434            };
435            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
436            Ok(())
437        }
438        Err(e) => {
439            let msg = if language == Language::English {
440                format!("❌ Creation failed: {}", e)
441            } else {
442                format!("❌ 创建失败: {}", e)
443            };
444            Err(msg)
445        }
446    }
447}
448
449#[cfg(feature = "solana-ops")]
450pub fn wrap_sol(keypair: &Keypair, language: Language) -> Result<(), String> {
451    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
452    if language == Language::English {
453        println!("  {}", "🔄 Wrap SOL → WSOL".bright_yellow().bold());
454    } else {
455        println!("  {}", "🔄 包装 SOL → WSOL".bright_yellow().bold());
456    }
457    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
458
459    let network_prompt = if language == Language::English {
460        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
461    } else {
462        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
463    };
464
465    let network = read_input(network_prompt, "1");
466    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
467
468    // WSOL 强制使用标准 ATA(SDK 设计)
469    let use_seed_optimize = false;
470
471    // 打印WSOL ATA地址
472    print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
473
474    let amount_prompt = if language == Language::English {
475        "\nAmount to wrap (SOL): "
476    } else {
477        "\n包装金额 (SOL): "
478    };
479    let amount_str = read_input(amount_prompt, "");
480    let amount_sol: f64 = amount_str.parse()
481        .map_err(|_| if language == Language::English {
482            "❌ Invalid amount".to_string()
483        } else {
484            "❌ 无效的金额".to_string()
485        })?;
486
487    let amount_lamports = (amount_sol * 1_000_000_000.0) as u64;
488
489    println!("\n{}", "📋 Operation Summary:".bright_yellow());
490    println!("  Wrap: {} SOL → WSOL", amount_sol.to_string().bright_white().bold());
491
492    let confirm_prompt = if language == Language::English {
493        "\nConfirm operation? (yes/no) [no]: "
494    } else {
495        "\n确认操作? (yes/no) [no]: "
496    };
497    let confirm = read_input(confirm_prompt, "no");
498
499    if confirm.to_lowercase() != "yes" {
500        let msg = if language == Language::English {
501            "❌ Operation cancelled"
502        } else {
503            "❌ 操作已取消"
504        };
505        println!("\n{}", msg.red());
506        return Ok(());
507    }
508
509    if language == Language::English {
510        println!("\n🚀 Wrapping SOL...");
511    } else {
512        println!("\n🚀 正在包装 SOL...");
513    }
514
515    // 使用SolanaClientSdk调用sol-trade-sdk
516    let client = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
517
518    // 使用run_async执行异步操作
519    match run_async(client.wrap_sol(keypair, amount_lamports)) {
520        Ok(signature) => {
521            println!("\n{}", "✅ Wrap successful!".bright_green().bold());
522            println!("  📝 Signature: {}", signature.to_string().bright_white());
523            let explorer_url = if network == "2" {
524                format!("https://explorer.solana.com/tx/{}?cluster=devnet", signature)
525            } else {
526                format!("https://explorer.solana.com/tx/{}", signature)
527            };
528            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
529            Ok(())
530        }
531        Err(e) => {
532            let msg = if language == Language::English {
533                format!("❌ Wrap failed: {}", e)
534            } else {
535                format!("❌ 包装失败: {}", e)
536            };
537            Err(msg)
538        }
539    }
540}
541
542#[cfg(feature = "solana-ops")]
543pub fn unwrap_sol(keypair: &Keypair, language: Language) -> Result<(), String> {
544    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
545    if language == Language::English {
546        println!("  {}", "🔄 Unwrap WSOL → SOL".bright_yellow().bold());
547    } else {
548        println!("  {}", "🔄 解包 WSOL → SOL".bright_yellow().bold());
549    }
550    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
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" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
560
561    // WSOL 强制使用标准 ATA(SDK 设计)
562    let use_seed_optimize = false;
563
564    // 打印WSOL ATA地址
565    print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
566
567    // 询问是否指定金额
568    let amount_prompt = if language == Language::English {
569        "\nUnwrap amount (in SOL, leave empty for ALL): "
570    } else {
571        "\n解包金额(单位: SOL,留空则解包全部): "
572    };
573    let amount_input = read_input(amount_prompt, "");
574    let amount_input = amount_input.trim();
575
576    let (is_partial, amount_lamports) = if amount_input.is_empty() {
577        // 解包全部
578        println!("\n{}", if language == Language::English {
579            "ℹ️  Will unwrap ALL WSOL back to SOL"
580        } else {
581            "ℹ️  将解包所有 WSOL 回 SOL"
582        }.bright_yellow());
583        (false, 0)
584    } else {
585        // 解包指定金额
586        let amount_sol: f64 = amount_input.parse()
587            .map_err(|_| if language == Language::English {
588                "❌ Invalid amount".to_string()
589            } else {
590                "❌ 无效的金额".to_string()
591            })?;
592        
593        if amount_sol <= 0.0 {
594            return Err(if language == Language::English {
595                "❌ Amount must be greater than 0".to_string()
596            } else {
597                "❌ 金额必须大于 0".to_string()
598            });
599        }
600
601        let lamports = (amount_sol * 1_000_000_000.0) as u64;
602        println!("\n{}", if language == Language::English {
603            format!("ℹ️  Will unwrap {} SOL from WSOL", amount_sol)
604        } else {
605            format!("ℹ️  将从 WSOL 解包 {} SOL", amount_sol)
606        }.bright_yellow());
607        (true, lamports)
608    };
609
610    let confirm_prompt = if language == Language::English {
611        "\nConfirm operation? (yes/no) [no]: "
612    } else {
613        "\n确认操作? (yes/no) [no]: "
614    };
615    let confirm = read_input(confirm_prompt, "no");
616
617    if confirm.to_lowercase() != "yes" {
618        let msg = if language == Language::English {
619            "❌ Operation cancelled"
620        } else {
621            "❌ 操作已取消"
622        };
623        println!("\n{}", msg.red());
624        return Ok(());
625    }
626
627    if language == Language::English {
628        println!("\n🚀 Unwrapping WSOL...");
629    } else {
630        println!("\n🚀 正在解包 WSOL...");
631    }
632
633    // 使用SolanaClientSdk调用sol-trade-sdk
634    let client = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
635
636    // 使用run_async执行异步操作
637    let result = if is_partial {
638        run_async(client.unwrap_sol_partial(keypair, amount_lamports))
639    } else {
640        run_async(client.unwrap_sol(keypair))
641    };
642    
643    match result {
644        Ok(signature) => {
645            println!("\n{}", "✅ Unwrap successful!".bright_green().bold());
646            println!("  📝 Signature: {}", signature.to_string().bright_white());
647            let explorer_url = if network == "2" {
648                format!("https://explorer.solana.com/tx/{}?cluster=devnet", signature)
649            } else {
650                format!("https://explorer.solana.com/tx/{}", signature)
651            };
652            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
653            Ok(())
654        }
655        Err(e) => {
656            let msg = if language == Language::English {
657                format!("❌ Unwrap failed: {}", e)
658            } else {
659                format!("❌ 解包失败: {}", e)
660            };
661            Err(msg)
662        }
663    }
664}
665
666#[cfg(feature = "solana-ops")]
667/// Close WSOL ATA account and reclaim rent
668pub fn close_wsol_ata(keypair: &Keypair, language: Language) -> Result<(), String> {
669    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
670    if language == Language::English {
671        println!("  {}", "🗑️  Close WSOL ATA Account".bright_yellow().bold());
672    } else {
673        println!("  {}", "🗑️  关闭 WSOL ATA 账号".bright_yellow().bold());
674    }
675    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
676
677    let network_prompt = if language == Language::English {
678        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
679    } else {
680        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
681    };
682
683    let network = read_input(network_prompt, "1");
684    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
685
686    // WSOL 强制使用标准 ATA(SDK 设计)
687    let use_seed_optimize = false;
688
689    // 打印WSOL ATA地址
690    print_wsol_ata_address(&keypair.pubkey(), language, use_seed_optimize);
691
692    println!("\n{}", if language == Language::English {
693        "ℹ️  This operation will:"
694    } else {
695        "ℹ️  此操作将:"
696    }.bright_yellow().bold());
697    
698    if language == Language::English {
699        println!("     • Unwrap ALL WSOL back to SOL automatically");
700        println!("     • Close the WSOL ATA account");
701        println!("     • Reclaim rent (~0.00203928 SOL) to your wallet");
702    } else {
703        println!("     • 自动将所有 WSOL 解包回 SOL");
704        println!("     • 关闭 WSOL ATA 账号");
705        println!("     • 回收租金 (~0.00203928 SOL) 到您的钱包");
706    }
707
708    println!("\n{}", if language == Language::English {
709        "💰 All SOL (unwrapped WSOL + rent) will be returned to your wallet!"
710    } else {
711        "�� 所有 SOL(解包的 WSOL + 租金)将返回到您的钱包!"
712    }.green().bold());
713
714    let confirm_prompt = if language == Language::English {
715        "\nConfirm operation? (yes/no) [no]: "
716    } else {
717        "\n确认操作? (yes/no) [no]: "
718    };
719    let confirm = read_input(confirm_prompt, "no");
720
721    if confirm.to_lowercase() != "yes" {
722        let msg = if language == Language::English {
723            "❌ Operation cancelled"
724        } else {
725            "❌ 操作已取消"
726        };
727        println!("\n{}", msg.red());
728        return Ok(());
729    }
730
731    if language == Language::English {
732        println!("\n🚀 Closing WSOL ATA account...");
733    } else {
734        println!("\n🚀 正在关闭 WSOL ATA 账号...");
735    }
736
737    // 使用SolanaClientSdk调用sol-trade-sdk
738    let client = SolanaClientSdk::new(rpc_url.to_string(), use_seed_optimize);
739
740    // 使用run_async执行异步操作
741    match run_async(client.unwrap_sol(keypair)) {
742        Ok(signature) => {
743            println!("\n{}", "✅ WSOL ATA closed successfully!".bright_green().bold());
744            println!("  📝 Signature: {}", signature.to_string().bright_white());
745            let explorer_url = if network == "2" {
746                format!("https://explorer.solana.com/tx/{}?cluster=devnet", signature)
747            } else {
748                format!("https://explorer.solana.com/tx/{}", signature)
749            };
750            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
751            println!("\n{}", if language == Language::English {
752                "💰 Rent reclaimed to your wallet!"
753            } else {
754                "💰 租金已返还到您的钱包!"
755            }.green());
756            Ok(())
757        }
758        Err(e) => {
759            let msg = if language == Language::English {
760                format!("❌ Close failed: {}", e)
761            } else {
762                format!("❌ 关闭失败: {}", e)
763            };
764            Err(msg)
765        }
766    }
767}
768
769#[cfg(feature = "solana-ops")]
770pub fn transfer_token(keypair: &Keypair, language: Language) -> Result<(), String> {
771    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
772    if language == Language::English {
773        println!("  {}", "💎 Transfer SPL Token".bright_yellow().bold());
774    } else {
775        println!("  {}", "💎 转账 SPL 代币".bright_yellow().bold());
776    }
777    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
778
779    let network_prompt = if language == Language::English {
780        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
781    } else {
782        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
783    };
784
785    let network = read_input(network_prompt, "1");
786    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
787
788    let mint_prompt = if language == Language::English {
789        "\nToken Mint address: "
790    } else {
791        "\n代币 Mint 地址: "
792    };
793    let mint_str = read_input(mint_prompt, "");
794
795    let mint = Pubkey::from_str(&mint_str)
796        .map_err(|_| if language == Language::English {
797            "❌ Invalid mint address".to_string()
798        } else {
799            "❌ 无效的 Mint 地址".to_string()
800        })?;
801
802    let recipient_prompt = if language == Language::English {
803        "Recipient address: "
804    } else {
805        "接收地址: "
806    };
807    let recipient_str = read_input(recipient_prompt, "");
808
809    let recipient = Pubkey::from_str(&recipient_str)
810        .map_err(|_| if language == Language::English {
811            "❌ Invalid recipient address".to_string()
812        } else {
813            "❌ 无效的接收地址".to_string()
814        })?;
815
816    let amount_prompt = if language == Language::English {
817        "Amount (smallest units): "
818    } else {
819        "金额 (最小单位): "
820    };
821    let amount_str = read_input(amount_prompt, "");
822    let amount: u64 = amount_str.parse()
823        .map_err(|_| if language == Language::English {
824            "❌ Invalid amount".to_string()
825        } else {
826            "❌ 无效的金额".to_string()
827        })?;
828
829    println!("\n{}", "📋 Transaction Summary:".bright_yellow());
830    println!("  From: {}", keypair.pubkey().to_string().bright_white());
831    println!("  To: {}", recipient.to_string().bright_white());
832    println!("  Token: {}", mint.to_string().bright_white());
833    println!("  Amount: {} (smallest units)", amount.to_string().bright_white().bold());
834
835    let confirm_prompt = if language == Language::English {
836        "\nConfirm transaction? (yes/no) [no]: "
837    } else {
838        "\n确认交易? (yes/no) [no]: "
839    };
840    let confirm = read_input(confirm_prompt, "no");
841
842    if confirm.to_lowercase() != "yes" {
843        let msg = if language == Language::English {
844            "❌ Transaction cancelled"
845        } else {
846            "❌ 交易已取消"
847        };
848        println!("\n{}", msg.red());
849        return Ok(());
850    }
851
852    if language == Language::English {
853        println!("\n🚀 Sending transaction...");
854    } else {
855        println!("\n🚀 正在发送交易...");
856    }
857
858    let client = SolanaClient::new(rpc_url.to_string());
859    match client.transfer_token(keypair, &recipient, &mint, amount) {
860        Ok(signature) => {
861            println!("\n{}", "✅ Transfer successful!".bright_green().bold());
862            println!("  📝 Signature: {}", signature.to_string().bright_white());
863            let explorer_url = if network == "2" {
864                format!("https://explorer.solana.com/tx/{}?cluster=devnet", signature)
865            } else {
866                format!("https://explorer.solana.com/tx/{}", signature)
867            };
868            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
869            Ok(())
870        }
871        Err(e) => {
872            let msg = if language == Language::English {
873                format!("❌ Transfer failed: {}", e)
874            } else {
875                format!("❌ 转账失败: {}", e)
876            };
877            Err(msg)
878        }
879    }
880}
881
882#[cfg(feature = "solana-ops")]
883pub fn create_nonce_account(keypair: &Keypair, language: Language) -> Result<(), String> {
884    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
885    if language == Language::English {
886        println!("  {}", "🔑 Create Nonce Account".bright_yellow().bold());
887    } else {
888        println!("  {}", "🔑 创建 Nonce 账户".bright_yellow().bold());
889    }
890    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_cyan());
891
892    let network_prompt = if language == Language::English {
893        "\nSelect network:\n  1. Mainnet\n  2. Devnet\nChoice [1]: "
894    } else {
895        "\n选择网络:\n  1. 主网 (Mainnet)\n  2. 测试网 (Devnet)\n选择 [1]: "
896    };
897
898    let network = read_input(network_prompt, "1");
899    let rpc_url = if network == "2" { DEVNET_RPC_URL } else { DEFAULT_RPC_URL };
900
901    println!("\n{}", if language == Language::English {
902        "ℹ️  A nonce account will be created for durable transactions"
903    } else {
904        "ℹ️  将创建一个用于持久交易的 Nonce 账户"
905    }.bright_yellow());
906
907    println!("{}", if language == Language::English {
908        "ℹ️  This requires ~0.00144 SOL for rent exemption"
909    } else {
910        "ℹ️  这需要约 0.00144 SOL 用于租金豁免"
911    }.bright_yellow());
912
913    let confirm_prompt = if language == Language::English {
914        "\nConfirm creation? (yes/no) [no]: "
915    } else {
916        "\n确认创建? (yes/no) [no]: "
917    };
918    let confirm = read_input(confirm_prompt, "no");
919
920    if confirm.to_lowercase() != "yes" {
921        let msg = if language == Language::English {
922            "❌ Operation cancelled"
923        } else {
924            "❌ 操作已取消"
925        };
926        println!("\n{}", msg.red());
927        return Ok(());
928    }
929
930    if language == Language::English {
931        println!("\n🚀 Creating nonce account...");
932    } else {
933        println!("\n🚀 正在创建 Nonce 账户...");
934    }
935
936    let client = SolanaClient::new(rpc_url.to_string());
937    match client.create_nonce_account(keypair) {
938        Ok((nonce_pubkey, signature)) => {
939            println!("\n{}", "✅ Nonce account created successfully!".bright_green().bold());
940            println!("  🔑 Nonce Account: {}", nonce_pubkey.to_string().bright_white().bold());
941            println!("  📝 Signature: {}", signature.to_string().bright_white());
942            let explorer_url = if network == "2" {
943                format!("https://explorer.solana.com/address/{}?cluster=devnet", nonce_pubkey)
944            } else {
945                format!("https://explorer.solana.com/address/{}", nonce_pubkey)
946            };
947            println!("  🔗 Explorer: {}", explorer_url.bright_blue());
948            println!("\n{}", if language == Language::English {
949                "💡 Save this nonce account address for future use!"
950            } else {
951                "💡 请保存此 Nonce 账户地址以供将来使用!"
952            }.bright_yellow());
953            Ok(())
954        }
955        Err(e) => {
956            let msg = if language == Language::English {
957                format!("❌ Creation failed: {}", e)
958            } else {
959                format!("❌ 创建失败: {}", e)
960            };
961            Err(msg)
962        }
963    }
964}
965
966/// Entry point for Solana operations from interactive menu
967/// Prompts for keystore file and password, then shows operations menu
968pub fn show_solana_operations_menu(language: crate::interactive::Language) -> Result<(), String> {
969    #[cfg(feature = "solana-ops")]
970    {
971        use crate::KeyManager;
972
973        // Convert language from interactive module to operations module
974        let ops_language = match language {
975            crate::interactive::Language::English => Language::English,
976            crate::interactive::Language::Chinese => Language::Chinese,
977        };
978
979        // Prompt for keystore file
980        let file_prompt = if ops_language == Language::English {
981            "Keystore file path (default: keystore.json): "
982        } else {
983            "Keystore 文件路径 (默认: keystore.json): "
984        };
985
986        let file_path = read_input(file_prompt, "keystore.json");
987
988        // Check if file exists
989        if !std::path::Path::new(&file_path).exists() {
990            let err_msg = if ops_language == Language::English {
991                format!("❌ File not found: {}", file_path)
992            } else {
993                format!("❌ 文件不存在: {}", file_path)
994            };
995            return Err(err_msg);
996        }
997
998        // Prompt for password
999        let password_prompt = if ops_language == Language::English {
1000            "Enter password: "
1001        } else {
1002            "请输入密码: "
1003        };
1004
1005        print!("{}", password_prompt);
1006        io::stdout().flush().map_err(|e| e.to_string())?;
1007        let password = rpassword::read_password()
1008            .map_err(|e| format!("Failed to read password: {}", e))?;
1009
1010        // Load keystore
1011        let keystore_json = std::fs::read_to_string(&file_path)
1012            .map_err(|e| {
1013                if ops_language == Language::English {
1014                    format!("❌ Failed to read file: {}", e)
1015                } else {
1016                    format!("❌ 读取文件失败: {}", e)
1017                }
1018            })?;
1019
1020        let keypair = KeyManager::keypair_from_encrypted_json(&keystore_json, &password)
1021            .map_err(|e| {
1022                if ops_language == Language::English {
1023                    format!("❌ Failed to decrypt keystore: {}", e)
1024                } else {
1025                    format!("❌ 解密 keystore 失败: {}", e)
1026                }
1027            })?;
1028
1029        println!("\n{}", if ops_language == Language::English {
1030            "✅ Wallet unlocked successfully!"
1031        } else {
1032            "✅ 钱包解锁成功!"
1033        }.bright_green());
1034
1035        // Run synchronous operations menu
1036        show_operations_menu(&keypair, ops_language)
1037    }
1038
1039    #[cfg(not(feature = "solana-ops"))]
1040    {
1041        let _ = language; // Suppress unused variable warning
1042        Err("Solana operations require the 'solana-ops' feature".to_string())
1043    }
1044}
1045
1046/// PumpSwap 交互式卖出
1047#[cfg(feature = "sol-trade-sdk")]
1048pub fn pumpswap_sell_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1049    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1050    if language == Language::English {
1051        println!("  {}", "🔥 PumpSwap Sell Tokens".bright_magenta().bold());
1052    } else {
1053        println!("  {}", "🔥 PumpSwap 卖出代币".bright_magenta().bold());
1054    }
1055    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1056
1057    println!("\n{}", if language == Language::English {
1058        "Current Wallet:"
1059    } else {
1060        "当前钱包:"
1061    }.bright_green());
1062    println!("  📍 {}", keypair.pubkey().to_string().bright_white());
1063
1064    // Step 1: 输入 RPC URL(可选)
1065    println!();
1066    let rpc_prompt = if language == Language::English {
1067        format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
1068    } else {
1069        format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
1070    };
1071    let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
1072
1073    // Step 2: 询问是否使用 seed 优化
1074    println!();
1075    if language == Language::English {
1076        println!("{}", "🔧 Seed Optimization Configuration".bright_cyan());
1077        println!("   Seed optimization is used to create optimized ATA addresses");
1078        println!("   If your token ATA was created using the standard method, choose 'no'");
1079        println!("   If unsure, it's recommended to choose 'no'");
1080    } else {
1081        println!("{}", "🔧 Seed 优化配置".bright_cyan());
1082        println!("   Seed 优化用于创建优化的 ATA 地址,可以节省交易费用");
1083        println!("   如果你的代币 ATA 是通过标准方式创建的,请选择 'no'");
1084        println!("   如果不确定,建议选择 'yes'(默认)");
1085    }
1086
1087    print!("\n{} ", if language == Language::English {
1088        "❓ Enable Seed Optimization? (yes/no, default: yes):"
1089    } else {
1090        "❓ 启用 Seed 优化? (yes/no, 默认 yes):"
1091    }.yellow());
1092    io::stdout().flush().map_err(|e| e.to_string())?;
1093
1094    let mut seed_input = String::new();
1095    io::stdin().read_line(&mut seed_input).map_err(|e| e.to_string())?;
1096    let seed_input_trimmed = seed_input.trim().to_lowercase();
1097    // 默认为 yes:空输入或 yes/y 都启用,只有明确输入 no/n 才禁用
1098    let use_seed = seed_input_trimmed.is_empty() || seed_input_trimmed == "yes" || seed_input_trimmed == "y";
1099
1100    if use_seed {
1101        println!("{}", if language == Language::English {
1102            "✅ Seed optimization enabled"
1103        } else {
1104            "✅ 已启用 Seed 优化"
1105        }.green());
1106    } else {
1107        println!("{}", if language == Language::English {
1108            "✅ Using standard ATA"
1109        } else {
1110            "✅ 使用标准 ATA"
1111        }.green());
1112    }
1113
1114    // Step 3: 输入 token mint 地址(支持多个,用逗号或空格分割)
1115    println!();
1116    if language == Language::English {
1117        println!("{}", "💡 You can enter multiple mint addresses separated by commas or spaces".bright_cyan());
1118        println!("   Tokens will be sold in the order entered");
1119    } else {
1120        println!("{}", "💡 可以输入多个 Mint 地址,用逗号或空格分割".bright_cyan());
1121        println!("   将按输入顺序依次卖出");
1122    }
1123
1124    let mint_prompt = if language == Language::English {
1125        "\nToken Mint Address(es): "
1126    } else {
1127        "\n代币 Mint 地址: "
1128    };
1129    print!("{}", mint_prompt.yellow());
1130    io::stdout().flush().map_err(|e| e.to_string())?;
1131
1132    let mut mint_input = String::new();
1133    io::stdin().read_line(&mut mint_input).map_err(|e| e.to_string())?;
1134    let mint_input = mint_input.trim();
1135
1136    if mint_input.is_empty() {
1137        return Err(if language == Language::English {
1138            "Token mint address cannot be empty".to_string()
1139        } else {
1140            "代币 Mint 地址不能为空".to_string()
1141        });
1142    }
1143
1144    // 解析多个 mint 地址(支持逗号和空格分割)
1145    let mint_addresses: Vec<String> = mint_input
1146        .split(|c: char| c == ',' || c.is_whitespace())
1147        .map(|s| s.trim())
1148        .filter(|s| !s.is_empty())
1149        .map(|s| s.to_string())
1150        .collect();
1151
1152    if mint_addresses.is_empty() {
1153        return Err(if language == Language::English {
1154            "No valid mint addresses found".to_string()
1155        } else {
1156            "未找到有效的 Mint 地址".to_string()
1157        });
1158    }
1159
1160    // 验证所有 mint 地址格式
1161    for (idx, mint) in mint_addresses.iter().enumerate() {
1162        if let Err(e) = Pubkey::from_str(mint) {
1163            return Err(if language == Language::English {
1164                format!("Invalid mint address #{}: {} (error: {})", idx + 1, mint, e)
1165            } else {
1166                format!("无效的 Mint 地址 #{}: {} (错误: {})", idx + 1, mint, e)
1167            });
1168        }
1169    }
1170
1171    // 显示将要处理的 mint 地址
1172    println!();
1173    if language == Language::English {
1174        println!("{}", format!("📋 Found {} token(s) to sell:", mint_addresses.len()).bright_green());
1175    } else {
1176        println!("{}", format!("📋 找到 {} 个代币待卖出:", mint_addresses.len()).bright_green());
1177    }
1178    for (idx, mint) in mint_addresses.iter().enumerate() {
1179        println!("   {}. {}", idx + 1, mint.bright_white());
1180    }
1181
1182    // Step 4: 使用默认滑点 99%
1183    let slippage = 9900u64;
1184    println!();
1185    if language == Language::English {
1186        println!("📊 Slippage tolerance: {}%", slippage as f64 / 100.0);
1187    } else {
1188        println!("📊 滑点容忍度: {}%", slippage as f64 / 100.0);
1189    }
1190
1191    // Step 4.5: 批量卖出前统一确认
1192    let total_mints = mint_addresses.len();
1193    if total_mints > 1 {
1194        println!();
1195        if language == Language::English {
1196            println!("{}", format!("⚠️  You are about to sell {} tokens", total_mints).yellow().bold());
1197            println!("   All tokens will be sold automatically without individual confirmation");
1198        } else {
1199            println!("{}", format!("⚠️  您即将卖出 {} 个代币", total_mints).yellow().bold());
1200            println!("   所有代币将自动卖出,不会逐个确认");
1201        }
1202
1203        print!("\n{}", if language == Language::English {
1204            "❓ Confirm batch sell? (yes/no, default: yes): "
1205        } else {
1206            "❓ 确认批量卖出? (yes/no, 默认 yes): "
1207        }.yellow());
1208        io::stdout().flush().map_err(|e| e.to_string())?;
1209
1210        let mut confirm = String::new();
1211        io::stdin().read_line(&mut confirm).map_err(|e| e.to_string())?;
1212        let confirm_trimmed = confirm.trim().to_lowercase();
1213
1214        if confirm_trimmed == "no" || confirm_trimmed == "n" {
1215            return Err(if language == Language::English {
1216                "❌ Batch sell cancelled".to_string()
1217            } else {
1218                "❌ 批量卖出已取消".to_string()
1219            });
1220        }
1221    }
1222
1223    // Step 5: 循环处理每个 mint 地址
1224    // 批量卖出时跳过单个确认(skip_confirmation=true)
1225    let skip_confirmation = total_mints > 1;
1226    for (idx, mint) in mint_addresses.iter().enumerate() {
1227        println!();
1228        println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1229        if language == Language::English {
1230            println!("{}", format!("🚀 Processing token {}/{}", idx + 1, total_mints).bright_blue());
1231            println!("   Mint: {}", mint.bright_white());
1232        } else {
1233            println!("{}", format!("🚀 处理第 {}/{} 个代币", idx + 1, total_mints).bright_blue());
1234            println!("   Mint: {}", mint.bright_white());
1235        }
1236        println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1237
1238        // 使用当前 tokio runtime 执行异步操作
1239        let result = match tokio::runtime::Handle::try_current() {
1240            Ok(handle) => {
1241                // 在当前运行时中执行
1242                let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
1243                let mint_clone = mint.to_string();
1244                let rpc_url_clone = rpc_url.clone();
1245
1246                std::thread::spawn(move || {
1247                    let keypair_clone = Keypair::from_base58_string(&keypair_b58);
1248                    handle.block_on(async move {
1249                        crate::solana_utils::pumpswap_sell::handle_pumpswap_sell_no_prompt(
1250                            &keypair_clone,
1251                            &mint_clone,
1252                            &rpc_url_clone,
1253                            slippage,
1254                            use_seed,
1255                            language,
1256                            skip_confirmation,  // 传入 skip_confirmation 参数
1257                        ).await
1258                    })
1259                })
1260                .join()
1261                .map_err(|_| "Thread panicked".to_string())?
1262            }
1263            Err(_) => {
1264                // 没有运行时,创建新的
1265                let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
1266                rt.block_on(async {
1267                    crate::solana_utils::pumpswap_sell::handle_pumpswap_sell_no_prompt(
1268                        keypair,
1269                        mint,
1270                        &rpc_url,
1271                        slippage,
1272                        use_seed,
1273                        language,
1274                        skip_confirmation,  // 传入 skip_confirmation 参数
1275                    ).await
1276                })
1277            }
1278        };
1279
1280        // 处理结果
1281        match result {
1282            Ok(_) => {
1283                if language == Language::English {
1284                    println!("\n{}", format!("✅ Token {}/{} sold successfully", idx + 1, total_mints).bright_green());
1285                } else {
1286                    println!("\n{}", format!("✅ 第 {}/{} 个代币卖出成功", idx + 1, total_mints).bright_green());
1287                }
1288            }
1289            Err(e) => {
1290                if language == Language::English {
1291                    println!("\n{}", format!("❌ Token {}/{} failed: {}", idx + 1, total_mints, e).bright_red());
1292                    println!("   Continuing with next token...");
1293                } else {
1294                    println!("\n{}", format!("❌ 第 {}/{} 个代币卖出失败: {}", idx + 1, total_mints, e).bright_red());
1295                    println!("   继续处理下一个代币...");
1296                }
1297            }
1298        }
1299
1300        // 如果不是最后一个,添加延迟
1301        if idx < total_mints - 1 {
1302            println!();
1303            if language == Language::English {
1304                println!("⏳ Waiting 2 seconds before next transaction...");
1305            } else {
1306                println!("⏳ 等待 2 秒后处理下一个交易...");
1307            }
1308            std::thread::sleep(std::time::Duration::from_secs(2));
1309        }
1310    }
1311
1312    // 所有交易完成
1313    println!();
1314    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1315    if language == Language::English {
1316        println!("{}", "🎉 All transactions completed!".bright_green().bold());
1317    } else {
1318        println!("{}", "🎉 所有交易已完成!".bright_green().bold());
1319    }
1320    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1321
1322    Ok(())
1323}
1324
1325#[cfg(not(feature = "sol-trade-sdk"))]
1326pub fn pumpswap_sell_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
1327    Err(if language == Language::English {
1328        "PumpSwap sell requires 'sol-trade-sdk' feature. Please rebuild with:\ncargo build --release --features sol-trade-sdk".to_string()
1329    } else {
1330        "PumpSwap 卖出需要 'sol-trade-sdk' 功能。请使用以下命令重新编译:\ncargo build --release --features sol-trade-sdk".to_string()
1331    })
1332}
1333
1334/// Pump.fun 内盘(bonding curve)交互式卖出
1335#[cfg(feature = "sol-trade-sdk")]
1336pub fn pumpfun_sell_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1337    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1338    if language == Language::English {
1339        println!("  {}", "🔥 Pump.fun Bonding Curve Sell".bright_magenta().bold());
1340    } else {
1341        println!("  {}", "🔥 Pump.fun 内盘卖出代币".bright_magenta().bold());
1342    }
1343    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1344
1345    println!("\n{}", if language == Language::English {
1346        "Current Wallet:"
1347    } else {
1348        "当前钱包:"
1349    }.bright_green());
1350    println!("  📍 {}", keypair.pubkey().to_string().bright_white());
1351
1352    let rpc_prompt = if language == Language::English {
1353        format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
1354    } else {
1355        format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
1356    };
1357    let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
1358
1359    println!();
1360    if language == Language::English {
1361        println!("{}", "🔧 Seed Optimization Configuration".bright_cyan());
1362        println!("   If your token ATA was created using the standard method, choose 'no'");
1363        println!("   If unsure, it's recommended to choose 'no'");
1364    } else {
1365        println!("{}", "🔧 Seed 优化配置".bright_cyan());
1366        println!("   如果你的代币 ATA 是通过标准方式创建的,请选择 'no'");
1367        println!("   如果不确定,建议选择 'yes'(默认)");
1368    }
1369
1370    print!("\n{} ", if language == Language::English {
1371        "❓ Enable Seed Optimization? (yes/no, default: yes):"
1372    } else {
1373        "❓ 启用 Seed 优化? (yes/no, 默认 yes):"
1374    }.yellow());
1375    io::stdout().flush().map_err(|e| e.to_string())?;
1376
1377    let mut seed_input = String::new();
1378    io::stdin().read_line(&mut seed_input).map_err(|e| e.to_string())?;
1379    let seed_input_trimmed = seed_input.trim().to_lowercase();
1380    let use_seed = seed_input_trimmed.is_empty() || seed_input_trimmed == "yes" || seed_input_trimmed == "y";
1381
1382    if language == Language::English {
1383        println!("{}", "💡 You can enter multiple mint addresses separated by commas or spaces".bright_cyan());
1384    } else {
1385        println!("{}", "💡 可以输入多个 Mint 地址,用逗号或空格分割".bright_cyan());
1386    }
1387
1388    let mint_prompt = if language == Language::English {
1389        "\nToken Mint Address(es): "
1390    } else {
1391        "\n代币 Mint 地址: "
1392    };
1393    print!("{}", mint_prompt.yellow());
1394    io::stdout().flush().map_err(|e| e.to_string())?;
1395
1396    let mut mint_input = String::new();
1397    io::stdin().read_line(&mut mint_input).map_err(|e| e.to_string())?;
1398    let mint_input = mint_input.trim();
1399
1400    if mint_input.is_empty() {
1401        return Err(if language == Language::English {
1402            "Token mint address cannot be empty".to_string()
1403        } else {
1404            "代币 Mint 地址不能为空".to_string()
1405        });
1406    }
1407
1408    let mint_addresses: Vec<String> = mint_input
1409        .split(|c: char| c == ',' || c.is_whitespace())
1410        .map(|s| s.trim())
1411        .filter(|s| !s.is_empty())
1412        .map(|s| s.to_string())
1413        .collect();
1414
1415    if mint_addresses.is_empty() {
1416        return Err(if language == Language::English {
1417            "No valid mint addresses found".to_string()
1418        } else {
1419            "未找到有效的 Mint 地址".to_string()
1420        });
1421    }
1422
1423    for (idx, mint) in mint_addresses.iter().enumerate() {
1424        if let Err(e) = Pubkey::from_str(mint) {
1425            return Err(if language == Language::English {
1426                format!("Invalid mint address #{}: {} (error: {})", idx + 1, mint, e)
1427            } else {
1428                format!("无效的 Mint 地址 #{}: {} (错误: {})", idx + 1, mint, e)
1429            });
1430        }
1431    }
1432
1433    println!();
1434    if language == Language::English {
1435        println!("{}", format!("📋 Found {} token(s) to sell:", mint_addresses.len()).bright_green());
1436    } else {
1437        println!("{}", format!("📋 找到 {} 个代币待卖出:", mint_addresses.len()).bright_green());
1438    }
1439    for (idx, mint) in mint_addresses.iter().enumerate() {
1440        println!("   {}. {}", idx + 1, mint.bright_white());
1441    }
1442
1443    let slippage = 9900u64;
1444    println!();
1445    if language == Language::English {
1446        println!("📊 Slippage tolerance: {}%", slippage as f64 / 100.0);
1447    } else {
1448        println!("📊 滑点容忍度: {}%", slippage as f64 / 100.0);
1449    }
1450
1451    let total_mints = mint_addresses.len();
1452    if total_mints > 1 {
1453        println!();
1454        if language == Language::English {
1455            println!("{}", format!("⚠️  You are about to sell {} tokens", total_mints).yellow().bold());
1456        } else {
1457            println!("{}", format!("⚠️  您即将卖出 {} 个代币", total_mints).yellow().bold());
1458        }
1459
1460        print!("\n{}", if language == Language::English {
1461            "❓ Confirm batch sell? (yes/no, default: yes): "
1462        } else {
1463            "❓ 确认批量卖出? (yes/no, 默认 yes): "
1464        }.yellow());
1465        io::stdout().flush().map_err(|e| e.to_string())?;
1466
1467        let mut confirm = String::new();
1468        io::stdin().read_line(&mut confirm).map_err(|e| e.to_string())?;
1469        let confirm_trimmed = confirm.trim().to_lowercase();
1470
1471        if confirm_trimmed == "no" || confirm_trimmed == "n" {
1472            return Err(if language == Language::English {
1473                "❌ Batch sell cancelled".to_string()
1474            } else {
1475                "❌ 批量卖出已取消".to_string()
1476            });
1477        }
1478    }
1479
1480    let skip_confirmation = total_mints > 1;
1481    for (idx, mint) in mint_addresses.iter().enumerate() {
1482        println!();
1483        println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1484        if language == Language::English {
1485            println!("{}", format!("🚀 Processing token {}/{}", idx + 1, total_mints).bright_blue());
1486            println!("   Mint: {}", mint.bright_white());
1487        } else {
1488            println!("{}", format!("🚀 处理第 {}/{} 个代币", idx + 1, total_mints).bright_blue());
1489            println!("   Mint: {}", mint.bright_white());
1490        }
1491        println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1492
1493        let result = match tokio::runtime::Handle::try_current() {
1494            Ok(handle) => {
1495                let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
1496                let mint_clone = mint.to_string();
1497                let rpc_url_clone = rpc_url.clone();
1498
1499                std::thread::spawn(move || {
1500                    let keypair_clone = Keypair::from_base58_string(&keypair_b58);
1501                    handle.block_on(async move {
1502                        crate::solana_utils::pumpfun_sell::handle_pumpfun_sell_no_prompt(
1503                            &keypair_clone,
1504                            &mint_clone,
1505                            &rpc_url_clone,
1506                            slippage,
1507                            use_seed,
1508                            language,
1509                            skip_confirmation,
1510                        ).await
1511                    })
1512                })
1513                .join()
1514                .map_err(|_| "Thread panicked".to_string())?
1515            }
1516            Err(_) => {
1517                let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
1518                rt.block_on(async {
1519                    crate::solana_utils::pumpfun_sell::handle_pumpfun_sell_no_prompt(
1520                        keypair,
1521                        mint,
1522                        &rpc_url,
1523                        slippage,
1524                        use_seed,
1525                        language,
1526                        skip_confirmation,
1527                    ).await
1528                })
1529            }
1530        };
1531
1532        match result {
1533            Ok(_) => {
1534                if language == Language::English {
1535                    println!("\n{}", format!("✅ Token {}/{} sold successfully", idx + 1, total_mints).bright_green());
1536                } else {
1537                    println!("\n{}", format!("✅ 第 {}/{} 个代币卖出成功", idx + 1, total_mints).bright_green());
1538                }
1539            }
1540            Err(e) => {
1541                if language == Language::English {
1542                    println!("\n{}", format!("❌ Token {}/{} failed: {}", idx + 1, total_mints, e).bright_red());
1543                } else {
1544                    println!("\n{}", format!("❌ 第 {}/{} 个代币卖出失败: {}", idx + 1, total_mints, e).bright_red());
1545                }
1546            }
1547        }
1548
1549        if idx < total_mints - 1 {
1550            println!();
1551            std::thread::sleep(std::time::Duration::from_secs(2));
1552        }
1553    }
1554
1555    println!();
1556    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1557    if language == Language::English {
1558        println!("{}", "🎉 All transactions completed!".bright_green().bold());
1559    } else {
1560        println!("{}", "🎉 所有交易已完成!".bright_green().bold());
1561    }
1562    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1563
1564    Ok(())
1565}
1566
1567/// Pump (Pump.fun) 返现:查询余额 → 显示 → 确认 → 领取(原生 SOL)
1568#[cfg(feature = "sol-trade-sdk")]
1569pub fn pumpfun_cashback_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1570    use std::sync::Arc;
1571    use sol_trade_sdk::{common::TradeConfig, SolanaTrade};
1572    use solana_commitment_config::CommitmentConfig;
1573
1574    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1575    if language == Language::English {
1576        println!("  {}", "💰 Pump (Pump.fun) Cashback – View & Claim".bright_magenta().bold());
1577        println!("  {}", "   Cashback is native SOL from trading on Pump.fun.".bright_white());
1578    } else {
1579        println!("  {}", "💰 Pump (Pump.fun) 返现 – 查看与领取".bright_magenta().bold());
1580        println!("  {}", "   返现为在 Pump.fun 交易累积的原生 SOL。".bright_white());
1581    }
1582    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1583
1584    println!("\n{}", if language == Language::English {
1585        "Current Wallet:"
1586    } else {
1587        "当前钱包:"
1588    }.bright_green());
1589    println!("  📍 {}", keypair.pubkey().to_string().bright_white());
1590
1591    let rpc_prompt = if language == Language::English {
1592        format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
1593    } else {
1594        format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
1595    };
1596    let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
1597
1598    // Step 1: 查询 UserVolumeAccumulator PDA 余额
1599    if language == Language::English {
1600        println!("\n{}", "🔍 Querying cashback balance...".bright_cyan());
1601    } else {
1602        println!("\n{}", "🔍 正在查询返现余额...".bright_cyan());
1603    }
1604
1605    let pda = sol_trade_sdk::instruction::utils::pumpfun::get_user_volume_accumulator_pda(&keypair.pubkey())
1606        .ok_or_else(|| if language == Language::English {
1607            "Failed to derive UserVolumeAccumulator PDA".to_string()
1608        } else {
1609            "无法派生 UserVolumeAccumulator PDA 地址".to_string()
1610        })?;
1611
1612    let rpc_client = RpcClient::new(rpc_url.clone());
1613    let pda_balance_lamports = rpc_client.get_balance(&pda).unwrap_or(0);
1614    let rent_exempt_min: u64 = 890_880;
1615    let claimable_lamports = pda_balance_lamports.saturating_sub(rent_exempt_min);
1616    let claimable_sol = claimable_lamports as f64 / 1_000_000_000.0;
1617
1618    // Step 2: 显示余额
1619    println!("\n{}", if language == Language::English {
1620        "📊 Cashback Info:"
1621    } else {
1622        "📊 返现信息:"
1623    }.bright_yellow());
1624    println!("  PDA: {}", pda.to_string().bright_white());
1625    if language == Language::English {
1626        println!("  PDA Balance: {} lamports ({:.9} SOL)", pda_balance_lamports, pda_balance_lamports as f64 / 1e9);
1627        println!("  Claimable:   {} lamports ({:.9} SOL)", claimable_lamports, claimable_sol);
1628    } else {
1629        println!("  PDA 余额:    {} lamports ({:.9} SOL)", pda_balance_lamports, pda_balance_lamports as f64 / 1e9);
1630        println!("  可领取:      {} lamports ({:.9} SOL)", claimable_lamports, claimable_sol);
1631    }
1632
1633    if claimable_lamports == 0 {
1634        if language == Language::English {
1635            println!("\n{}", "ℹ️  No cashback available to claim.".bright_yellow());
1636        } else {
1637            println!("\n{}", "ℹ️  暂无可领取的返现。".bright_yellow());
1638        }
1639        return Ok(());
1640    }
1641
1642    // Step 3: 确认是否领取
1643    let confirm_prompt = if language == Language::English {
1644        format!("\nClaim {:.9} SOL? (yes/no) [no]: ", claimable_sol)
1645    } else {
1646        format!("\n领取 {:.9} SOL? (yes/no) [no]: ", claimable_sol)
1647    };
1648    let confirm = read_input(&confirm_prompt, "no");
1649
1650    if confirm.to_lowercase() != "yes" {
1651        let msg = if language == Language::English {
1652            "❌ Claim cancelled"
1653        } else {
1654            "❌ 已取消领取"
1655        };
1656        println!("\n{}", msg.red());
1657        return Ok(());
1658    }
1659
1660    // Step 4: 执行领取
1661    if language == Language::English {
1662        println!("\n{}", "🚀 Claiming...".bright_cyan());
1663    } else {
1664        println!("\n{}", "🚀 正在领取...".bright_cyan());
1665    }
1666
1667    let sig = match tokio::runtime::Handle::try_current() {
1668        Ok(handle) => {
1669            let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
1670            let rpc_url_clone = rpc_url.clone();
1671            std::thread::spawn(move || {
1672                let kp = Keypair::from_base58_string(&keypair_b58);
1673                let payer = Arc::new(kp);
1674                let config = TradeConfig::builder(
1675                    rpc_url_clone.clone(),
1676                    vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url_clone)],
1677                    CommitmentConfig::confirmed(),
1678                )
1679                .create_wsol_ata_on_startup(false)
1680                .use_seed_optimize(false)
1681                .check_min_tip(false)
1682                .log_enabled(false)
1683                .swqos_cores_from_end(false)
1684                .mev_protection(false)
1685                .build();
1686                handle.block_on(async move {
1687                    let client = SolanaTrade::new(payer, config).await;
1688                    client.claim_cashback_pumpfun().await.map_err(|e| e.to_string())
1689                })
1690            })
1691            .join()
1692            .map_err(|_| "Thread panicked".to_string())??
1693        }
1694        Err(_) => {
1695            let payer = Arc::new(keypair.insecure_clone());
1696            let config = TradeConfig::builder(
1697                rpc_url.clone(),
1698                vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url)],
1699                CommitmentConfig::confirmed(),
1700            )
1701            .create_wsol_ata_on_startup(false)
1702            .use_seed_optimize(false)
1703            .check_min_tip(false)
1704            .log_enabled(false)
1705            .swqos_cores_from_end(false)
1706            .mev_protection(false)
1707            .build();
1708            let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
1709            let client = rt.block_on(SolanaTrade::new(payer, config));
1710            rt.block_on(client.claim_cashback_pumpfun()).map_err(|e| e.to_string())?
1711        }
1712    };
1713
1714    if language == Language::English {
1715        println!("{}", "✅ Claim successful!".bright_green().bold());
1716        println!("Signature: {}", sig.yellow());
1717        println!("Explorer: https://solscan.io/tx/{}", sig);
1718    } else {
1719        println!("{}", "✅ 领取成功!".bright_green().bold());
1720        println!("签名: {}", sig.yellow());
1721        println!("浏览器: https://solscan.io/tx/{}", sig);
1722    }
1723    Ok(())
1724}
1725
1726/// PumpSwap 返现:查询余额 → 显示 → 确认 → 领取(WSOL)
1727#[cfg(feature = "sol-trade-sdk")]
1728pub fn pumpswap_cashback_interactive(keypair: &Keypair, language: Language) -> Result<(), String> {
1729    use std::sync::Arc;
1730    use sol_trade_sdk::{common::TradeConfig, SolanaTrade};
1731    use solana_commitment_config::CommitmentConfig;
1732
1733    println!("\n{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1734    if language == Language::English {
1735        println!("  {}", "💰 PumpSwap Cashback – View & Claim".bright_magenta().bold());
1736        println!("  {}", "   Cashback is WSOL from trading on PumpSwap.".bright_white());
1737    } else {
1738        println!("  {}", "💰 PumpSwap 返现 – 查看与领取".bright_magenta().bold());
1739        println!("  {}", "   返现为在 PumpSwap 交易累积的 WSOL。".bright_white());
1740    }
1741    println!("{}", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".bright_magenta());
1742
1743    println!("\n{}", if language == Language::English {
1744        "Current Wallet:"
1745    } else {
1746        "当前钱包:"
1747    }.bright_green());
1748    println!("  📍 {}", keypair.pubkey().to_string().bright_white());
1749
1750    let rpc_prompt = if language == Language::English {
1751        format!("RPC URL (default: {}): ", DEFAULT_RPC_URL)
1752    } else {
1753        format!("RPC URL (默认: {}): ", DEFAULT_RPC_URL)
1754    };
1755    let rpc_url = read_input(&rpc_prompt, DEFAULT_RPC_URL);
1756
1757    // Step 1: 查询 PumpSwap UserVolumeAccumulator 的 WSOL ATA 余额
1758    if language == Language::English {
1759        println!("\n{}", "🔍 Querying cashback balance...".bright_cyan());
1760    } else {
1761        println!("\n{}", "🔍 正在查询返现余额...".bright_cyan());
1762    }
1763
1764    let wsol_ata = sol_trade_sdk::instruction::utils::pumpswap::get_user_volume_accumulator_wsol_ata(&keypair.pubkey())
1765        .ok_or_else(|| if language == Language::English {
1766            "Failed to derive PumpSwap UserVolumeAccumulator WSOL ATA".to_string()
1767        } else {
1768            "无法派生 PumpSwap UserVolumeAccumulator WSOL ATA 地址".to_string()
1769        })?;
1770
1771    let rpc_client = RpcClient::new(rpc_url.clone());
1772    let claimable_lamports = match rpc_client.get_token_account_balance(&wsol_ata) {
1773        Ok(balance) => balance.amount.parse::<u64>().unwrap_or(0),
1774        Err(_) => 0,
1775    };
1776    let claimable_sol = claimable_lamports as f64 / 1_000_000_000.0;
1777
1778    // Step 2: 显示余额
1779    println!("\n{}", if language == Language::English {
1780        "📊 Cashback Info:"
1781    } else {
1782        "📊 返现信息:"
1783    }.bright_yellow());
1784    println!("  WSOL ATA: {}", wsol_ata.to_string().bright_white());
1785    if language == Language::English {
1786        println!("  Claimable: {} lamports ({:.9} SOL)", claimable_lamports, claimable_sol);
1787    } else {
1788        println!("  可领取:   {} lamports ({:.9} SOL)", claimable_lamports, claimable_sol);
1789    }
1790
1791    if claimable_lamports == 0 {
1792        if language == Language::English {
1793            println!("\n{}", "ℹ️  No cashback available to claim.".bright_yellow());
1794        } else {
1795            println!("\n{}", "ℹ️  暂无可领取的返现。".bright_yellow());
1796        }
1797        return Ok(());
1798    }
1799
1800    // Step 3: 确认是否领取
1801    let confirm_prompt = if language == Language::English {
1802        format!("\nClaim {:.9} SOL (WSOL)? (yes/no) [no]: ", claimable_sol)
1803    } else {
1804        format!("\n领取 {:.9} SOL (WSOL)? (yes/no) [no]: ", claimable_sol)
1805    };
1806    let confirm = read_input(&confirm_prompt, "no");
1807
1808    if confirm.to_lowercase() != "yes" {
1809        let msg = if language == Language::English {
1810            "❌ Claim cancelled"
1811        } else {
1812            "❌ 已取消领取"
1813        };
1814        println!("\n{}", msg.red());
1815        return Ok(());
1816    }
1817
1818    // Step 4: 执行领取
1819    if language == Language::English {
1820        println!("\n{}", "🚀 Claiming...".bright_cyan());
1821    } else {
1822        println!("\n{}", "🚀 正在领取...".bright_cyan());
1823    }
1824
1825    let sig = match tokio::runtime::Handle::try_current() {
1826        Ok(handle) => {
1827            let keypair_b58 = bs58::encode(keypair.to_bytes()).into_string();
1828            let rpc_url_clone = rpc_url.clone();
1829            std::thread::spawn(move || {
1830                let kp = Keypair::from_base58_string(&keypair_b58);
1831                let payer = Arc::new(kp);
1832                let config = TradeConfig::builder(
1833                    rpc_url_clone.clone(),
1834                    vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url_clone)],
1835                    CommitmentConfig::confirmed(),
1836                )
1837                .create_wsol_ata_on_startup(false)
1838                .use_seed_optimize(false)
1839                .check_min_tip(false)
1840                .log_enabled(false)
1841                .swqos_cores_from_end(false)
1842                .mev_protection(false)
1843                .build();
1844                handle.block_on(async move {
1845                    let client = SolanaTrade::new(payer, config).await;
1846                    client.claim_cashback_pumpswap().await.map_err(|e| e.to_string())
1847                })
1848            })
1849            .join()
1850            .map_err(|_| "Thread panicked".to_string())??
1851        }
1852        Err(_) => {
1853            let payer = Arc::new(keypair.insecure_clone());
1854            let config = TradeConfig::builder(
1855                rpc_url.clone(),
1856                vec![sol_trade_sdk::swqos::SwqosConfig::Default(rpc_url)],
1857                CommitmentConfig::confirmed(),
1858            )
1859            .create_wsol_ata_on_startup(false)
1860            .use_seed_optimize(false)
1861            .check_min_tip(false)
1862            .log_enabled(false)
1863            .swqos_cores_from_end(false)
1864            .mev_protection(false)
1865            .build();
1866            let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
1867            let client = rt.block_on(SolanaTrade::new(payer, config));
1868            rt.block_on(client.claim_cashback_pumpswap()).map_err(|e| e.to_string())?
1869        }
1870    };
1871
1872    if language == Language::English {
1873        println!("{}", "✅ Claim successful!".bright_green().bold());
1874        println!("Signature: {}", sig.yellow());
1875        println!("Explorer: https://solscan.io/tx/{}", sig);
1876    } else {
1877        println!("{}", "✅ 领取成功!".bright_green().bold());
1878        println!("签名: {}", sig.yellow());
1879        println!("浏览器: https://solscan.io/tx/{}", sig);
1880    }
1881    Ok(())
1882}
1883
1884#[cfg(not(feature = "sol-trade-sdk"))]
1885pub fn pumpfun_sell_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
1886    Err(if language == Language::English {
1887        "Pump.fun sell requires 'sol-trade-sdk' feature. Please rebuild with:\ncargo build --release --features sol-trade-sdk".to_string()
1888    } else {
1889        "Pump.fun 内盘卖出需要 'sol-trade-sdk' 功能。请使用以下命令重新编译:\ncargo build --release --features sol-trade-sdk".to_string()
1890    })
1891}
1892
1893#[cfg(not(feature = "sol-trade-sdk"))]
1894pub fn pumpfun_cashback_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
1895    Err(if language == Language::English {
1896        "Pump cashback requires 'sol-trade-sdk' feature.".to_string()
1897    } else {
1898        "Pump 返现需要 'sol-trade-sdk' 功能。".to_string()
1899    })
1900}
1901
1902#[cfg(not(feature = "sol-trade-sdk"))]
1903pub fn pumpswap_cashback_interactive(_keypair: &Keypair, language: Language) -> Result<(), String> {
1904    Err(if language == Language::English {
1905        "PumpSwap cashback requires 'sol-trade-sdk' feature.".to_string()
1906    } else {
1907        "PumpSwap 返现需要 'sol-trade-sdk' 功能。".to_string()
1908    })
1909}