Skip to main content

pancakeswap_sdk/
lib.rs

1/// This module is the pancakeswap service entry module.
2pub mod abi;
3pub mod analytics;
4pub mod events;
5pub mod factory;
6pub mod farm;
7pub mod global;
8pub mod limit_order;
9pub mod liquidity;
10pub mod multicall;
11pub mod price;
12pub mod router;
13pub mod tool;
14pub mod types;
15pub mod v3_position;
16
17use ethers::{
18    providers::{Http, Provider},
19    signers::{LocalWallet, Signer},
20    types::{Address, U256},
21};
22use evm_client::EvmType;
23use evm_sdk::Evm;
24use std::sync::Arc;
25
26use crate::{
27    abi::IQuoter,
28    analytics::AnalyticsService,
29    factory::FactoryService,
30    global::{
31        BASE_QUOTER, BASE_ROUTER_V3, BSC_QUOTER, BSC_ROUTER_V2, BSC_ROUTER_V3, ETHEREUM_QUOTER,
32        ETHEREUM_ROUTER_V2, ETHEREUM_ROUTER_V3,
33    },
34    liquidity::LiquidityService,
35    price::PriceService,
36    router::RouterService,
37    types::PriceInfo,
38};
39use evm_sdk::types::EvmError;
40/// PancakeSwap Service for interacting with PancakeSwap protocols
41pub struct PancakeSwapService {
42    evm: Arc<Evm>,
43    router: Arc<RouterService>,
44    factory: Arc<FactoryService>,
45    liquidity: Arc<LiquidityService>,
46    price: Arc<PriceService>,
47    analytics: Arc<AnalyticsService>,
48}
49
50impl PancakeSwapService {
51    /// Create a new PancakeSwap service instance
52    pub fn new(evm: Arc<Evm>) -> Self {
53        Self {
54            evm: evm.clone(),
55            router: Arc::new(RouterService::new(evm.clone())),
56            factory: Arc::new(FactoryService::new(evm.clone())),
57            liquidity: Arc::new(LiquidityService::new(evm.clone())),
58            price: Arc::new(PriceService::new(evm.clone())),
59            analytics: Arc::new(AnalyticsService::new(evm.clone())),
60        }
61    }
62
63    /// Get amounts out for a swap (V2)
64    ///
65    /// # Example
66    /// ```
67    /// use pancake_swap_sdk::{PancakeSwapService, EvmClient, EvmType};
68    /// use ethers::types::{Address, U256};
69    ///
70    /// #[tokio::main]
71    /// async fn main() -> Result<(),()> {
72    ///     let client = EvmClient::new(EvmType::Bsc).await?;
73    ///     let service = PancakeSwapService::new(std::sync::Arc::new(client));
74    ///     
75    ///     let amount_in = U256::from(1000000000000000000u64); // 1 token
76    ///     let path = vec![
77    ///         "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c".parse()?, // WBNB
78    ///         "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse()?, // BUSD
79    ///     ];
80    ///     
81    ///     let amounts = service.get_amounts_out_v2(amount_in, path).await?;
82    ///     println!("Output amounts: {:?}", amounts);
83    ///     Ok(())
84    /// }
85    /// ```
86    pub async fn get_amounts_out_v2(
87        &self,
88        amount_in: U256,
89        path: Vec<Address>,
90    ) -> Result<Vec<U256>, EvmError> {
91        let router_address =
92            PancakeSwapConfig::v2_router_address(self.evm.client.evm_type.unwrap())?;
93        let router = self.router.v2_router(router_address);
94        router
95            .get_amounts_out(amount_in, path)
96            .call()
97            .await
98            .map_err(|e| EvmError::ContractError(format!("Failed to get amounts out: {}", e)))
99    }
100
101    /// Get amounts in for a swap (V2)
102    pub async fn get_amounts_in_v2(
103        &self,
104        amount_out: U256,
105        path: Vec<Address>,
106    ) -> Result<Vec<U256>, EvmError> {
107        let router_address =
108            PancakeSwapConfig::v2_router_address(self.evm.client.evm_type.unwrap())?;
109        let router = self.router.v2_router(router_address);
110        router
111            .get_amounts_in(amount_out, path)
112            .call()
113            .await
114            .map_err(|e| EvmError::ContractError(format!("Failed to get amounts in: {}", e)))
115    }
116
117    /// execute V2 swap
118    ///
119    /// # Example
120    /// ```
121    /// use pancake_swap_sdk::{PancakeSwapService, EvmClient, EvmType};
122    /// use ethers::types::{Address, U256};
123    ///
124    /// #[tokio::main]
125    /// async fn main() -> Result<(),()> {
126    ///     let private_key = "your_private_key_here";
127    ///     let client = EvmClient::with_wallet(EvmType::Bsc, private_key).await?;
128    ///     let service = PancakeSwapService::new(std::sync::Arc::new(client));
129    ///     
130    ///     let token_in: Address = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c".parse()?; // WBNB
131    ///     let token_out: Address = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse()?; // BUSD
132    ///     let amount_in = U256::from(1000000000000000000u64); // 1 BNB
133    ///     let slippage_percent = 1.0; // 1% slippage
134    ///     
135    ///     let tx_hash = service.swap_v2(token_in, token_out, amount_in, slippage_percent).await?;
136    ///     println!("Transaction hash: {:?}", tx_hash);
137    ///     Ok(())
138    /// }
139    /// ```
140    pub async fn swap_v2(
141        &self,
142        token_in: Address,
143        token_out: Address,
144        amount_in: U256,
145        slippage_percent: f64,
146    ) -> Result<ethers::types::H256, EvmError> {
147        if self.evm.client.wallet.is_none() {
148            return Err(EvmError::WalletError("No wallet configured".to_string()));
149        }
150
151        let router_address =
152            PancakeSwapConfig::v2_router_address(self.evm.client.evm_type.unwrap())?;
153        let deadline = crate::tool::time_utils::calculate_deadline(30); // 30 minutes
154
155        // Get expected output
156        let amounts = self
157            .get_amounts_out_v2(amount_in, vec![token_in, token_out])
158            .await?;
159        let expected_out = amounts
160            .last()
161            .ok_or_else(|| EvmError::CalculationError("Invalid path".to_string()))?;
162
163        // Calculate minimum output with slippage
164        let amount_out_min = self.calculate_amount_with_slippage(*expected_out, slippage_percent);
165        let wallet_address = self.evm.client.wallet.as_ref().unwrap().address();
166
167        let router = self.router.v2_router(router_address);
168        let tx = router.swap_exact_tokens_for_tokens(
169            amount_in,
170            amount_out_min,
171            vec![token_in, token_out],
172            wallet_address,
173            deadline.into(),
174        );
175
176        let pending_tx = tx
177            .send()
178            .await
179            .map_err(|e| EvmError::TransactionError(format!("Failed to swap tokens: {}", e)))?;
180
181        Ok(pending_tx.tx_hash())
182    }
183
184    /// Execute V3 swap
185    ///
186    /// # Example
187    /// ```
188    /// use pancake_swap_sdk::{PancakeSwapService, EvmClient, EvmType};
189    /// use ethers::types::{Address, U256};
190    ///
191    /// #[tokio::main]
192    /// async fn main() -> Result<(),()> {
193    ///     let private_key = "your_private_key_here";
194    ///     let client = EvmClient::with_wallet(EvmType::Bsc, private_key).await?;
195    ///     let service = PancakeSwapService::new(std::sync::Arc::new(client));
196    ///     
197    ///     let token_in: Address = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c".parse()?; // WBNB
198    ///     let token_out: Address = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse()?; // BUSD
199    ///     let amount_in = U256::from(1000000000000000000u64); // 1 BNB
200    ///     let slippage_percent = 1.0; // 1% slippage
201    ///     let fee_tier = Some(500); // 0.05% fee
202    ///     
203    ///     let tx_hash = service.swap_v3(token_in, token_out, amount_in, slippage_percent, fee_tier).await?;
204    ///     println!("Transaction hash: {:?}", tx_hash);
205    ///     Ok(())
206    /// }
207    /// ```
208    pub async fn swap_v3(
209        &self,
210        token_in: Address,
211        token_out: Address,
212        amount_in: U256,
213        slippage_percent: f64,
214        fee_tier: Option<u32>,
215    ) -> Result<ethers::types::H256, EvmError> {
216        if self.evm.client.wallet.is_none() {
217            return Err(EvmError::WalletError("No wallet configured".to_string()));
218        }
219
220        let router_address =
221            PancakeSwapConfig::v3_router_address(self.evm.client.evm_type.unwrap())?;
222        let deadline = crate::tool::time_utils::calculate_deadline(30);
223
224        let fee = fee_tier.unwrap_or_else(|| self.get_default_fee_tier(token_in, token_out));
225        let expected_out = self
226            .simulate_v3_swap(token_in, token_out, fee, amount_in)
227            .await?;
228        let amount_out_min = self.calculate_amount_with_slippage(expected_out, slippage_percent);
229        let wallet_address = self.evm.client.wallet.as_ref().unwrap().address();
230
231        let router = self.router.v3_router_signer(router_address)?;
232
233        // 使用单独的参数调用 exactInputSingle
234        let tx = router.exact_input_single(
235            token_in,
236            token_out,
237            fee,
238            wallet_address,
239            deadline.into(),
240            amount_in,
241            amount_out_min,
242            U256::zero(),
243        );
244
245        let pending_tx = tx
246            .send()
247            .await
248            .map_err(|e| EvmError::TransactionError(format!("Failed to execute V3 swap: {}", e)))?;
249
250        Ok(pending_tx.tx_hash())
251    }
252
253    /// Auto swap - find best price between V2 and V3 and execute
254    ///
255    /// # Example
256    /// ```
257    /// use pancake_swap_sdk::{PancakeSwapService, EvmClient, EvmType};
258    /// use ethers::types::{Address, U256};
259    ///
260    /// #[tokio::main]
261    /// async fn main() -> Result<(),()> {
262    ///     let private_key = "your_private_key_here";
263    ///     let client = EvmClient::with_wallet(EvmType::Bsc, private_key).await?;
264    ///     let service = PancakeSwapService::new(std::sync::Arc::new(client));
265    ///     
266    ///     let token_in: Address = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c".parse()?; // WBNB
267    ///     let token_out: Address = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse()?; // BUSD
268    ///     let amount_in = U256::from(1000000000000000000u64); // 1 BNB
269    ///     let slippage_percent = 1.0; // 1% slippage
270    ///     
271    ///     let result = service.auto_swap(token_in, token_out, amount_in, slippage_percent).await?;
272    ///     println!("Auto swap result: {:?}", result);
273    ///     Ok(())
274    /// }
275    /// ```
276    pub async fn auto_swap(
277        &self,
278        token_in: Address,
279        token_out: Address,
280        amount_in: U256,
281        slippage_percent: f64,
282    ) -> Result<crate::types::AutoSwapResult, EvmError> {
283        // Get best price comparison
284        let price_comparison = self.get_best_price(token_in, token_out, amount_in).await?;
285
286        let price_comparison_clone = price_comparison.clone();
287
288        let (selected_version, amount_out_min, tx_hash) = match price_comparison.best {
289            crate::types::PriceSource::V2 => {
290                let v2_info = price_comparison.v2.ok_or_else(|| {
291                    EvmError::CalculationError("V2 price not available".to_string())
292                })?;
293                let amount_out_min =
294                    self.calculate_amount_with_slippage(v2_info.amount_out, slippage_percent);
295                let tx_hash = self
296                    .swap_v2(token_in, token_out, amount_in, slippage_percent)
297                    .await?;
298                (crate::types::PoolVersion::V2, amount_out_min, tx_hash)
299            }
300            crate::types::PriceSource::V3 => {
301                let v3_info = price_comparison.v3.ok_or_else(|| {
302                    EvmError::CalculationError("V3 price not available".to_string())
303                })?;
304                let amount_out_min =
305                    self.calculate_amount_with_slippage(v3_info.amount_out, slippage_percent);
306                let fee = self.get_default_fee_tier(token_in, token_out);
307                let tx_hash = self
308                    .swap_v3(token_in, token_out, amount_in, slippage_percent, Some(fee))
309                    .await?;
310                (crate::types::PoolVersion::V3, amount_out_min, tx_hash)
311            }
312        };
313
314        Ok(crate::types::AutoSwapResult {
315            tx_hash,
316            version: selected_version,
317            expected_amount_out: amount_out_min,
318            price_comparison: price_comparison_clone,
319        })
320    }
321
322    /// Get best price comparison between V2 and V3
323    pub async fn get_best_price(
324        &self,
325        token_in: Address,
326        token_out: Address,
327        amount_in: U256,
328    ) -> Result<crate::types::PriceComparison, EvmError> {
329        let v2_price = self.get_v2_price(token_in, token_out, amount_in).await;
330        let v3_price = self.get_v3_price(token_in, token_out, amount_in).await;
331        let best_price = match (&v2_price, &v3_price) {
332            (Ok(v2), Ok(v3)) => {
333                if v2.amount_out > v3.amount_out {
334                    crate::types::PriceSource::V2
335                } else {
336                    crate::types::PriceSource::V3
337                }
338            }
339            (Ok(_), Err(_)) => crate::types::PriceSource::V2,
340            (Err(_), Ok(_)) => crate::types::PriceSource::V3,
341            _ => return Err(EvmError::CalculationError("No price available".to_string())),
342        };
343        Ok(crate::types::PriceComparison {
344            v2: v2_price.ok(),
345            v3: v3_price.ok(),
346            best: best_price,
347        })
348    }
349
350    /// Swap exact tokens for tokens (V2)
351    pub async fn swap_exact_tokens_for_tokens(
352        &self,
353        amount_in: U256,
354        amount_out_min: U256,
355        path: Vec<Address>,
356        deadline: u64,
357    ) -> Result<ethers::types::H256, EvmError> {
358        if self.evm.client.wallet.is_none() {
359            return Err(EvmError::WalletError("No wallet configured".to_string()));
360        }
361        let router_address =
362            PancakeSwapConfig::v2_router_address(self.evm.client.evm_type.unwrap())?;
363        let wallet_address = self.evm.client.wallet.as_ref().unwrap().address();
364        let router = self.router.v2_router(router_address);
365        let tx = router.swap_exact_tokens_for_tokens(
366            amount_in,
367            amount_out_min,
368            path,
369            wallet_address,
370            deadline.into(),
371        );
372        let pending_tx = tx
373            .send()
374            .await
375            .map_err(|e| EvmError::TransactionError(format!("Failed to swap tokens: {}", e)))?;
376        Ok(pending_tx.tx_hash())
377    }
378
379    /// Get V2 price  
380    async fn get_v2_price(
381        &self,
382        token_in: Address,
383        token_out: Address,
384        amount_in: U256,
385    ) -> Result<PriceInfo, EvmError> {
386        let amounts = self
387            .get_amounts_out_v2(amount_in, vec![token_in, token_out])
388            .await?;
389        let amount_out = amounts
390            .last()
391            .ok_or_else(|| EvmError::CalculationError("Invalid path".to_string()))?;
392
393        Ok(PriceInfo {
394            token_in,
395            token_out,
396            amount_in,
397            amount_out: *amount_out,
398            price: amount_out.as_u128() as f64 / amount_in.as_u128() as f64,
399            price_impact: 0.0,
400            timestamp: crate::tool::time_utils::current_timestamp() as u64,
401        })
402    }
403
404    /// Get V3 price  
405    async fn get_v3_price(
406        &self,
407        token_in: Address,
408        token_out: Address,
409        amount_in: U256,
410    ) -> Result<PriceInfo, EvmError> {
411        let fee = self.get_default_fee_tier(token_in, token_out);
412        let amount_out = self
413            .simulate_v3_swap(token_in, token_out, fee, amount_in)
414            .await?;
415
416        Ok(PriceInfo {
417            token_in,
418            token_out,
419            amount_in,
420            amount_out,
421            price: amount_out.as_u128() as f64 / amount_in.as_u128() as f64,
422            price_impact: 0.0,
423            timestamp: crate::tool::time_utils::current_timestamp() as u64,
424        })
425    }
426
427    /// Simulate V3 swap to get expected output by querying the actual Quoter contract
428    async fn simulate_v3_swap(
429        &self,
430        token_in: Address,
431        token_out: Address,
432        fee: u32,
433        amount_in: U256,
434    ) -> Result<U256, EvmError> {
435        use ethers::prelude::*;
436        // Get Quoter contract address
437        let quoter_address = match self.evm.client.evm_type {
438            Some(EvmType::BSC_MAINNET) => BSC_QUOTER
439                .parse::<Address>()
440                .map_err(|e| EvmError::ConfigError(format!("Invalid BSC quoter address: {}", e)))?,
441            Some(EvmType::ETHEREUM_MAINNET) => ETHEREUM_QUOTER.parse::<Address>().map_err(|e| {
442                EvmError::ConfigError(format!("Invalid Ethereum quoter address: {}", e))
443            })?,
444            Some(EvmType::BASE_MAINNET) => BASE_QUOTER.parse::<Address>().map_err(|e| {
445                EvmError::ConfigError(format!("Invalid Ethereum quoter address: {}", e))
446            })?,
447            _ => {
448                return Err(EvmError::ConfigError(
449                    "Unsupported chain for V3 Quoter".to_string(),
450                ));
451            }
452        };
453        // Create Quoter contract instance
454        let quoter = IQuoter::new(quoter_address, self.evm.client.provider.clone());
455        let amount_out = quoter
456            .quote_exact_input_single(token_in, token_out, fee.into(), amount_in, U256::zero())
457            .call()
458            .await
459            .map_err(|e| EvmError::ContractError(format!("Failed to quote V3 swap: {}", e)))?;
460        Ok(amount_out)
461    }
462
463    /// Calculate amount with slippage
464    fn calculate_amount_with_slippage(&self, amount: U256, slippage_percent: f64) -> U256 {
465        let slippage_factor = (100.0 - slippage_percent) / 100.0;
466        let amount_f64 = amount.as_u128() as f64 * slippage_factor;
467        U256::from(amount_f64 as u128)
468    }
469
470    /// Get default fee tier based on token pair
471    fn get_default_fee_tier(&self, token_a: Address, token_b: Address) -> u32 {
472        // Simple logic: use lower fees for stablecoin pairs
473        let stable_tokens = [
474            PancakeSwapConfig::busd_address(self.evm.client.evm_type.unwrap()).unwrap_or_default(),
475            PancakeSwapConfig::usdt_address(self.evm.client.evm_type.unwrap()).unwrap_or_default(),
476        ];
477        if stable_tokens.contains(&token_a) && stable_tokens.contains(&token_b) {
478            100 // 0.01% for stable pairs
479        } else {
480            500 // 0.05% for other pairs
481        }
482    }
483}
484
485/// PancakeSwap configuration for different chains
486pub struct PancakeSwapConfig;
487
488impl PancakeSwapConfig {
489    pub fn v2_router_address(chain: EvmType) -> Result<Address, EvmError> {
490        match chain {
491            EvmType::BSC_MAINNET => Ok(BSC_ROUTER_V2.parse().unwrap()),
492            EvmType::ETHEREUM_MAINNET => Ok(ETHEREUM_ROUTER_V2.parse().unwrap()),
493            EvmType::BASE_MAINNET => Ok(BSC_ROUTER_V2.parse().unwrap()),
494            _ => Err(EvmError::ConfigError(
495                "Unsupported chain for PancakeSwap V2".to_string(),
496            )),
497        }
498    }
499
500    pub fn v3_router_address(chain: EvmType) -> Result<Address, EvmError> {
501        match chain {
502            EvmType::BSC_MAINNET => Ok(BSC_ROUTER_V3.parse().unwrap()),
503            EvmType::ETHEREUM_MAINNET => Ok(ETHEREUM_ROUTER_V3.parse().unwrap()),
504            EvmType::BASE_MAINNET => Ok(BASE_ROUTER_V3.parse().unwrap()),
505            _ => Err(EvmError::ConfigError(
506                "Unsupported chain for PancakeSwap V3".to_string(),
507            )),
508        }
509    }
510
511    pub fn busd_address(chain: EvmType) -> Result<Address, EvmError> {
512        match chain {
513            EvmType::BSC_MAINNET => Ok("0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"
514                .parse()
515                .unwrap()),
516            EvmType::ETHEREUM_MAINNET => Ok("0x4Fabb145d64652a948d72533023f6E7A623C7C53"
517                .parse()
518                .unwrap()),
519            _ => Err(EvmError::ConfigError(
520                "Unsupported chain for BUSD".to_string(),
521            )),
522        }
523    }
524
525    pub fn usdt_address(chain: EvmType) -> Result<Address, EvmError> {
526        match chain {
527            EvmType::BSC_MAINNET => Ok("0x55d398326f99059fF775485246999027B3197955"
528                .parse()
529                .unwrap()),
530            EvmType::ETHEREUM_MAINNET => Ok("0xdAC17F958D2ee523a2206206994597C13D831ec7"
531                .parse()
532                .unwrap()),
533            _ => Err(EvmError::ConfigError(
534                "Unsupported chain for USDT".to_string(),
535            )),
536        }
537    }
538}