1pub 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;
40pub 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 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 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 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 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); 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 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 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 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 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 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 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 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 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 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 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 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 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 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 fn get_default_fee_tier(&self, token_a: Address, token_b: Address) -> u32 {
472 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 } else {
480 500 }
482 }
483}
484
485pub 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}